• Linux操作系统和进程基本概念


     

     

     其实人生在世,是不太需要别人的建议的,不经历过不会明白的。

                                                                                                            -Deft

    目录

    🎵一、冯诺依曼体系和操作系统

    🎈1.1冯诺依曼体系结构

    🎈1.2操作系统

    Ⅰ概念

    Ⅱ目的

    🎵二、进程

    🎈2.1基本概念

     🎈2.2描述进程-PCB

    🎈2.3进程基本指令

    👓Ⅰps ajx 

     👓Ⅱkill 指令

    👓Ⅲ getpid && getppid

    👓 Ⅳ fork

     🎵三、进程状态

    🎈ⅠR状态

    🎈Ⅱ S状态

    👓①阻塞状态

    👓②挂起状态

    🎈 ⅢT状态

    🎈ⅣD状态

    🎈Ⅴ t状态

     🎈Ⅵ Z状态

    🎈Ⅶ 孤儿进程

    🎵四、进程优先级

    🎈Ⅰ基本概念

    🎈Ⅱ查看进程优先级

    🎵五、其他概念

    🎵一、冯诺依曼体系和操作系统

    🎈1.1冯诺依曼体系结构

    我们常见的台式计算机、笔记本以及不常见的公司使用的服务器,大部分都遵从冯诺依曼体系。

     冯诺依曼体系结构主要讲述的是什么呢?它主要向我们介绍了计算机的基本架构:

    输入设备:包括键盘、鼠标、扫描仪、写板等。

    中央处理器(CPU):含有运算器、控制器等

    输出设备:显示器、打印机、网卡等。

    我们输入设备输入的数据必须先加载到内存(存储器),才能被CPU处理,CPU能且只能对内存进行读写不能访问外设(输入设备)输出设备从内存读取信息

    🎈1.2操作系统

    Ⅰ概念

    任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。简单来说,操作系统是一款软硬件资源管理的软件,包括:

    🖊内核(进程管理,内存管理,文件管理,驱动管理)

    🖊其他程序(例如函数库,shell程序等)

    Ⅱ目的

    设计操作系统的目的是为了与硬件交互,通过合理的管理软硬件资源,为用户提供良好的执行环境。

     Linux操作系统内核是用C语言写的,所以它是如何管理硬件呢?操作系统本身并不管理硬件,驱动管理硬件,从硬件获取数据,对数据做管理,用struct结构体描述,把数据做分类描述,对应设备做特定的结构管理起来,比如队列,链表等数据结构。最后交付给操作系统,而我们用户通过操作系统提供的接口查看这些数据信息。

    🎵二、进程

    🎈2.1基本概念

    进程:一个运行起来(加载到内存)的程序。也就是在内存中的程序,进程与程序相比,具有动态属性

    比如Windows下我们打开任务管理器就可以查看当前正在运行的程序,这些都是一个个独立的进程:

     🎈2.2描述进程-PCB

    进程信息被放在一个叫做进程控制块的数据结构中,也就是PCBLinux操作系统下的PCB是:task_struct。进程被转化为内核数据结构(task_struct)和进程对应的磁盘代码。

    PCB概念的提出,使得对进程管理,变成了对进程对应的PCB相关管理。

     task_struct 内容分类:

    🖊标示符:描述本进程的唯一标示符,用来区分其他进程。

    🖊状态:任务状态,退出代码,退出信号等。

    🖊优先级:相对于其他进程的优先级。

    🖊程序计数器:程序中即将被执行的下一条指令的地址。

    🖊内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。

    🖊上下文数据:进程执行时处理器的寄存器中的数据。

    🖊I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

    🖊记账信息:可能包括处理器时间总和,使用的时间钟总和,时间限制,记账号等。

    🖊其他信息。

    🎈2.3进程基本指令

    👓Ⅰps ajx 

    ps ajx :显示当前目录下所有进程

     我们也可以通过管道检索指定正在运行的进程。比如:

     当然,我们看向这些内容并不知道是什么数据,我们可以把它的标头打印出来方便我们查看。

     👓Ⅱkill 指令

    kill -l 我们查看一下这个指令。

    kill指令集下有很多指令,我们这里需要着重关注的有三条指令 :

    🖊kill -9 终止进程

    🖊kill -19 停止/暂停进程

    🖊kill -18 继续进程

    我们先来看如何终止一个进程。

     

     这里的PID是进程的编号,Linux下的大多数对进程的操作都是对进程的PID,也可称之为它的ID进行操作。

     执行过后,进程就被终止了。还有两个重要的kill指令,后续会提及。

    👓Ⅲ getpid && getppid

    getpidgetppidLinux下获取当前进程和父进程id的函数。

     

    1. //头文件
    2. #include
    3. #include
    4. //sunopsis
    5. pid_t getpid(void);
    6. pid_t getppid(void);
    7. //返回值
    8. getpid() 返回当前进程ID
    9. getppid() 返回当前进程父进程ID

    ①getpid

    我们写一个程序来查看一下它的ID

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. while(1)
    7. {
    8. printf("我是一个进程,我的ID是%d\n",getpid());
    9. sleep(1);
    10. }
    11. return 0;
    12. }

     那么这个ID它真的可以代表这个进程吗?我们来验证一下:

     

     

    通过验证,我们发现,确实如此,pid确实可以代表各个进程,就如同我们的学号对应一位同学。

     ②getppid

    在了解pid之后,ppid又是何方神圣呢?ppid是当前进程的父进程id。按照关系,当前进程是其父进程的子进程。

    我们编写一个程序来查看一下父进程的id

    1. /*myprocess.c*/
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. while(1)
    8. {
    9. printf("我是一个进程,我的ID是%d,父进程的ID是%d\n",getpid(),getppid());
    10. sleep(1);
    11. }
    12. return 0;
    13. }

     我们发现子进程的id每次运行程序都会变化,而父进程的id是不会变化的。也就是说,父进程相对于子进程来说只有一个,而子进程可以经父进程创建生成很多个执行程序时由子进程来执行,就算挂了也不会影响父进程。而shell执行命令就是以子进程来执行的。

    那么这里子进程父进程到底是什么呢?其实就是当前的命令行解释器:

     

    👓 Ⅳ fork

    fork用于创建子进程。

     

     

    1. /*fork*/
    2. //头文件
    3. #include
    4. pid_t fork(void);
    5. //返回值
    6. //如果创建成功,子进程的ID被返回给父进程,0返回给子进程。如果失败,-1返回给父进程,没有子进程创建并报错。

    我们研究一下返回值就会发现一个惊奇的现象:如果创建成功,子进程的PID被返回给父进程,0返回给子进程。失败-1返回给父进程,没有子进程创建。我们发现fork的返回值有两个!!而这个会对我们的程序有什么影响呢?

     我们看到,同一段代码执行了两次,这两次一次是父进程执行的,一次是子进程执行的。而在这里我们也可以看出系统和语言的不同,语言是不可能出现有两个返回值这样的情况的。

    所以根据这种特性,我们在使用fork的时候,需要使用if条件判断执行。

     如果我现在把正在执行的进程的可执行程序给删除,那么进程还会继续执行吗?

     我们发现进程还在执行,但是我们查看exe文件会发现闪红并告诉我们deleted

     🎵三、进程状态

    为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态,而一个进程可以有多少种状态呢?我们可以查看一下Linux内核代码的定义:
     

    1. /*
    2. * The task state array is a strange "bitmap" of
    3. * reasons to sleep. Thus "running" is zero, and
    4. * you can test for combinations of others with
    5. * simple bit tests.
    6. */
    7. static const char * const task_state_array[] = {
    8. "R (running)", /* 0 */
    9. "S (sleeping)", /* 1 */
    10. "D (disk sleep)", /* 2 */
    11. "T (stopped)", /* 4 */
    12. "t (tracing stop)", /* 8 */
    13. "X (dead)", /* 16 */
    14. "Z (zombie)", /* 32 */
    15. };

    我们可以看到后面的注释,没错,其实进程状态没老铁想的那么复杂,在PCB内部就是简单的整数表示的。

    查看进程状态的指令是 ps aux / ps ajx / ps axj 这些都可以。

    🎈ⅠR状态

    要了解什么是R状态,我们首先要明白进程具体在CPU如何执行的。

    在操作系统CPU有它的运行队列,在这个队列中都是等待被CPU执行代码的进程,而正在被CPU处理或处于运行队列的进程所处的状态都称为R状态:运行状态。

     这里需要阐明几点:

    1、一个CPU在任何时刻只能运行一个进程,一个CPU一个运行队列。

    2、让进程进入运行队列的本质就是将该进程的task_struct 结构体对象放入运行队列中。

    3、进程PCB只要在runqueue,就是R状态,并不一定是这个进程在运行才是运行状态。

     

    🎈Ⅱ S状态

    👓①阻塞状态

    S状态意味着进程在等待时间完成。比较常见的S状态就是阻塞状态

    那么什么是阻塞状态呢?很简单,我们的进程不会只占用CPU资源,它可能随时随地地占用外设,比如我们要打印一些字符到显示器,就是占用外设。那么我们都知道CPU的运行速度是远大于外设的,如果我们的进程需要访问外设而外设较慢,那么此时CPU会停下来等我们访问外设结束吗?这样显然太过于浪费CPU资源了,此时,为了追求效率,操作系统会把该进程连接到外设的等待队列,然后CPU调用执行别的进程。那么此时进程就不是R状态,而是阻塞状态,也称为S状态

    当然,当进程访问外设结束的时候,操作系统会得知然后把进程状态由阻塞(S)改为R状态,放在CPU队列。

     可以看到这里我们需要打印到显示器就是进程在访问使用外设,这里是死循环不停调用外设显示器,当然中间也会调用CPU,但是访问外设可能占用99%的时间,剩下的1%时间调用CPU,所以我们查询显示一般都是S状态。

    👓②挂起状态

    什么是挂起状态呢?我们都知道内存空间是有限的,如果我们的内存空间充满了大量阻塞的进程导致内存空间不够用这时候怎么办呢?不够的空间我们要找哪里去借空间,去哪里呢?磁盘就是一个不错的地方,它的空间很大!没错,我们的操作系统也是这么处理的,它会把阻塞进程的代码和数据暂时保存到磁盘上为其他进程腾出空间。这种要等待很长时间,被暂时保存到磁盘的进程状态称为挂起。

    🎈 ⅢT状态

    T停止状态:可以通过发送SIGSTOP信号给进程来停止(T)进程,这个被暂停的进程可以通过SIGCONT信号让进程继续运行。

    首先我们要搞清楚这两个信号是什么鬼?不知道老铁是否还有印象我上文提到的两个没有细说的kill指令呢?

     没错,就是kill -19kill -18指令。

    kill -19 指令停止进程。

     kill -18 继续进程

     差点忘记,这里还有一个点没说。不知道老铁注意到没有我们之前的进程状态是R+状态,现在是R状态,为什么少了+号,以及+号有什么用处呢?

    🖊状态后带有+号的是前台,我们对它的输入无效,只能通过CTRL+c或者kill -9 终止进程。

    🖊状态后没有+号的是后台,我们输入的指令有效,但是仍在运行程序,CTRL+c也不能终止,只能通过kill -9 终止进程。

    演示:

     

     通过演示相信老铁感受到了后台和前台的不同。

    🎈ⅣD状态

    D磁盘休眠状态有时候也叫不可中断睡眠状态,在这个状态的进程通常会等待IO的结束。

    D状态叫做深度休眠,那它和S状态有什么区别吗?S状态是浅度睡眠,能够被终止,而D状态是不可被终止的

    那么D状态的应用情景是什么呢?我们有没有这样的一种情景:当操作系统严重内存不足,就连挂起也解决不了的时候,操作系统会采取终止进程的操作,否则操作系统会挂掉。而我们不想让操作系统挂掉某些重要的进程,就把这些进程标识为D状态,避免被终止。在该状态的进程无法被OS杀掉,只能通过断电,或者进程自己醒来来解决

    这种情形只有高IO的情形下才会出现。

    🎈Ⅴ t状态

    t (tracing sleep)状态 也是一种暂停状态。调试等待运行,当我们调试运行到断点处时我们发现gdb调试的状态为t状态

    插播一个小tip

     makefile的小技巧

     🎈Ⅵ Z状态

    Z(zombie)-僵尸进程。

    🖊僵死状态是一种比较特殊的状态。当进程退出但是父进程或操作系统没有读取到子进程退出的返回代码时就会产生僵尸进程。

    🖊僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程或操作系统读取退出状态代码。

    🖊所以,只要子进程退出,父进程或操作系统还在运行,但父进程或操作系统没有读取子进程状态,子进程进入Z状态。

    我们这里写一个脚本指令循环监控显示。

     我们写一个函数,让父进程循环,子进程运行后就停止。

     运行查询进程状态:

     因为我们的代码是子进程只打印一次就停止,父进程是不断循环打印,所以父进程未结束而子进程结束时父进程处于S+状态,而子进程处于Z+状态。

    当然,我们不只这种方式使子进程先停止,如果我们使用kill指令终止子进程,子进程也会处于僵尸状态等待父进程运行结束。

     僵尸进程的危害:

    🖊进程的退出状态只要不被父进程或操作系统读取就会一直维持僵尸状态下去,内存也一直要分配空间给它。那么要是一个父进程创建很多子进程,而子进程早早结束父进程不回收就会造成内存资源的浪费,可能造成内存泄漏

    至此,我们的进程有三种状态是无法被杀死的状态:D X Z 

    🎈Ⅶ 孤儿进程

    之前是子进程先于父进程结束而处于Z状态,那么有没有可能父进程先结束呢?有的,这种父进程提前结束的子进程被称为孤儿进程

    注意啊,这里并不是在一个窗口执行指令的,我是同一个用户Gyh开了三个窗口,一个窗口执行运行进程,一个窗口查询进程状态,一个窗口执行杀死父进程指令

     这里有一个疑问,我们在杀掉父进程之后,为什么不显示父进程为僵尸状态(Z)呢?原因很简单,因为父进程也有它的父进程,在它的父进程显示为Z状态,这里我们更加关注子进程。我们发现子进程有父进程,它的父进程pid为1,那么这个父进程是谁呢?

    🖊我们要明白,父进程先结束这种现象一定是存在的,而子进程也会被操作系统(1号进程)领养,如果不领养,那么子进程退出的时候,对应的僵尸进程,便无法回收了。

    我们这里还发现孤儿进程的状态由S+状态变为S状态,由前台切换为后台,所以要想杀死,得用kill -9 指令杀死

    🎵四、进程优先级

    🎈Ⅰ基本概念

    进程在都能被执行的情况下,我们先做哪个进程和它的优先级有一定关系,优先级高的进程先执行。这个道理很容易明白,因为我们的CPU资源总是有限的,而进程的数量可能很多,我们优先执行比较重要的进程。配置进程优先权对多任务环境的Linux很有用,可以改善系统性能。而Linux优先级的本质也就是PCB里面的整数数字。

    🎈Ⅱ查看进程优先级

    我们在Linux下使用ps -la 指令会输出以下内容:

     除了我们已经介绍过的pidppid,我们Linux进程优先级主要和PRI(Priority)和NI(Nice)有关.那么它们又代表什么意思呢?

    🖊PRI即进程的优先级,它的值越小,进程的优先级别越高。

    🖊NI 就是我们要说的nice值,它表示进程可被执行的优先级的修正数值。

    简单来说,Linux下有一套优先级计算公式:

    最终优先级= 固定优先级数80 + nice值

    也就是说Linux支持进程运行中,进行优先级调整的,调整的策略就是更改nice值。那么nice值是否可以任意修改呢?当然不是,它的范围是[-20,19]的整数,如果你输入大于19或者小于-20那么按照19和-20处理,也就是说我们最终优先级的范围有40个值,区间是[60,99]

    说了这么多,我们怎么更改进程优先级呢?

    top指令更改nice值。

    top --> 按"r"--> 输入进程PID-->输入nice值

    演示:

     

    🎵五、其他概念

    🖊竞争性:系统进程数目众多,而CPU资源只有少量,比较老的甚至只有一个,所以进程之间是有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。

    🖊独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

    🖊并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行。

    🖊并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

    这里其他都好理解,需要着重强调的是并发的概念。

    这里一个CPU时,一个进程并不是直到运行完才被拿下来,单CPU采用时间轮转片的策略,不管进程执行完花多长时间,每个进程比如说只给10ms的时间在CPU上运行,时间到了不管有没有运行结束都必须从CPU上剥离下来,然后放在运行队列多个进程不断切换,实现单CPU在一个时间段内多个进程同时进行,我们称为并发。

    这是具体并发是如何单核实现多进程的。当然这其中还有很多细节需要阐述。

    ①如果进程没有运行结束就被剥离下来,那么再次被放到CPU上是重新开始运行,还是继续执行之前的进度,如果执行之前的进度,那么产生的临时数据文件被寄存在哪里呢?

    ②单CPU在任一时间段进行多进程,那么任一时刻呢?

    🖊先来回答第一个问题:首先我们要明白单个CPU只有一套寄存器,而且它的空间很小,负责当前正在运行的进程。它当然是继续之前的进度,那么它的临时数据不是由寄存器保管,记住,寄存器只保存当前进程的数据,不会去保存之前进程的数据,之前进程的数据专门保存在内存的某一特定区域寄存器被所有的进程共享,但是寄存器内的数据,是每个进程私有的,仔细品品。

    🖊第二个问题很简单,任一时刻单个CPU只能进行一个进程

  • 相关阅读:
    《计算机病毒技术及其防御》 第一章 课后练习详解
    零基础Linux_10(进程)进程终止(main函数的返回值)+进程等待
    UniParser:异构日志数据的统一日志解析器
    MySQL的varchar存储原理:InnoDB记录存储结构
    kafka命令
    VsCode中C文件调用其他C文件函数失败
    数商云SRM系统询比价有何优势?供应商平台助力汽车零部件企业快速查找供应商
    汽车电控悬架
    国鑫受邀出席2023英特尔中国区数据中心渠道客户金秋会
    Linux环境下使用Docker搭建Jenkins容器
  • 原文地址:https://blog.csdn.net/JJR_YZ/article/details/127751584