内容简介:APUE 学习笔记——进程控制
1、子进程与父进程的写时赋值是什么?
- 子进程是父进程的一份一模一样的拷贝,如子进程获取了父进程数据空间、堆、栈的副本。
- 父子进程共享正文段(因为正文段是只读的)
- 父子进程并不共享这些数据空间、堆、栈
- 由于创建子进程的目的通常是为了完成某个任务,因此
fork
之后经常跟随exec
,所以很多操作系统的实现并不执行一个父进程数据段、堆和栈的完全拷贝,而是使用写时赋值技术(copy-on-write:COW
)- 这些区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读
- 如果父子进程中有一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本
2、 子进程继承父进程的属性有哪些?
- 当前工作目录、根目录
- 实际用户
ID
、实际组ID
、有效用户ID
、有效组ID
、附属组ID
、进程组ID
、会话ID
、控制终端、设置用户ID
标志和设置组ID
标志 - 打开的文件描述符
- 文件模式创建屏蔽字、信号屏蔽和信号处理
- 对任一打开文件描述符的执行时关闭标志、环境、连接的共享存储段、存储映像、资源限制
3、父子进程的文件描述符的继承关系如何?
- 父进程的所有打开的文件描述符都被复制到子进程中。父进程和子进程每个相同的打开描述符共享同一个文件表项
- 更重要的是:父进程和子进程共享同一个文件偏移量
- 如果父进程和子进程写同一个描述符指向的文件,但是又没有任何形式的同步,则它们的输出会相互混合
- 如果父进程
fork
之后的任务就是等待子进程完成,而不作任何其他的事情,则父进程和子进程无需对打开的文件描述符做任何处理。因为此时只有子进程处理文件 - 如果父进程
fork
之后,父进程与子进程都有自己的任务要处理,则此时父进程和子进程需要各自关闭它们不需要使用的文件描述符,从而避免干扰对方的文件操作
- 如果父进程
4、父子进程的区别有哪些?
- 子进程的
tms_utime,tms_stime,tms_cutime,tms_ustime
的值设置为0 - 子进程不继承父进程设置的文件锁
- 子进程的未处理闹钟被清除
- 子进程的未处理信号集设置为空集
5、vfork 与 fork 直接的区别?
-
vfork
用于创建一个新进程,该新进程的目的是exec
一个新程序,所以vfork
并不将父进程的地址空间拷贝到子进程中。vfork
的做法是:在调用exec
或者exit
之前,子进程在父进程的空间中运行
所以在
exec
或者exit
之前,子进程可以篡改父进程的数据空间 vfork
保证子进程优先运行,在子进程调用exec
或者exit
之后父进程才可能被调度运行当子进程调用
exec
或者exit
中的任何一个时,父进程会恢复运行,在此之前内核会使父进程处于休眠状态
6、父进程如何获知子进程已经结束?
内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait
函数或者waitpid
函数时,可以得到这些信息。
- 这些信息至少包括:终止进程的进程
ID
、终止进程的终止状态、终止进程的使用的CPU时间总量 - 内核此时可以释放终止进程使用的所有内存,关闭它所有的打开文件。但是该终止进程还残留了上述信息等待父进程处理
- 如果父进程在子进程之前终止,那么内核会将该子进程的父进程改变为
init
进程,称作由init
进程收养。其原理为:- 在一个进程终止时,内核逐个检查所有活动进程,以判断这些活动进程是否是正要终止的进程的子进程
- 如果是,则该活动进程的父进程
ID
就改为 1
- 对于
init
超级进程,它被设计成:任何时候只要有一个子进程终止,就立即调用wait
函数取得其终止状态。这种做法防止系统中塞满了僵死进程 - 当一个进程终止时,内核就向其父进程发送
SIGCHLD
信号。这种信号是一个异步信号,因为该信号可能在任何时间发出- 父进程可以选择忽略此信号。这是系统的默认行为
- 父进程也可以针对此信号注册一个信号处理程序,从而当接收到该信号时调用相应的信号处理程序
- 我们称一个已经终止、但是等待父进程对它进行善后处理的进程称作僵死进程,在
ps
命令中显示为Z
- 所谓善后处理,就是父进程调用
wait
函数或者waitpid
函数读取终止进程的残留信息 - 一旦父进程进行了善后处理,则终止进程的所有占用资源(包括残留信息)都得到释放,该进程被彻底销毁
- 所谓善后处理,就是父进程调用
7、waitpid
的 pid
参数与 options
参数设置?
pid
:- 如果
pid==-1
:则等待任意一个子进程终止 - 如果
pid>0
:则等待进程ID
等于pid
的那个子进程终止 - 如果
pid==0
:则等待组ID
等于调用进程组ID
的任一子进程终止 - 如果
pid<0
:等待组ID
等于pid
绝对值的任一子进程终止
- 如果
options
:或者是0,或者是下列常量按位或的结果:WNOHANG
:没有指定的子进程终止时,并不阻塞程序的执行WUNTRACED
:执行作业控制。若操作系统支持作业控制,则由pid
指定的任一子进程在停止后已经继续,但其状态尚未报告,则返回其状态WCONTINUED
:执行作业控制。若操作系统支持作业控制,则由pid
指定的任一子进程已处于停止状态,并且其状态自停止以来尚未报告过,则返回其状态
8、wait
与 waitpid
之间的区别?
-
wait
的语义是等待任何一个子进程终止:- 如果当前进程的所有子进程都还在运行,则阻塞
- 如果有一个子进程已终止,正在等待父进程获取其终止状态,则当前进程取得该子进程的终止状态并立即返回
- 如果当前进程没有任何子进程,则立即出错返回
-
waitpid
的语义是等待指定的子进程终止:- 如果当前进程的所有子进程都在运行:
- 如果
options
指定为WNOHANG
,则waitpid
并不阻塞,而是立即返回 0 - 如果
options
未指定为WNOHANG
,则waitpid
阻塞
- 如果
- 如果指定
pid
的子进程已终止,正在等待父进程获取其终止状态,则当前进程取得该子进程的终止状态并立即返回 - 如果指定的
pid
有问题(如不存在,或者不是当前进程的子进程),则立即出错返回
- 如果当前进程的所有子进程都在运行:
- 对于出错的情况:
wait
出错的原因是:- 调用进程没有子进程
- 函数调用(正在阻塞中)被一个信号中断
waitpid
出错的原因是:- 指定的进程或者进程组不存在
pid
指定的进程不是调用进程的子进程- 函数调用(正在阻塞中)被一个信号中断
9、waitid
函数中 idtype
与 options
参数的意义?
idtype
:指定了id
类型,可以为下列常量P_PID
:等待特定进程。此时id
表示要等待的子进程的进程ID
P_GID
:等待属于特定进程组的任一子进程。此时id
表示要等待的进程组ID
P_ALL
:等待任一子进程。此时忽略id
options
:指示调用者关心哪些状态变化。可以是下列常量的按位或:WCONTINUED
:等待这样的子进程:它以前曾被停止过,此后又继续执行,但是其状态尚未报告WEXITED
:等待已经终止的子进程WNOHANG
:如无可用的子进程终止状态,立即返回而不是阻塞WNOWAIT
:不破坏子进程的终止状态,该子进程的终止状态可以由后续的wait,waitid,waitpid
调用取得WSTOPPED
:等待这样的子进程:它已经停止,但是其状态尚未报告
10、exec
几个函数之间的区别?
- 前四个函数取路径名作为参数;后两个函数取文件名作为参数;最后一个取文件描述符做参数
- 若
filename
中包含/
,则视为路径名 - 若
filename
不包含/
,则按照PATH
环境变量指定的各个目录中搜寻可执行文件
- 若
- 函数
execl,execlp,execle
要求将新程序的每个命令行参数都说明为一个单独的参数,这种参数表以空指针结尾;函数execv,execvp,execve,fexecve
应先构造一个指向各参数的指针数组,然后将该指针数组的地址作为参数l
表示列表list
v
表示矢量vector
l
形式中,必须以空指针结尾,否则新程序根本不知道要读取多少个参数。空指针就是命令行参数序列终止的标记v
形式中,数组的最后一个元素必须是空指针,否则报错。
- 以
e
结尾的execle,execve,fexecve
可以传递一个指向环境字符串指针数组的指针。注意这个数组的最后一个元素必须是空指针,否则报错。其他四个函数则使用调用进程的environ
变量为新程序复制现有的环境 -
在很多UNIX操作系统中,这7个函数只有
execve
是内核的系统调用。另外 6 个只是库函数。它们最终都要调用该系统调用11、
exec
系列函数对进程属性有什么影响? - 执行
exec
之后,新程序的进程ID不变,进程的大多数属性不变。但是对打开文件的处理要注意:- 进程中每个打开的文件描述符都有一个执行时关闭标志。若设置了此标志,则执行
exec
时会关闭该文件描述符; - 否则该文件描述符仍然保持打开。系统默认行为是不设置执行时关闭标志
- 进程中每个打开的文件描述符都有一个执行时关闭标志。若设置了此标志,则执行
- 执行
exec
之后,进程的实际用户 ID 和实际组 ID不变,但是进程的有效用户 ID 要注意:- 进程的有效用户 ID 和有效组 ID 是否改变取决于所执行程序文件的设置用户 ID 和设置组 ID 位是否设置。
- 若程序文件的设置用户 ID 位已设置,则进程的有效用户 ID 变成程序文件所有者的 ID;否则有效用户 ID 不变
- 若程序文件的设置组 ID 位已设置,则进程的有效组 ID 变成程序文件所有组的 ID;否则有效组 ID 不变
- 进程的有效用户 ID 和有效组 ID 是否改变取决于所执行程序文件的设置用户 ID 和设置组 ID 位是否设置。
12、system
函数与 exec
的区别?
system
用于将一个字符作为命令来执行。它等同于同时调用了fork
、exec
、waitpid
-
system
相较于fork+exec
的优点是:system
进行了所需的各种出错处理以及各种信号处理。缺点是:一旦调用system
的进程具有超级用户权限,则system
执行的命令也具有超级用户权限。因为
system
的实现过程中并没有更改有效用户ID和实际用户ID的操作。- 因此如果一个进程以特殊的权限运行,而它又想生成另一个进程执行另外一个程序,则它应该直接使用
fork_exec
并且在fork
之后,exec
之前改回普通权限。 - 设置用户
ID
和设置组ID
程序绝不应该调用system
函数
- 因此如果一个进程以特殊的权限运行,而它又想生成另一个进程执行另外一个程序,则它应该直接使用
13、 setuid
、setuid
设置的规则?
- 如果进程具有超级用户特权,则
setuid
函数将实际用户ID
,有效用户ID
以及保存的设置用户ID
(saved set-user-ID
) 全部设置为uid
(此时uid
没有限制) - 如果进程没有超级用户特权,但是
uid
等于实际用户ID
或者保存的设置用户ID
,则setuid
只会将有效用户ID
设置为uid
,不改变实际用户ID
和保存的设置用户ID
- 如果上面两个条件都不满足,则
errno
设置为EPERM
并返回 -1 - 上述讨论中,假设
_POSIX_SAVED_IDS
为真。如果为提供此功能,则对于保存的设置用户ID
部分都无效 - 针对
setgid
的讨论类似setuid
14、 seteuid
、seteuid
设置的规则?
- 如果进程具有超级用户权限,则
seteuid
将设置进程的有效用户ID
为uid
(此时uid
没有限制) - 如果进程没有超级用户权限,则
seteuid
只能将进程的有效用户ID
设置为它的实际用户ID
或者保存的设置用户ID
- 针对
setegid
的讨论类似seteuid
API
pid_t getpid(void); // 返回值:调用进程的进程ID
pid_t getppid(void); // 返回值:调用进程的父进程ID
uid_t getuid(void); // 返回值:返回进程的实际用户ID
uid_t geteuid(void); // 返回值:返回进程的有效用户ID
gid_t getgid(void); // 返回值:返回进程的实际组ID
gid_t getegid(void); // 返回值:返回进程的有效组ID
pid_t fork(void);
pid_t wait(int *staloc);
pid_t waitpid(pid_t pid,int *staloc,int options);
WIFEXITED(status):如果子进程正常终止,则为真。此时可以执行`WEXITSTATUS(status)`获取子进程的退出状态的低 8 位
WIFSIGNALED(status):如果子进程异常终止,则为真。此时可以执行`WTERMSIG(status)`获取使得子进程终止的信号编号
WIFSTOPPED(status):如果子进程的当前状态为暂停,则为真。此时可执行`WSTOPSIG(status)`获取使得子进程暂停的信号编号
WIFCONTINUED(status):如果子进程在暂停后已经继续执行了,则为真。
int waitid(idtype_t idtype,id_t id,siginfo_t *infop,int options);
pid_t wait3(int *staloc,int options,struct rusage *rusage);
pid_t wait4(pid_t pid,int *staloc,int options,struct rusage *rusage);
int execl(const char *pathname,const char *arg0,.../*(char *) 0 */);
int execv(const char *pathname,char *const argv[]);
int execle(const char *pathname,const char *arg0,.../*(char *) 0,char *const envp[] */);
int execve(const char *pathname,char *const argv[],char *const envp[]);
int execlp(const char *filename,const char*arg0,.../*(char *) 0*/);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd,char *const argv[],char *const evnp[]);
int system(const char *cmdstring);
int seteuid(uid_t uid);
int setegid(gid_t gid);
int seteuid(uid_t uid);
int setegid(gid_t gid);
char *getlogin(void);
clock_t times(struct tms *buf);//返回流逝的墙上始终时间(以时钟滴答数为单位);调用两次`times`,然后取两次墙上时钟的差值
struct tms{
clock_t tms_utime; //用户 CPU 时间
clock_t tms_stime; //系统 CPU 时间
clock_t tms_cutime; //终止的子进程的用户 CPU 时间的累加值
clock_t tms_cstime; //终止的子进程的系统 CPU 时间的累加值
}
int nice(int incr);
int getpriority(int which,id_t who);
int setpriority(int which,id_t who,int value);
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CGroups 控制进程资源
- 如何在 Go 中使用 CGroup 实现进程内存控制
- 如何在 Go 中使用 CGroup 实现进程内存控制
- Firefox 69 Beta 9 发布,提供控制进程优先级的能力
- 进程:进程生命周期
- Python 知识巩固:通过主进程带起多个子进程实现多进程执行逻辑
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。