【Linux】进程概念

时间:2023-02-09 12:53:15


1 基本概念

  • 书本概念:程序的一个执行实例,正在执行的程序等。
  • 内核观点:担当分配系统资源(CPU时间,内存)的实体。

在我们自己的电脑上,打开任务管理器,我们就可以看到一个个的进程信息。

【Linux】进程概念

可以这么理解:我们以前任何启动并运行程序的行为(如Linux下通过 ./ 方式运行程序、平常双击打开一个应用等),最终都是要由操作系统帮助我们将程序转换成为进程,才能完成特定的任务。

总结:进程 = 内核关于进程的相关数据结构 + 当前进程的代码和数据

【Linux】进程概念


2 描述进程 - PCB

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
  • 书上称之为 PCB(process control block),Linux操作系统下的 PCB 是:task_struct

3 task_struct - PCB的一种

  • 在 Linux 中描述进程的结构体叫做 task_struct
  • task_struct 是 Linux 内核的一种数据结构,它会被装载到 RAM(内存)里并且包含着进程的信息。

4 task_struct内容分类

  • 标识符:描述本进程的唯一标识符,用来区别其它进程。
  • 状态:任务状态,退出代码,退出信号等。
  • 优先级:相对于其它进程的优先级。
  • 程序计数器:程序中即将被执行的下一条指令的地址。
  • 内存指针:包括程序代码和进程相关数据的指针,还有和其它进程共享的内存块的指针。
  • 上下文数据:进程执行时处理器的寄存器中的数据。
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其它信息。

5 组织进程

可以在内核源代码中找到。所有运行在系统里的进程都以 task_struct 链表的形式存在内核里。


6 查看进程

  • 如下编写了一段死循环代码(myprocess.c),使得能持续运行对应程序:

【Linux】进程概念

  • 通过 ./myprocess 指令运行编译生成的可执行程序,该条指令也意味着将对应可执行程序的代码和数据加载到内存中,并由操作系统为其创建包含相关属性的数据结构PCB。

【Linux】进程概念

  • 通过 ps axjps aux 指令可查看当前进程状态如下
    【Linux】进程概念
    可以发现,除了我们刚刚启动的进程外,还有很多其它进程在运行,这样不是很方便查看我们的目标进程,因此,这里我们可以通过 ps axj | head -1 && ps axj | grep myprocess 指令过滤出进程属性名的同时也将 myprocess 对应的进程状态也过滤出来。
    【Linux】进程概念
  • 可以看到显示的进程状态中其中有一项属性名为 PID ,这即是进程的唯一标识符(当前运行myprocess程序对应创建的进程PID即为6614)。不终止上一个进程,在另一个会话窗口下我们再次通过 ./myprocess 指令运行程序,重新查看当前进程状态,发现新增了一个myprocess对应相关的进程,虽然执行的程序运行指令是相同的,但从PID也可以看出这是两个不同的进程。
    【Linux】进程概念
  • 除了 ps axj 指令外,我们还可以通过 /proc 系统文件夹查看当前进程信息。proc 是 process 的简称,该目录下保存了进程相关属性。与一般的文件不同,/proc 目录是一个内存级的文件系统,只有当操作系统启动才会存在,在磁盘上并不存在对应的/proc目录。进程一旦被创建好,操作系统自动会在 /proc 目录下为我们创建一个文件夹(以新增进程的PID命名,里面保存对应进程的相关信息)。
    【Linux】进程概念
  • 通过按 Ctrl + c 键分别将之前两个程序运行终止,即终止 PID 为 6614 和 7741 的两个进程,此时再执行 ls /proc/6614 指令查看对应进程信息,发现进程对应文件夹已被删除了。其实当我们把对应的进程终止时,操作系统会自动的把 /proc 目录下曾经为该进程创建好的以进程PID命名的文件夹及里面的内容全部删除。
    【Linux】进程概念

7 通过系统调用获取进程标识符(PID)

  • 首先我们通过 man getpid 来认识两个函数 getpid()getppid()。通过调用该函数我们可以获取到对应进程标识符(PID)。
    【Linux】进程概念

  • 接着我们修改代码如下:
    【Linux】进程概念

  • 重新编译运行程序。如下可以发现每次我们运行程序再终止,接着再次运行程序,其对应进程的PID是不一样的,但其父进程的PID却始终相同,可以理解,每次运行程序就创建一个进程,只要这个进程没有被终止,那其PID也就一直存在不变,而终止后再次运行程序创建的是新的进程了,所以每次的PID是不同的,那为什么每次新创建的进程的父进程的PID都是一样的呢?是不是说明这个父进程一直在运行,没有被终止呢?
    【Linux】进程概念

  • 那我们就通过这个相同的父进程PID来看看它究竟是谁。输入 ps axj | head -1 && ps axj | grep 4120 命令查看指定PID的进程信息,可以发现该PID对应的进程源于命令行解释器。由此可以知道,bash命令行解释器本质上也是一个进程。事实上,命令行启动的所有程序,最终都会变成进程,而该进程对应的父进程都是 bash
    【Linux】进程概念

  • 此外,可以使用 Ctrl + c 键来终止进程外,我们还可以通过指令 kill -9 PID 来终止对应PID的进程。那可不可以将父进程也终止呢?当然,通过该条指令我们也可以将 bash 进程终止,当这样会造成命令行崩溃,因为当父进程被终止后,所有依赖于该父进程的子进程都将无法正常运行。
    【Linux】进程概念


8 通过系统调用创建子进程 - fork初识

  • 同样,我们先通过 man fork 指令来认识 fork 。通过调用fork()我们可以创建一个子进程。
    【Linux】进程概念
  • 接着修改代码,重新编译运行程序,如下所示:结果输出了一行 "AAAAAAAAAA" 和两行 "BBBBBBBBBB" ,可是按照代码看来,我们不是各输出一行的吗?
    【Linux】进程概念【Linux】进程概念
  • 再次修改代码,输出当前进程的PID。如下我们发现,原来其中一行 "BBBBBBBBBB" 的输出是属于创建出的子进程的,且该子进程的父进程正是另一个输出的所属进程。
    【Linux】进程概念
  • 继续进行修改,增加对fork函数返回值及其地址的输出,一个很有意思的事情出现了:同一个函数,父进程中返回了子进程的PID,而子进程中返回了0。当然在前面我们查看的fork函数的说明中也表示了:如果父进程创建成功,则对应创建的子进程的PID会返回给父进程,0会返回给子进程;如果父进程创建失败,则-1返回给父进程,对应子进程不会被创建。 但这是如何做到的呢?为什么同一个函数会有两个返回值?为什么同一块地址空间中存储的内容还会不同?
    【Linux】进程概念
  • 暂且先不看上面的问题,我们再来看看如下代码。事实上,一般我们在用fork函数创建子进程时,通常会使用条件判断语句来区分父子进程,并使其完成不同的任务。但有一点奇怪的是,怎么一段代码里 ifelse 可以同时满足?
    【Linux】进程概念
  • 从上面的示例中我们可以做出以下总结:
    • 调用fork函数之后,代码的执行流会变成两个;
    • 调用fork函数之后,哪个进程先运行由调度器决定;
    • 调用fork函数之后的代码共享,通常我们通过 ifelse 进行执行流分流,来让父子进程执行不同的代码块。

  • 接下来我们来解决一些疑问:
    • fork做了什么?
      答:通过前面的讲解,我们知道 进程 = 内核数据结构 + 进程的代码和数据 ,当我们运行父进程时,也就代表其已经有了自己的内核数据结构以及代码和数据,而 fork 创建子进程并不是说直接将父进程对应的代码和数据拷贝一份,而是在内核中再创建一个子进程所对应的PCB,子进程PCB中的大部分属性会以父进程PCB中的为模板拷贝而来,其余小部分属性则是子进程PCB私有的(如子进程PID等),当子进程PCB创建好之后再使其指向父进程所对应的代码和数据,也就是说父子进程看向的是同一份代码和数据。
      【Linux】进程概念
    • fork是如何看待代码和数据的?
      答:进程在运行的时候是具有独立性的(就好比我们日常使用电脑时登录的一个个软件,一个应用的退出并不影响我们另一个应用的使用),父子进程也是如此。从代码层面来看,我们的编写的代码一般是只读的,不会自己发生变化,这也就意味着其不会被别人写入和修改,而父子进程共用一块代码,区别只是在于去读哪一部分,所以可以理解父子进程在代码层面上是独立的;从数据层面来看,如下图所示,我们对其中一个进程数据的修改并不会影响另一个进程的数据,即保证了数据独立性。那这是如何做到的呢?注意:当有一个执行流尝试修改数据的时候,操作系统会自动给我们当前进程触发一种机制:写时拷贝 ,将数据拷贝一份到别的空间,再对拷贝的数据进行修改,以保证数据不会被影响。
      【Linux】进程概念
      【Linux】进程概念
    • 如何理解fork函数有两个返回值的问题?
      答:首先注意一点:当函数内部准备执行 return 进行返回时,表示函数的主体功能已经完成。 也就是说,调用fork函数,当fork函数的函数主体功能执行完成时,表示子进程已经被创建,那此时,之后的代码由父子进程共享, return 作为一条语句也不例外,即意味着在子进程创建完成后,执行流一分为二,不同的执行流执行不同的 return 语句,也就返回了两个不同值。那还有一个问题,一个变量是如何保存两个不同的返回值的呢?事实上,当接收返回值的变量被写入时,操作系统也自动触发了 写时拷贝 ,虽然看起来两个返回值的存储空间地址相同,实际上它们被写到了不同的空间中,而我们所看到的地址实际上只是一个 虚拟地址
      【Linux】进程概念

以上是我对Linux中进程概念相关的一些学习记录总结,如有错误,希望大家帮忙指正,也欢迎大家给予建议和讨论,谢谢!