关于linux父子进程几个问题的总结

一、vfork

为什么会有vfork,因为以前的fork 很傻, 它创建一个子进程时,将会创建一个新的地址
空间,并且拷贝父进程的资源,而往往在子进程中会执行exec 调用,这样,前面的拷贝工
作就是白费力气了,这种情况下,聪明的人就想出了vfork,它产生的子进程刚开始暂时与
父进程共享地址空间(其实就是线程的概念。

fork基本
cnt=1
I am the child process,ID is 4711
cnt=1
I am the parent process,ID is 4710

改为vfork
cnt=1
I am the child process,ID is 4711
cnt=1
I am the parent process,ID is 4710
段错误
为什么与预期的值不一致,且出现段错误呢

什么是vfork?

“写时复制”其实还是有复制,进程的mm结构、页表都还是被复制了(“写时复制”也必须由这些信息来支撑。否则内核捕捉到CPU访存异常,怎么区分这是“写时复制”引起的,还是真正的越权访问呢?)。
而vfork就把事情做绝了,所有有关于内存的东西都不复制了,父子进程的内存是完全共享的。但是这样一来又有问题了,虽然用户程序可以设计很多方法来避免 父子进程间的访存冲突。但是关键的一点,父子进程共用着栈,这可不由用户程序控制的。一个进程进行了关于函数调用或返回的操作,则另一个进程的调用栈(实 际上就是同一个栈)也被影响了。这样的程序没法运行下去。
所以,vfork有个限制,子进程生成后,父进程在vfork中被内核挂起,直 到子进程有了自己的内存空间(exec**)或退出(_exit)。并且,在此之前,子进程不能从调用vfork的函数中返回(同时,不能修改栈上变量、 不能继续调用除_exit或exec系列之外的函数,否则父进程的数据可能被改写)


形象地说:在儿子 霸占”着老子房子时候,要委屈老子一
下了,让他在外面歇着(阻塞),一旦儿子执行了exec 或者exit 后,相 于儿子买了自己的
房子了,这时候就相于分家了。


总结有以下三点区别:

1.fork:子进程拷贝父进程的数据段,代码段

 vfork:子进程与父进程共享数据段

2. fork ()父子进程的执行次序不确定
vfork 保证子进程先运行,在调用exec 或exit 之前与父进程数据是共享的,在它调用exec
或exit 之后父进程才可能被调度运行。

3.vfork ()保证子进程先运行,在她调用exec 或exit 之后父进程才可能被调度运行。如果在
调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。


关于系统调用:
在系统调用中,进程使用的是内核栈(每个进程有着自己独有 的内核栈)。此时,父进程在内核里面是安全的,随便子进程怎么违规。内核会保证系统调用vfork的完整性,系统调用的返回值也不会有问题(它是通过寄存 器传回用户空间的,跟栈无关)。

接下来引入exec。

fork() 函数用于创建一个新的子进程,该子进程几乎复制了父进程的全部内容,但是,这个新创建的子进程如何执行呢?exec 函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件


第4位统一为:exec
第5位
l:参数传递为逐个列举方式
execl、execle、execlp
v:参数传递为构造指针数组方式
execv、execve、execvp
第6位
e:可传递新进程环境变量
execle、execve
p:可执行文件查找方式为文件名
execlp、execvp

== 事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用  (库函数和系统函数的区别)

l表示list(列举参数),v表示vector(参数向量表)


调用示例
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};

char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};

execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);

execv("/bin/ps", ps_argv);

execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);

execve("/bin/ps", ps_argv, ps_envp);

execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);

execvp("ps", ps_argv);


exec函数族使用注意点
在使用exec函数族时,一定要加上错误判断语句。因为exec很容易执行失败,其中最常见的原因有:
① 找不到文件或路径,此时errno被设置为ENOENT。
② 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT。
③ 没有对应可执行文件的运行权限,此时errno被设置为EACCES。

exec后新进程保持原进程以下特征
环境变量(使用了execle、execve函数则不继承环境变量);
1.程ID和父进程ID;
2.用户ID和实际组ID;
3.附加组ID;
4,进程组ID;
5.会话ID;
6.控制终端;
7.当前工作目录;
8.根目录;
9.文件权限屏蔽字;
10.文件锁;
11.进程信号屏蔽;
12.未决信号;
13.资源限制;
此处注意信号处理方式会恢复默认

二、为什么有了wait函数族还需要SIGCHLD信号


首先,在谈这个问题时,先说说unix中僵尸进程的含义,APUE2中如下定义:
In UNIX System terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie.
也就是说,但凡是父进程没有调用wait函数获得子进程终止状态的子进程在终止之后都是僵尸进程,这个概念的关键一点就是父进程是否调用了wait函数.

而关于SIGCHLD信号,APUE2中又如是说:
Whenever a process terminates or stops, the SIGCHLD signal is sent to the parent. By default, this signal is ignored, so the parent must catch this signal if it wants to be notified whenever a child's status changes. The normal action in the signal-catching function is to call one of the wait functions to fetch the child's process ID and termination status.
简单的说,子进程退出时父进程会收到一个SIGCHLD信号,默认的处理是忽略这个信号,而常规的做法是在这个信号处理函数中调用wait函数获取子进程的退出状态.

这里存在一个疑问,既然在SIGCHLD信号的处理函数中要调用wait函数族,为什么有了wait函数族还需要使用SIGCHLD信号?

我们知道,unix中信号是异步处理某事的机制,好比说你准备去做某事,去之前跟邻居张三说如果李四来找你的话就通知他一声,这让你可以抽身出来去做这件事,而李四真正来访时会有人通知你,这个就是异步信号一个较为形象的比喻.

一般的,父进程在生成子进程之后会有两种情况,一种是父进程继续去做别的事情,类似上面举的例子,另一种是父进程啥都不做,一直在wait子进程退出.SIGCHLD信号就是为这第一种情况准备的,它让父进程去做别的事情,而只要父进程注册了处理该信号的函数,在子进程退出时就会调用该函数,在函数中wait子进程得到终止状态之后再继续做父进程的事情.

也就是说,明确以下几点:
1)凡父进程不调用wait函数族获得子进程终止状态的子进程在退出时都会变成僵尸进程.
2)SIGCHLD信号可以异步的通知父进程有子进程退出.

三、system函数中为什么要屏蔽child信号

在 UNIX的system()函数实现过程中,要求在父进程中忽略掉SIGINT和SIGQUIT信号,但是要将SIGCHLD信号阻塞(在子进程中将 SIGINT和SIGQUIT信号设为默认,SIGCHLD信号解锁)。子进程执行完毕后,在父进程中调用waitpid(pid_t, &state, 0)。问题: 
1、若父进程已被waitpid阻塞,在子进程返回时,此时在父进程中SIGCHLD被阻塞(BLOCK),父进程收不到SIGCHLD信号,waitpid()函数能否正确返回,收集到子进程的信息? 
2、 waitpid若能正确完成,在以后父进程中,将SIGCHLD信号UNBLOCK,用sigprocmask()函数解锁,书上说,在 sigprocmask()函数返回以前,会将以前阻塞的信号发送给进程,父进程是否还能收到SIGCHLD信号?若能收到何必在开始时将SIGCHLD 进程阻塞。 


简单的对这个问题的解释是wait及其变体并不是通过sigchld信号来知道子进程状态的。
sigprocmask 阻塞的是有signal或sigaction设置的信号处理程序,即带有SIGCHLD_Handle()等处理函数。wait不是靠接收sigchld 信号获得子进程的退出状态的,如果进程中同时设置了signal和wait,则子进程退出后发出sigchld信号,交到signal的信号处理程序处 理,wait接收到子进程退出状态。
只是接收sigchld,而不调用wait还是会使子进程僵死的。一般的只有调用wait才能使子进程不成为僵死进程(除了2次fork 等或其他一些手段)。

概括下:waitpid不是依靠SIGCHLD是否到达来判断子进程是否退出,但是如果设置了SIGCHLD的处理函数,那么就需要等待SIGCHLD信号 的发生并完成信号处理函数,waitpid才能接收到子进程的退出状态。在APUE中的system()实现中阻塞了SIGCHLD信号,但是并没有设置 信号处理函数,所以waitpid在阻塞了SIGCHLD的情况下依然能正常返回,因为SIGCHLD在未设置信号处理函数的情况下不会影响到 waitpid的工作。至于为什么要阻塞SIGCHLD信号呢?那就是为了防止其他程序(main除了会调用system还会使用其他程序)设置了 SIGCHLD的信号处理函数,如果其他程序设置了SIGCHLD信号处理函数,在waitpid等待子程序的返回前,要去处理SIGCHLD信号处理程 序,如果阻塞了该信号,就不会去处理该信号处理程序,防止多余信息在system()中的出现。

四、wait函数的参数(包括状态值细分)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,父子进程常见的问题包括: 1. 父子进程间的通信:父子进程可以通过管道、共享内存、消息队列等方式进行通信,以实现数据的交换和同步操作。 2. 子进程的状态获取:父进程可以使用wait/waitpid函数来获取子进程的状态,包括子进程的退出状态和资源使用情况。 3. 父子进程间的同步:在某些情况下,父进程需要等待子进程完成某些任务后再继续执行。可以使用wait/waitpid函数或者使用信号机制来实现父子进程的同步。 4. 父子进程共享内存的问题:在vfork调用中,子进程与父进程共享内存,这可能导致父子进程之间的访存冲突。程序需要注意避免在共享内存区域进行写操作,以免影响到其他进程的运行。 5. 父子进程的资源管理:父子进程共享一些资源,如文件描述符、系统信号处理函数等。在创建子进程时,父进程需要考虑好资源的合理分配和管理,以免造成资源的浪费或冲突。 总之,父子进程之间的关系和交互是Linux中常见的问题,合理的设计和管理可以确保程序的正确运行和资源的有效利用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Linux父子进程的一些常见问题](https://blog.csdn.net/flurry_rain/article/details/122327665)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [关于linux父子进程几个问题总结](https://blog.csdn.net/qq_27569699/article/details/120568763)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值