【Linux】进程概念(一):基本概念与基本操作
一、描述进程—PCB我们已经了解了进程的概念那什么是PCB进程控制块呢系统当中可以同时存在大量进程使用命令ps ajx便可以显示系统当中存在的进程。在这里插入图片描述当我们开机的时候启动的第一个程序就是操作系统意思就是操作系统是第一个加载到内存的我们都知道操作系统是做管理工作的而其中就一个管理就是进程管理。而系统内是存在大量进程的那么操作系统是如何对进程进行管理的呢这时我们就应该想到管理的精髓先描述再组织。操作系统管理进程也是一样的操作系统作为管理者是不需要直接和被管理者进程直接进行沟通的我们说过会有一个执行者当一个进程出现时操作系统就立马对其进行描述之后对该进程的管理实际上就是对其描述信息的管理。进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合课本上称之为PCBprocess control block。PCBProcess Control Block是操作系统用来描述一个进程信息的数据结构。它保存了进程的各种状态、控制信息以及调度所需的所有数据。简单来说进程是由内核中PCB 数据结构和我们编写的代码构成的。进程控制块用来描述进程的所有相关属性信息。它包含了一个进程在执行过程中所需要的全部数据如进程的状态、CPU寄存器、内存管理信息等。程序本质上是一个二进制文件包含了我们编写的代码和数据。这个二进制文件会被操作系统加载到内存中并在执行时形成一个进程。程序和进程是两个不同的概念程序是静态的存储在磁盘上而进程是动态的是程序在内存中的执行实例。二、task_structtask_struct 是用来描述一个 进程 的重要数据结构。每个进程都有一个对应的 task_struct它保存了该进程的各种信息和状态。上面我们提到操作系统对进程的管理其实是对进程的描述信息的管理它的信息包含了许多对其管理的增删查改其实最适用的task_struct就是用双向链表组织起来。这样一来操作系统只要拿到这个双链表的头指针便可以访问到所有的PCB。此后操作系统对各个进程的管理就变成了对这条双链表的一系列操作。1. 内容分类进程信息类别描述标识符描述本进程的唯一标识符用来区别其他进程。状态任务状态、退出代码、退出信号等。优先级相对于其他进程的优先级。程序计数器程序中即将被执行的下一条指令的地址。内存指针包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针。上下文数据进程执行时处理器的寄存器中的数据如CPU、寄存器等。I/O状态信息包括显式的I/O请求分配给进程的I/O设备和被进程使用的文件列表。记账信息可能包括处理器时间总和使用的时钟数总和时间限制记账号等。其他信息具体详细信息会在后续介绍。2. 组织进程在这里插入图片描述三、查看进程1. 通过系统目录查看ls /proc在这里插入图片描述/proc系统目录中储存着所有正在运行进程的目录文件以及相关的进程信息并且进程的目录文件名是以进程对应的进程编号PID为名称。ls /proc/编号查看进程信息在这里插入图片描述exe是进程的可执行文件的路径链接指向二进制文件的绝对位置。进程是由二进制文件加载到内存中运行而生成的实例而exe则对应该二进制文件在磁盘上的位置。你可以通过exe了解进程对应的可执行文件的路径。cwd是进程的当前工作目录进程的所有操作如文件创建或读取默认都在该目录下进行。进程在运行过程中生成的临时文件通常会以cwd作为基础路径从而在该目录下创建文件。2. 通过ps命令来查看在这里插入图片描述ps命令与grep命令搭配使用即可只显示某一进程的信息。ps ajx | head -1 ps ajx | grep 文件名在这里插入图片描述四、通过系统调用获取进程的PID和PPID什么是PID什么是PPID?PID 是进程标识符Process Identifier的缩写是操作系统分配给每个正在运行的进程的唯一编号。每个进程都有一个唯一的 PID它帮助操作系统和用户区分不同的进程。例如当你查看正在运行的进程时PID 是识别进程的关键标识符。你可以通过ps命令查看当前进程的 PID。当你运行一个程序如./test操作系统会为这个进程分配一个 PID如1234。使用ps aux命令时你会看到类似于1234这样的 PID 列在进程信息中。PPID 是父进程标识符Parent Process Identifier的缩写它是指创建当前进程的父进程的 PID。每个进程都有一个父进程除了系统启动时的初始化进程通常是init或systemd。PPID 表示的是哪个进程启动了当前的进程。通过查看 PPID我们可以了解进程之间的父子关系。如果一个进程1234是由进程5678启动的那么进程1234的 PPID 就是5678。你可以使用ps -eo pid,ppid,cmd来查看每个进程的 PID 和 PPID。在这里插入图片描述在这里插入图片描述我们可以通过ps命令查看该进程的信息即可发现通过ps命令得到的进程的PID和PPID与使用系统调用函数getpid和getppid所获取的值相同。在这里插入图片描述五、通过系统调用创建进程——fork初识1. 概念在这里插入图片描述这里我们可以看到他是用来创建子进程的当前进程的子进程包含在头文件unistd.h中返回类型pid_t它定义的遍历是进程号类型fork创建成功会给子进程返回0然后给父进程返回PID创建失败则会返回一个1的数给父进程。2. fork有两个返回值代码语言javascriptAI代码解释#includestdio.h #includeunistd.h int main() { printf(begin\n); fork(); printf(end\n); return 0; }在这里插入图片描述printf(begin\n);这行会首先输出 begin。无论是父进程还是子进程它们都会执行到这一行因此你会看到 begin 输出一次。fork();fork() 调用会创建一个新的子进程父进程和子进程都会从 fork() 调用之后的地方开始执行。 在 父进程 中fork() 会返回子进程的 PID。 在 子进程 中fork() 会返回 0。 这意味着fork() 被父进程和子进程都执行一次导致 fork() 之后的代码会在两个进程中各自执行一次。printf(end\n);这行代码会在父进程和子进程中各自执行一次。因此end 会输出两次一次来自父进程另一次来自子进程。代码语言javascriptAI代码解释1 #includestdio.h 2 #includeunistd.h 3 #includesys/types.h 4 5 int main() 6 { 7 printf(我是一个进程PID%d,PPID%d\n,getpid(),getppid()); 8 sleep(1); 9 pid_t id fork(); 10 if(id 0)//执行父进程 11 { 12 while(1) 13 { 14 printf(父进程PID%d,PPID%d\n,getpid(),getppid()); 15 sleep(1); 16 } 17 } 18 else if(id 0)//执行子进程 19 { 20 while(1) 21 { 22 printf(子进程PID%d,PPID%d\n,getpid(),getppid()); 23 sleep(1); 24 } 25 } 26 else{ 27 printf(创建失败\n); 28 } 29 30 return 0; 31 }在这里插入图片描述这段话的核心思想是关于fork()系统调用为什么要给父进程返回子进程的 PID进程 ID而给子进程返回0以及如何通过这种设计区分父进程和子进程的执行流。让我们通过简单清晰的思维来总结3. 为什么 fork() 给父进程返回子进程的 PID给子进程返回 0fork()会在父进程和子进程中都执行一次父进程需要知道自己创建了哪个子进程以便后续管理和控制。因此父进程需要获取子进程的 PID即fork()返回子进程的 PID。如果父进程不对子进程进行区分那么就没有办法找到特点的子进程了。 子进程则不需要关心父进程的 PID因为子进程只对应一个父进程在描述信息的时候它的task_struct就已经对应初始化放置了父进程的PPID并且它不需要对谁进行管理所以给子进程返回0。在操作系统中父进程创建子进程是为了让子进程去执行不同代码特定的任务。因为如果像第一段代码父进程和子进程执行相同的代码块就没啥意义了。如果父进程创建了多个子进程父进程需要知道这些子进程以便进行管理、等待或者控制。每个进程都有唯一的 PID父进程通过fork()返回的 PID 就能知道哪个子进程对应哪个任务方便后续对特定子进程进行管理。创建子进程的一个关键目的是让子进程去执行与父进程不同的任务。父进程和子进程虽然代码是共享的但它们可以根据fork()返回值的不同来决定执行不同的代码块。 通过if (pid 0)判断子进程if (pid 0)判断父进程父子进程可以分别执行不同的代码这就是fork()设计的核心之一。4. fork的操作我们使用fork函数创建子进程后系统就多了一个子进程它最初没有自己的PCB所以它就复制父进程的PCB进行适量修改PPID、PID以及其它属性时候就变成了子进程自己的PCB即子进程的进程控制块task_struct。我们知道程序文件是有对应的代码和数据的程序文件运行之后对应的进程就是当前的父进程所以父进程是有自己的代码和数据的子进程什么都没有连进程控制块PCB中的大部分内容都是复制的父进程的一个进程是由内核数据结构PCB加代码和数据构成此时子进程已经有PCB了那么它既然想要成为一个进程却又没有代码和数据那么它只能从父进程的代码和数据想办法其中对于代码代码是不能修改的那么父进程自然可以允许子进程和自己共用代码即父进程和子进程指向同一块代码我们知道程序有自己的代码和数据。但是一个进程是由内核数据结构PCB和代码数据构成我们的子进程初始是和父进程共享同一块数据内存空间的所以它为了变成一个进程就只能从父进程那里共用代码但是数据如变量和内存中的动态数据是不能共享的因为如果他们一方修改共享数据会相互影响从而不符合进程独立性的要求。在Linux中为了解决这个问题采用了写时拷贝Copy-On-Write。他就是让父进程和子进程在最初的时候指向同一块数据空间如果他们都不对这块空间进行修改的化他们就会共享这块内存空间同时也节省了资源但是如果有一方需要对数据进行修改操作系统将会为需要修改数据的进程创建一份独立的副本这个副本就是“写时拷贝”。然后原来的空间就属于未进行修改数据的进程了。修改数据的进程就在自己的副本上修改通过这种方式父进程和子进程都能各自独立的操作数据互不干扰确保进程的独立性。如何让父子进程执行不同的代码 当 fork() 被调用时它会 返回不同的值给父进程和子进程父进程接收到子进程的 PID子进程的进程ID返回值大于 0。子进程接收到 0表示它是一个新创建的进程。这两者的不同返回值就是用来区分父进程和子进程的执行流。 根据返回值分支执行代码父进程 根据返回的子进程的 PID大于0来执行特定的代码块。子进程 根据返回的 0 来执行另一个代码块。通过这种方式父子进程会 共享代码但 根据 fork() 返回值的不同父子进程会执行不同的代码块确保它们各自完成不同的任务。