APUE学习笔记三——进程
main函数
当内核执行C程序时(使用一个exec函数),在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。
进程终止
正常终止: 1. 从main返回; 1. 调用exit; 1. 调用_exit或_Exit; 1. 最后一个线程从其启动例程返回; 1. 从最后一个线程调用pthread_exit。 异常终止: 1. 调用abort; 1. 接到一个信号; 1. 最后一个线程对取消请求做出响应。
退出函数
_exit()、_Exit()
立即进入内核,exit()
则先执行一些清理处理,然后返回内核。- 终止处理程序,通过
atexit()
登记,exit()
自动调用,调用顺序与登记顺序相反,同一函数如若登记多次,也会被调用多次。
环境变量
- 访问特定的环境变量:
getenv()、setenv()、putenv()、unsetenv()、clearenv()
; - 查看整个环境,必须使用environ指针。
C程序的存储空间布局
存储空间分配
malloc()、calloc()、realloc()、free()
函数setjmp和longjmp
在C中,goto语句是不能跨越函数的,而执行这种类型跳转功能的是函数setjmp和longjmp。这两个函数对于处理发生在很深层嵌套函数调用中的出错情况是非常有用的。
在希望返回到的位置调用setjmp,在要跳转的地方调用longjmp。
进程的资源限制
每个进程都有一组资源限制,其中一些可以用getrlimit和setrlimit函数查询和更改。
进程标示符
getpid()、getppid()、getuid()、geteuid()、getgid()、getegid()
,这些函数都没有出错返回。
父进程调用fork后,子进程继承的属性
- 所有打开的文件描述符;
- 实际用户ID、实际组ID、有效用户ID、有效组ID;
- 附属组ID;
- 进程组ID;
- 会话ID;
- 控制终端;
- 设置用户ID标志和设置组ID标志;
- 当前工作目录;
- 根目录;
- 文件模式创建屏蔽字;
- 信号屏蔽和安排;
- 对任一打开文件描述符的执行时关闭(close-on-exec)标志;
- 环境;
- 连接的共享存储段;
- 存储映像;
- 资源限制。
父进程和子进程之间的区别
- fork返回值不同;
- 进程ID不同;
- 父进程ID不同;
- 子进程的tms_utime、tms_stime、tms_cutime和tms_cstime的值设置为0;
- 子进程不继承父进程设置的文件锁;
- 子进程的未处理闹钟被清除;
- 子进程的未处理信号集设置未空集。
fork失败原因
- 系统中已经有了太多的进程;
- 该实际用户ID的进程总数超过了系统限制(CHILD_MAX)。
fork的两种用法
- 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段;
- 一个进程要执行一个不同的程序。
vfork调用
- 子进程调用exec或exit之前,它在父进程的空间中运行,fork会部分复制。
- 保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。
- 如果子进程修改数据(除了用于存放vfork返回值的变量)、进行函数调用、或者没有调用exec或exit就返回都可能会带来未知的结果。
进程终止
- 对于父进程已经终止的所有进程,它们(孤儿进程)的父进程都改为init进程。
- 内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息(进程ID、该进程的终止状态以及该进程使用的CPU时间总量)内核可以释放终止进程所使用的所有存储区,关闭所有打开的文件。如果一个已经终止、但是其父进程尚未对其进行善后处理的进程称为僵死进程。
调用wait或waitpid的进程可能会发生什么
- 如果其所有子进程都还在运行,则阻塞;
- 如果一个子进程终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
- 如果它没有任何子进程,则立即出错返回。
与终止状态有关的宏:WIFEXITED()、WIFSIGNALED()、WIFSTOPPED()、WIFCONTINUED()
其他相关函数:waitid()、wait3()、wait()4
exec族函数
调用exec并不创建新进程,所以前后进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
执行exec后,新程序从调用进程继承了下列属性
- 进程ID和父进程ID;
- 实际用户ID和实际组ID;
- 附属组ID;
- 进程组ID;
- 会话ID;
- 控制终端;
- 闹钟尚余留的时间;
- 当前工作目录;
- 根目录;
- 文件模式创建屏蔽字;
- 文件锁;
- 进程信号屏蔽;
- 未处理信号;
- 资源限制;
- nice值;
- tms_utime、tms_stime、tms_cutime、tms_cstime值;
- 对于打开文件的处理与每个描述符的执行时关闭标志值有关;
- 打开的目录流被关闭,opendir函数实现的,它调用fcntl函数为对应于打开目录流的描述符设置执行时关闭标志。
有效ID是否改变取决于所执行程序文件的设置用户ID位和设置组ID位是否设置。
更改用户ID和更改组ID
setuid()、setgid()、setreuid()、setregid()、seteuid()、setegid()
GID的设置与UID类似,附属组ID不受setgid、setregid和setegid函数的影响。
函数system
在程序中执行一个命令字符串。
进程会计
统计进程的信息和资源使用情况。
进程调度
nice()、getpriority()、setpriority()
进程时间
- 墙上时钟时间:从进程从开始运行到结束,时钟走过的时间,这其中包含了进程在阻塞和等待状态的时间。
- 用户CPU时间:就是用户的进程获得了CPU资源以后,在用户态执行的时间。
- 系统CPU时间:用户进程获得了CPU资源以后,在内核态的执行时间。
任一进程都可通过times()
获取它自己以及已终止子进程的上述时间值。
进程关系
每个 进程组 有一个组长进程,组长进程的进程组ID等于其进程ID。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命周期。
进程调用setpgid()
可以加入一个现有的进程组或者创建一个新进程组。一个进程只能为它自己或它的子进程设置进程组ID。
会话 是一个或多个进程组的集合。
进程调用setsid()
函数建立一个新会话。如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话。将发生如下3件事:
- 该进程变成新会话的会话首进程,此时,该进程是新会话中的唯一进程;
- 该进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID;
- 该进程没有(失去)控制终端。
如果该调用进程已经是一个进程组的组长,则此函数返回出错。
getsid()
返回会话首进程的进程组ID,也即会话ID。
控制终端
- 一个会话可以有一个控制终端;
- 建立与控制终端连接的会话首进程被称为控制进程;
- 一个会话中的几个进程组可被分成一个前台进程组以及一个或多个后台进程组;
- 如果一个会话有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组;
- 无论何时键入终端的中断键(退出键),都会将中断(退出)信号发送至前台进程组的所有进程;
- 如果终端接口检测到网络已经断开连接,则将挂断信号发送至控制进程。
一个终端至多只能成为一个会话的控制终端。打开控制终端会致使会话首进程成为终端的控制进程。一旦断开了与终端的连接(比如,关闭了终端窗口),控制进程将会收到SIGHUP信号。
函数tcgetpgrp(int fd)、tcsetpgrp(int fs, pid_t pgrpid)和tcgetsid(int fd)
函数tcgetpgrp返回前台进程组ID,它与在fd上打开的终端相关联。
如果进程有一个控制终端,则该进程可以调用tcsetpgrp将前台进程组ID设置为pgrpid。pgrpid值应当是同一会话中的一个进程组的ID。fd必须引用该会话的控制终端。
通过tcgetsid能获得会话首进程的进程组ID。
tpgid为前台进程组的ID。
孤儿进程组:该组中每个成员的父进程要么是该组的一个成员,要么不是该组所属会话的成员。如果进程组不是孤儿进程组,那么在属于同一会话的另一个组中的父进程就有机会重新启动该组中停止的进程。
前台进程组的组长进程终止时,该进程组将变成后台进程组。