1.进程创建1.1fork函数的详细理解进程创建在Linux中使用的就是fork函数fork函数会基于此进程创建一个子进程fork函数最特别的地方就是一次调用两个返回值#include unistd.h pid_t fork(void);创建成功之后给父进程返回子进程的pid,给子进程返回0创建失败给父进程返回-1进程调用 fork当控制转移到内核中的 fork 代码后内核做✅ ①分配新的内核数据结构和内存资源给子进程内核会为子进程创建一个新的 进程描述符 以及子进程所需的必要内核数据结构如 task_struct。同时为子进程分配一个唯一的 PID。✅ ② 复制父进程的部分数据结构内容到子进程子进程不是完全复制父进程的所有内容而是复制父进程的 许多内核数据结构如打开的文件描述符表、信号处理设置等。但对于内存管理现代 Linux 采用 写时拷贝 技术子进程和父进程共享相同的物理内存页内核将这些内存页标记为 只读只有当其中一个进程尝试写入时才会复制该页(缺页中断的写时拷贝)✅ ③将子进程添加到系统进程列表内核将子进程的进程描述符加入到系统的进程链表如任务列表中使其成为系统调度的一个可运行实体。✅ ④ fork 返回开始调度器调度fork 在内核中完成创建工作后会准备返回到用户空间但由于现在有两个进程父进程和子进程内核会调用调度器来决定接下来哪个进程获得 CPU最终①父进程从 fork 调用点返回子进程的 PID②子进程从相同的调用点返回 0③两个进程继续执行后续代码所以fork之前父进程独立执行fork之后父子两个执行流分别执行。注意fork之后谁先执行完全由调度器决定1.2 fork函数的使用场景1.3fork函数失败的原因系统中进程过多实际用户的进程数量超过限2.进程终止进程终止的核心目的就是归还所有占用的系统资源让操作系统能够重新分配这些资源给其他进程使用。2.1进程退出的场景// 正常终止方式 exit(0); // 标准C库函数 _exit(0); // 系统调用 return n; // main函数返回 // 异常终止方式 abort(); // 发送SIGABRT信号 被信号杀死 // 如段错误、用户kill进程的终止可以分为两大类正常终止和异常终止。无论哪种方式最终都会进入内核的终止流程释放进程占用的资源。对于进程异常退出操作系统会受到具体的信号然后把对应的进程杀掉进程异常退出的时候进程退出的信息中会记录下来自己的退出信号退出信号为0时表示没有异常退出的情况但是结果对不对还要看我们的退出码判断main函数的返回值是返回给系统或者是父进程的所以main函数的返回值就是用来确定当前程序的执行情况从main返回return n; → 退出码 n -让程序结束调用exit()exit(n); → 会执行清理atexit、缓冲区刷新 - 属于语言级别-会让进程直接结束调用_exit()_exit(n); → 直接进内核不清理/缓冲区不刷新 -属于系统级别补充:①系统头文件必须加.h② 语言级缓冲区会导致有些结果刷新不及时系统级缓冲区在进程退出的时候会自动刷新2.2退出码退出码退出状态可以告诉我们最后⼀次执行的命令的状态。在命令结束以后我们可以知道命令是成功完成的还是以错误结束的。其基本思想是程序返回退出代码 0 时表示执行成功没有问题。代码1 或 0 以外的任何代码都被视为不成功。退出码也可以自己规定XShell中常见的退出码可以使⽤ strerror 函数来获取退出码对应的描述。2.3 return退出return是⼀种更常见的退出进程方法。执行return n等同于执行exit(n),因为调⽤main的运行时函数会将main的返回值当做 exit的参数2.4 exit()函数#include unistd.h void exit(int status);status是进程的退出码status 就是进程留给父进程的“遗言”告诉父进程自己死得怎么样。3.进程等待其实就是父进程创建子进程就要对子进程负责至少要让子进程被回收如果子进程一直不退出父进程就要一直阻塞等待3.1进程等待的必要性①回收资源防止僵尸进程子进程退出后若父进程不管子进程会变成僵尸进程只保留task_struct在内核僵尸进程不释放内核资源长期积累会导致内存泄漏僵尸进程无法杀死包括kill -9因为它已经死了只等父进程来收尸②获取子进程的退出信息父进程需要知道子进程任务完成得怎么样退出码判断成功/失败子进程是正常退出还是被信号杀死③进程等待的作用父进程通过调用wait()或waitpid()✅ 回收子进程残留的内核资源释放task_struct✅ 获取子进程的终止状态退出码/终止信号✅ 彻底消除僵尸进程3.2如何进行进程等待3.2.1使用wait( )函数3.2.2 想要获取子进程的退出信息:使用waitpid( )status中不只有进程的退出码(这是包括进程正常退出的信息)还有异常突出status就是一个输出型参数能否使用全局变量来获取子进程的退出码呢不可以因此进程具有独立性子进程将这个变量更改之后父进程看不到这也就是 为什么我们只能通过系统调用来获取我们子进程的退出信息进程的退出信息是由我们的PCB存储的我们wait成功了并不代表我们的子进程也是成功运行了具体情况我们可以使用下面的两个宏3.3进程等待方式① 阻塞等待最常用② 非阻塞等待在等待过程中不断地对进程进行询问这种情况叫做非阻塞轮询这个循环调用是由程序员自己进行的4.进程程序替换4.1什么是程序替换程序替换exec系列函数的核心作用是让一个进程加载并运行一个全新的程序fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢进程的程序替换来完成这个功能程序替换是通过特定的接⼝加载磁盘上的⼀个全新的程序(代码和数据)加载到调⽤进程的地址空间中4.2进程程序替换的原理⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀种 exec 函数以执⾏另⼀个程序。当进程调⽤⼀种 exec 函数时,该进程的⽤⼾空间代码和数据完全被新程序替换,从新程序的启动例程开始执⾏。调⽤ exec 并不创建新进程,所以调⽤ exec 前后该进程的 id 并未改变。其实就是这个新的程序会将我们原来程序的数据和代码替换掉谁调我这个接口我就用哪个进程的代码和数据覆盖掉我的代码和数据所以进程替换不是创建新的进程所以其实linux中的子进程拷贝一些父进程的代码和数据本质上也是用来execl,因此linux上所有的进程都是通过execl和fork跑起来的呢我们不就可以使用程序替换让我们的代码可以用JAVA、PYTHON跑都可以了4.3替换函数①②execl失败返回-1也就说只要返回必定是失败的③④
进程控制详解
1.进程创建1.1fork函数的详细理解进程创建在Linux中使用的就是fork函数fork函数会基于此进程创建一个子进程fork函数最特别的地方就是一次调用两个返回值#include unistd.h pid_t fork(void);创建成功之后给父进程返回子进程的pid,给子进程返回0创建失败给父进程返回-1进程调用 fork当控制转移到内核中的 fork 代码后内核做✅ ①分配新的内核数据结构和内存资源给子进程内核会为子进程创建一个新的 进程描述符 以及子进程所需的必要内核数据结构如 task_struct。同时为子进程分配一个唯一的 PID。✅ ② 复制父进程的部分数据结构内容到子进程子进程不是完全复制父进程的所有内容而是复制父进程的 许多内核数据结构如打开的文件描述符表、信号处理设置等。但对于内存管理现代 Linux 采用 写时拷贝 技术子进程和父进程共享相同的物理内存页内核将这些内存页标记为 只读只有当其中一个进程尝试写入时才会复制该页(缺页中断的写时拷贝)✅ ③将子进程添加到系统进程列表内核将子进程的进程描述符加入到系统的进程链表如任务列表中使其成为系统调度的一个可运行实体。✅ ④ fork 返回开始调度器调度fork 在内核中完成创建工作后会准备返回到用户空间但由于现在有两个进程父进程和子进程内核会调用调度器来决定接下来哪个进程获得 CPU最终①父进程从 fork 调用点返回子进程的 PID②子进程从相同的调用点返回 0③两个进程继续执行后续代码所以fork之前父进程独立执行fork之后父子两个执行流分别执行。注意fork之后谁先执行完全由调度器决定1.2 fork函数的使用场景1.3fork函数失败的原因系统中进程过多实际用户的进程数量超过限2.进程终止进程终止的核心目的就是归还所有占用的系统资源让操作系统能够重新分配这些资源给其他进程使用。2.1进程退出的场景// 正常终止方式 exit(0); // 标准C库函数 _exit(0); // 系统调用 return n; // main函数返回 // 异常终止方式 abort(); // 发送SIGABRT信号 被信号杀死 // 如段错误、用户kill进程的终止可以分为两大类正常终止和异常终止。无论哪种方式最终都会进入内核的终止流程释放进程占用的资源。对于进程异常退出操作系统会受到具体的信号然后把对应的进程杀掉进程异常退出的时候进程退出的信息中会记录下来自己的退出信号退出信号为0时表示没有异常退出的情况但是结果对不对还要看我们的退出码判断main函数的返回值是返回给系统或者是父进程的所以main函数的返回值就是用来确定当前程序的执行情况从main返回return n; → 退出码 n -让程序结束调用exit()exit(n); → 会执行清理atexit、缓冲区刷新 - 属于语言级别-会让进程直接结束调用_exit()_exit(n); → 直接进内核不清理/缓冲区不刷新 -属于系统级别补充:①系统头文件必须加.h② 语言级缓冲区会导致有些结果刷新不及时系统级缓冲区在进程退出的时候会自动刷新2.2退出码退出码退出状态可以告诉我们最后⼀次执行的命令的状态。在命令结束以后我们可以知道命令是成功完成的还是以错误结束的。其基本思想是程序返回退出代码 0 时表示执行成功没有问题。代码1 或 0 以外的任何代码都被视为不成功。退出码也可以自己规定XShell中常见的退出码可以使⽤ strerror 函数来获取退出码对应的描述。2.3 return退出return是⼀种更常见的退出进程方法。执行return n等同于执行exit(n),因为调⽤main的运行时函数会将main的返回值当做 exit的参数2.4 exit()函数#include unistd.h void exit(int status);status是进程的退出码status 就是进程留给父进程的“遗言”告诉父进程自己死得怎么样。3.进程等待其实就是父进程创建子进程就要对子进程负责至少要让子进程被回收如果子进程一直不退出父进程就要一直阻塞等待3.1进程等待的必要性①回收资源防止僵尸进程子进程退出后若父进程不管子进程会变成僵尸进程只保留task_struct在内核僵尸进程不释放内核资源长期积累会导致内存泄漏僵尸进程无法杀死包括kill -9因为它已经死了只等父进程来收尸②获取子进程的退出信息父进程需要知道子进程任务完成得怎么样退出码判断成功/失败子进程是正常退出还是被信号杀死③进程等待的作用父进程通过调用wait()或waitpid()✅ 回收子进程残留的内核资源释放task_struct✅ 获取子进程的终止状态退出码/终止信号✅ 彻底消除僵尸进程3.2如何进行进程等待3.2.1使用wait( )函数3.2.2 想要获取子进程的退出信息:使用waitpid( )status中不只有进程的退出码(这是包括进程正常退出的信息)还有异常突出status就是一个输出型参数能否使用全局变量来获取子进程的退出码呢不可以因此进程具有独立性子进程将这个变量更改之后父进程看不到这也就是 为什么我们只能通过系统调用来获取我们子进程的退出信息进程的退出信息是由我们的PCB存储的我们wait成功了并不代表我们的子进程也是成功运行了具体情况我们可以使用下面的两个宏3.3进程等待方式① 阻塞等待最常用② 非阻塞等待在等待过程中不断地对进程进行询问这种情况叫做非阻塞轮询这个循环调用是由程序员自己进行的4.进程程序替换4.1什么是程序替换程序替换exec系列函数的核心作用是让一个进程加载并运行一个全新的程序fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢进程的程序替换来完成这个功能程序替换是通过特定的接⼝加载磁盘上的⼀个全新的程序(代码和数据)加载到调⽤进程的地址空间中4.2进程程序替换的原理⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀种 exec 函数以执⾏另⼀个程序。当进程调⽤⼀种 exec 函数时,该进程的⽤⼾空间代码和数据完全被新程序替换,从新程序的启动例程开始执⾏。调⽤ exec 并不创建新进程,所以调⽤ exec 前后该进程的 id 并未改变。其实就是这个新的程序会将我们原来程序的数据和代码替换掉谁调我这个接口我就用哪个进程的代码和数据覆盖掉我的代码和数据所以进程替换不是创建新的进程所以其实linux中的子进程拷贝一些父进程的代码和数据本质上也是用来execl,因此linux上所有的进程都是通过execl和fork跑起来的呢我们不就可以使用程序替换让我们的代码可以用JAVA、PYTHON跑都可以了4.3替换函数①②execl失败返回-1也就说只要返回必定是失败的③④