2016-02-02
进程控制
进程标识
每个进程都有一个非负整型的唯一的进程id,因为进程id表示服总是唯一的,常将其用作其他标识符的一部分以保证其唯一性。
有某些专用的进程:进程id为0是调度进程,常常被称为交换进程。该进程不执行任何磁盘上的程序,它是内核的一部分,因此也被称为系统进程。进程id 1通常是init进程,在自举过程结束时由内核调用。该进程的程序文件在早期unix版本中是/etc/init在较新版本中是/sbin/init。此进程负责在内核自居后启动一个unix系统。init通常读与系统有关的初始化文件/etc/rc*文件并将系统引导到一个状态。init京城绝不会终止。它是一个普通用户,但是它以超级用户特权运行。某些unix的虚存视线中,进程id 2是页精灵进程。此进程负责支持虚存系统的请页操作。与交换进程一样,页精灵进程也是内核进程。
除了进程id,每个进程还有一些其他标识。
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
fork函数
一个现存进程调用fork函数是unix内核创建一个新进程的唯一方法。
pid_t fork(void)
由fork创建的新进程被称为子进程。该函数被调用一次但返回两次。两次返回的区别是子进程的返回值时0,父进程的返回值时新子进程的进程id。子进程和父进程急促执行fork之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意这是子进程所拥有的靠背。子父进程并不共享这些存储空间。或者是写时复制(copy-on-write).一般来说,在fork之后是父进程还是子进程先执行不确定,取决于内核使用的调度算法。如果要求父子进程之间同步,则需要进行某种形式的进程间通信。
在fork之后处理文件描述符有两种常见的情况:
- 父进程等待子进程完成。这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读写操作的任一共享描述符的文件位移量已做了相应的更新。
- 父子进程各自执行不同的程序段。在这种情况下,在fork后父子进程各自关闭他们不需要使用的文件描述符,并且不干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。
除了打开文件之外,很多父进程的而其他性子也由子进程继承
- 实际用户id、实际组id、有效用户id、有效组id
- 添加组id
- 进程组id
- 对话期id
- 控制终端
- 设置用户id和设置组id
- 当前工作目录
- 根目录
- 文件方式创建屏蔽字
- 信号屏蔽和排列
- 对任意打开文件描述符的在执行时关闭标识
- 环境
- 连接的共享存储段
- 资源限制
父子进程之间的区别是
- fork的返回值
- 进程id
- 不同的父进程id
- 子进程的tms_utime,tms_stime,tms_cutime及tms_ustime设置为0
- 父进程设置的锁,子进程不继承
- 子进程的未决告警被清除
- 子进程的未决信号集设置为空
fork失败的两个主要原因:系统已经有了太多的进程或者该实际用id的进程总数超过了系统限制。
fork有两种用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。这在网络服务进程中是常见的--父进程等待委托者的服务请求。当这种请求到达时,父进程调用fork使子进程处理此请求。父进程则继续等待下一个服务请求。
- 一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程在从fork返回后立即调用exec.
vfork
vfork函数调用序列和返回值与fork相同。
vfork用于创建一个新进程,而该进程的目的是exec一个新程序。vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec,于是也就不会存访该地址空间。不过在子进程调用exec或者exit之前,它在父进程 的空间中运行
。这种工作方式在某些unix的页式虚存视线中提高了效率。
vfork保证子进程先运行,在它调用exec或exit之后父进程才能被调度运行。
exit函数
不管进程如何终止,都会执行内核中的同一段代码。这段代码为相应的继承关闭所有打开描述符,释放它所使用的存储器等。
对于任意一种终止情形,我们都希望终止进程能够通知其父进程它如何终止的。对于exit _exit 这是依靠传递给他们退出状态参数来实现的。在异常终止情况,内核产生一个指示其异常终止的终止状态。在任意一种情况下,该终止进程的父进程都能用wait或waitpid
取得终止状态。注意,这里使用了退出状态和终止状态,他们是有区别的。在最后调用_exit时内核将其退出状态转化为终止状态。如果子进程正常终止,则父进程可以获得子进程的退出状态。
对于其父进程已经终止的所有进程,他们的父进程都改变为init进程。我们称这些进程由init进程领养。其操作过程大致是:在一个进程终止时,内核诸葛检查所有的活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程id就更改为1.这种处理方法保证了每个进程都有一个父进程。
内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程id,进程的终止状态以及该进程使用的CPU时间重量。内核可以释放终止进程的所有存储器,关闭其所打开文件。
unix术语中一个已经终止但其父进程尚未对其进行善后处理的进程被称为僵死进程。ps命令将僵死进程的状态打印为Z.如果编写了一个长期运行的程序,它fork了很多子进程,那么除非父进程等待取得子进程的终止状态,否则这些子进程就会变成僵死进程。当一个进程被init领养后就不会变成僵死进程。
wait和waitpid函数
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是一个异步事件,所以这种信号也是内核向父进程发的异步通知,父进程可以忽略该信号,或者提供一个该信号发生时被调用执行的函数。对于这种信号的系统默认动作是忽略它。现在需要知道的是调用wait或者waitpid的进程可能会:
- 阻塞(如果其所有子进程都还在运行)
- 带子进程的终止状态立即返回
- 出错立即返回
如果进程由于接收到SIGCHLD信号而调用wait则可期望wait会立即返回。但是如果在任意时刻调用wait则进程可能阻塞。
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options)
这两个函数区别是:
- 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选这项,可使调用者不阻塞。
- waitpid 并不等待第一个终止的子进程,他有若干个选择项,可以控制它所等待的进程。
如果一个子进程已经终止,是一个僵死进程,则wait立即返回并取得该进程的状态,否则wait使其调用者阻塞知道一个子进程终止。如果调用者阻塞而他有多个子进程,则在其一个子进程终止时,wait就立即返回。因为wait返回终止子进程的ID,所以它总能了解是哪个子进程终止了。
这两个函数参数statloc是一个整型指针,如果statloc不是一个空指针,则终止进程的终止状态就放在它所指向的单元内。如果不关心终止状态,则可以将参数指定为空指针。
根据传统这两个函数返回的整型状态字是由实现定义的。其中某些位表示退出状态,其他为则表示信号编号。有一位指示是否产生了一个core文件等等。POSIX规定终止状态用定义在<sys/wait.h>中的各个宏来查看。
WIFIEXITED(status) 若为正常终止子进程返回的状态则为真。若为这种情况可以执行WEXITSTATUS(status)取子进程传给exit
WIFISIGNALED(status)若为异常终止子进程返回的状态则为真。对于这种情况可以执行WTERMSIG(status)取是子进程终止的信号编号
WIFISTOPPED(status)若为当前展厅子进程的返回状态,则为真。对于这种情况,可执行WSTOPSIG(status)取使子进程暂停的信号编号。
waitpid提供等待特定进程的方法
如果pid==-1等待任一子进程,于是在这一功能方面waitpid与wait等效
如果pid>0等待期进程id与pid相等的子进程。
pid==0等待期组id等于调用进程的组id的任意子进程
pid<-1等待其组id等于pid的绝对值的任意子进程
waitpid返回终止子进程的进程id,而该子进程的终止状态则通过statloc返回。对于wait其唯一的出错时调用进程没有子进程,但对于waitpid如果指定的进程或进程组不存在或者调用进程没有子进程都能出错。
options参数使我们能进一步控制waitpid的操作。此参数或者是0,或者是WNOHANG及WUNTRACED的位运算
WNOHANG 若由pid指定的子进程不立即可用则waitpid不阻塞,此时其返回值为0
WUNTRACED 若实现支持作业控制,则由pid指定的任意子进程状态已暂停,且其状态自暂停以来还未报告过则返回器状态。
waitpid提供了wait函数没提供的三个功能:
- waitpid可以等待某特定的进程
- waitpid提供了一个wait的非阻塞版本
- waitpid支持作业控制