中国美食网站模板免费下载,广州网络运营课程培训班,无锡手机网站制作,海南最新消息今天#x1f3ac; 个人主页#xff1a;谁在夜里看海.
#x1f4d6; 个人专栏#xff1a;《C系列》《Linux系列》《算法系列》
⛰️ 一念既出#xff0c;万山无阻 目录 #x1f4d6;一、进程创建
1.fork函数
#x1f4da;高层封装特性
#x1f4da;fork返回值
2.写时拷… 个人主页谁在夜里看海. 个人专栏《C系列》《Linux系列》《算法系列》
⛰️ 一念既出万山无阻 目录 一、进程创建
1.fork函数
高层封装特性
fork返回值
2.写时拷贝
3.调用失败
资源耗尽
进程数限制
内核限制
二、进程终止
1.退出场景
2.status退出码
3.退出方法
exit函数
_exit函数
main函数返回
三、进程等待
1.wait方法
语法
总结
2.waitpid方法
语法
总结 一、进程创建
1.fork函数
操作系统中进程的创建通常是通过系统调用实现的在Linux中是通过fork()它从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程。
#includeunistd.h // 使用需包含头文件unistd.h
pid_t pid fork(); // 在子进程中返回0父进程中返回子进程pid出错返回-1
fork是操作系统提供的一种高层封装它抽象了进程创建的复杂过程fork将底层的一系列操作封装在一个简单的系统调用中屏蔽了许多复杂的细节
高层封装特性
① 简化进程创建的步骤
fork的调用接口非常简洁只需要调用一次系统会自动创建一个子进程并返回。父进程和子进程共享相同的代码子进程可以继续从父进程的当前执行点运行。
② 屏蔽底层细节
底层需要分配新的内存空间、复制父进程的状态、初始化子进程的资源fork函数将这些细节全部封装起来。
③ 依赖操作系统
fork的具体实现以来于操作系统内核它负责管理进程表等关键数据结构系统调用fork当控制转移到内核中的fork代码后内核做 1. 分配新的内存块和内核数据结构给子进程 2. 将父进程部分数据结构内容拷贝给子进程 3. 添加子进程到系统进程列表当中 4. fork返回开始调度器调danddandan 当一个进程调用fork之后就会有两个二进制代码相同的进程并且都能运行到相同的地方各自开始往下走
int main() {printf(Before: pid is %d, ppid is %d\n,getpid(),getppid());fork();printf(After: pid is %d, ppid is %d\n,getpid(),getppid());return 0;
}❓这里为什么只有三行输出子进程共享父进程的代码并各自独立执行应该是打印两次Before才对。分析打印结果10226应该是父进程确实打印了Before而子进程10227没有打印Before说明子进程并没有执行Before的代码正如上面所说的子进程继续从父进程的当前执行点运行也就是从fork代码处往下执行而fork之前的不会被执行 所以在一个进程调用fork之前该进程单独执行调用fork之后父子两个进程执行流各自执行
fork返回值
在子进程中fork返回0
在父进程中frok返回子进程pid。
❓这里提出一个问题在父进程中fork返回值没有异议因为fork函数是父进程调用的自然会有返回值但是在子进程中fork也有返回值那么是不是子进程也调用了fork函数呢 这不肯定因为一个进程调用fork函数之后会创建出它的子进程而子进程再调用fork函数再创建。。。这显然不对所以子进程并没有调用fork函数但是为什么会有fork函数的返回值呢
✅上面说到子进程是从父进程的执行点开始往下执行的所以对上述问题合理的解释是父进程创建子进程时的执行点在fork函数调用之后返回之前所以子进程往下执行也会有返回值产生
2.写时拷贝
父子进程的代码是共享的所以它们往后执行相同的操作那么它们的数据也是共享的吗的确在没有进行写入时父子进程的数据也是共享的只有当一方尝试对共享数据进行写入时系统才会拷贝一份数据用于写入这样既确保了资源的高效利用又保证了父子进程间的独立性 。 3.调用失败
fork()调用失败通常于系统资源、权限、或操作系统限制有关下面是常见的原因
资源耗尽
当系统的资源不足时如内存或进程表项不足fork()会失败 内存不足操作系统需要为每个新进程分配内存如果系统内存耗尽fork() 就会失败。 进程表已满每个进程都有一个进程控制块PCB操作系统维护一个进程表。如果系统中运行的进程数量已经达到限制无法再为新进程分配进程控制块时fork() 会失败。 堆栈空间不足如果子进程的堆栈空间无法分配尤其在某些嵌入式或资源受限的环境中fork() 也会失败。 进程数限制
大多数操作系统对一个用户或系统总共能创建的进程数有限制。若当前用户或系统已经达到了此限制调用 fork() 时就会失败。
可以通过 ulimit -u 查看单个用户的最大进程数 内核限制
内核的资源如文件描述符和信号等也可能导致 fork() 失败。例如如果父进程持有太多打开的文件句柄可能会达到系统文件描述符的限制。 二、进程终止
1.退出场景
进城退出场景无非下面三种
①代码运行完毕结果正确
②代码运行完毕结果不正确
③代码异常终止没有运行完
第一种情况自然是最好的但是如果是另外两种情况我们就需要进行额外处理但是我们怎么才能知道进程退出是哪种情况呢什么时候需要处理什么时候不需要呢
这个时候就需要进程退出时做一些标记返回退出码告知操作系统或程序员具体的退出情况
2.status退出码
status状态码用于表示进程的退出状态提供了进程执行结果的信息状态码遵循以下约定 0表示命令成功执行没有错误发生。非0表示命令执行失败。具体的非0值表示不同类型的错误具体含义通常与执行的程序或命令相关。例如 1一般性错误。2命令语法错误。126命令不能执行权限问题。127命令未找到。128命令因信号导致终止例如程序被 kill 命令中断。130程序因接收到 CtrlCSIGINT信号而退出。 status通常被定义成整形但是并不能当作一般的整形看待而是要看作成位图 我们有一个 32 位的 status其中高8位用于表示退出状态低8位用于表示因信号退出的原因。 高8位退出状态可以有 256 种可能的退出码 0正常退出。 1 到 127表示不同的错误。 128 到 255表示因信号终止计算方式为 128 信号编号。 低8位信号终止标志 如果进程是由于信号终止的那么低8位会记录相应的信号编号例如SIGKILL 对应 9SIGSEGV 对应 11。 如果进程不是由信号终止的低8位通常为 0。
3.退出方法
进程退出的常见方法有exit()_exit()以及main()函数返回下面依次进行介绍
exit函数
exit(int status)这是进程正常终止的一种方式。调用exit()后进程会清理其资源文件描述符、内存等并将状态码status返回给操作系统。当返回0时表示成功退出返回非0表示出现错误。
在多线程程序中exit() 会终止当前进程以及所有线程。
#include stdio.h
#include stdlib.hint main() {printf(This process will exit normally.\n);exit(0); // 正常退出状态码为0
}_exit函数
_exit(int status)这个函数与 exit() 很相似但它不会执行标准库的清理操作如缓冲区刷新等直接终止进程。来看下面这段代码
int main()
{printf(this is a process, pid is %d, ppid is %d,getpid(),getppid());exit(0);
}调用exit时正常打印
int main()
{printf(this is a process, pid is %d, ppid is %d,getpid(),getppid());_exit(0);
}❓调用_exit时没有正常打印这是为什么呢
✅printf输出时如果没有加上\n此时输出的内容会存在标准输出缓冲区中并不会立刻显示在终端而调用_exit函数时由于它不会执行标准库的清理操作所以缓冲区的内容就不会显示在终端
exit函数最后其实会调用_exit函数只不过在调用之前多做了如清理缓冲区的操作 main函数返回
return在 main 函数中使用时程序会结束并返回指定的退出状态码通常为 0 表示成功非 0 表示错误。return 结束当前函数的执行但如果在 main 函数中调用它会导致程序退出。
return返回和exit调用的效果是一样的其实他们本质上是等价的return 0 等价于 exit(0)
只不过在main函数中用return返回作为程序终止的标志更符合函数的语义可读性更强。 三、进程等待
之前的博客讲过子进程退出如果父进程不做任何处理就会引发内存泄露进程表等信息不会被清理产生僵尸进程。 博客链接在此详解僵尸进程于孤儿进程
那么避免僵尸进程的办法就是进程等待父进程通过进程等待的方式回收子进程资源获取子进程退出信息。
1.wait方法
wait()是一个比较简化的系统调用用于让父进程等待任意一个子进程的终止。wait()函数会阻塞父进程直到有子进程终止并且返回一个子进程的PID。
语法
#include sys/wait.h
pid_t wait(int *status);status用于返回子进程的退出状态。
返回值如果调用成功返回子进程的PID如果没有则返回-1。
总结 1. 父进程调用wait()时会阻塞直到有子进程结束并回收它的状态 2. 如果有多个子进程退出wait()返回任意一个子进程的PID 3. 如果没有子进程wait()会返回-1。 2.waitpid方法
waitpid() 是 wait() 的更为灵活和可控制的版本允许父进程等待特定的子进程结束或者通过指定参数进行更精细的控制。
语法
#include sys/wait.h
pid_t waitpid(pid_t pid, int *status, int options);pid指定需要等待的子进程的 PID。可以取以下几种值 pid 0等待指定 PID 的子进程。pid -1等待同组进程中的任意子进程。status与 wait() 相同保存子进程的退出状态。options控制行为的标志常用的选项有 WNOHANG非阻塞模式如果没有子进程退出立即返回而不是阻塞。WUNTRACED如果子进程已经停止但没有退出也返回。返回值 返回子进程的 PID如果没有子进程或者发生错误返回 -1。如果 status 中的退出状态有特殊状态如退出信号需要使用宏来解析。
int main()
{pid_t pid fork();if(pid 0){// 子进程printf(this is child process,pid is %d,ppid is %d\n,getpid(),getppid());exit(20);}// 父进程printf(this is father process,pid is %d\n,getpid());int status;pid_t child_pid waitpid(-1,status,WNOHANG);printf(child process has exited,code is %d,pid is %d\n,WEXITSTATUS(status),child_pid);exit(0);
}父进程调用 wait() 或 waitpid() 时它会传递一个指向 status 变量的指针用于写入子进程的退出状态所以我们需要在外部定义一个status变量并通过取地址的方式传入函数内部。
❓定义成其他变量名可以吗完全可以
✅变量名只是内存的一个标识符是用户自定义的wait() 或 waitpid() 只关心的是传递给它的地址而不是变量的名字只不过定义成status这样代码更加易读。
❓定义成其他类型可以吗不可以
✅status 参数必须是一个指向 int 类型的指针。如果传递其他类型例如 float* 或 char*程序可能会产生编译错误这是因为 wait() 和 waitpid() 会在 status 指向的内存中写入整数值用来存储子进程的退出状态。如果指针指向的类型不匹配内存解释将出错。
上述代码中由于waitpid内部设置为WNOHANG模式没有子进程返回时直接退出不阻塞 需要sleep(1)等待子进程退出后waitpid才能接收到退出信息 // 父进程printf(this is father process,pid is %d\n,getpid());int status;sleep(1);pid_t child_pid waitpid(-1,status,WNOHANG);printf(child process has exited,code is %d,pid is %d\n,WEXITSTATUS(status),child_pid);exit(0); 其中WEXITSTATUS是一个宏函数用于解码退出状态因为上面讲过32位status的高8位存储退出状态所以不能直接引用status查看而要用一个宏函数进行解码。
总结
特性wait()waitpid()等待目标等待任意子进程的结束可以指定特定的子进程通过 pid 参数阻塞与非阻塞总是阻塞直到至少有一个子进程结束可以通过 WNOHANG 使其非阻塞灵活性较少灵活性只能等待任何一个子进程更灵活可以等待指定的子进程或进程组选项没有额外选项支持更多控制选项如 WNOHANG返回值返回一个子进程的 PID返回指定子进程的 PID或者 -1 错误错误处理如果没有子进程返回 -1如果没有子进程返回 -1 以上就是【进程的生命之旅——诞生、消逝与守候】的全部内容欢迎指正~ 码文不易还请多多关注支持这是我持续创作的最大动力