UNIX环境高级编程之第8章:进程控制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/youngyangyang04/article/details/48037855

8.1 引言

本章介绍UNIX系统的 进程控制,包括 创建进程执行进程进程终止.还说明 进程属性的各种ID-实际,有效和保存的用户ID和组ID,以及他们如何受到进程控制原语的影响.
本章包含 解释器文件(Interpreter file)system function
还有 进程会记机制(process accounting),可以让给我们从不同的角度谅解进程的控制功能.

8.2 进程标示(Process Identifiers)

每个进程都有一个非负整型表示的唯一进程ID.
除了进程ID,每个进程还有一些其他标识符(identifiers)


8.3 函数fork

一个现有的进程可以调用fork函数创建一个新进程.
#include <unistd.h>
pid_t fork(void);
由fork创建的新进程被成为子进程.但返回两次,一次是子进程的返回值是0,而父进程返回新建子进程的进程ID
将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID.
fork是子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID.
子进程和父进程继续执行fork调用之后的指令.子进程是父进程的副本(得到父进程数据空间,堆和栈的副本). 父进程和子进程并不共享这些存储空间部分.父进程和子进程共享正文段. 
标准输出到终端设备,则它是行缓冲,否则是全缓冲

文件共享

重定向(shell)父进程的标准输出,子进程的标准输出也被重定向.实际上,fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中,这里的复制针对文件描述符,就好像执行dup函数.父进程和子进程每个相同的打开描述符共享一个文件表项(entry).
一个进程具有3个不同的打开文件,标准输入,标准输出和标准错误.所示结构:


重要的一点是:父进程和子进程共享同一个文件偏移量

在fork之后处理文件描述符有以下两个常见的情况
(1)父进程等待子进程完成.
(2)父进程和子进程各自执行不同的程序段

除了打开文件之外,父进程的其他属性也由子进程继承
实际用户ID,实际组ID,有效用户ID,有效组ID
附属组ID
进程组ID
会话ID
控制终端
设置用户ID标志和设置组ID标志
当前工作目录
根目录
文件模式创建屏蔽字
信号屏蔽和安排
对任一打开文件描述符的执行时关闭标志
环境
连接的共享存储段
存储影像
资源限制
父进程和子进程之间的区别具体如下
fork的返回值不同
进程ID不同
......

8.4 函数vfork

vfork函数用于创建一个新进程,而该新进程的目的是exec一个新程序
vfork和fork一样都创建一个子进程,但是它并不是将父进程的地址空间完全复制到子进程中,因为子进程会调用exec或者exit( 如果不调用就会带来未知结果,这样做主要会提高效率因为只是复制父进程的一部分),于是也不会引用该地址空间. 不过子进程调用exec或exit之前,它在父进程的空间中运行,所以子进程对变量进行加减,父进程也会改变,而fork是子进程复制父进程的储存空间
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调用运行,子进程调用这两个函数中的任意一个,父进程会恢复运行.( 如果调用这两个函数之前子进程一栏父进程的进一步动作,则会导致死锁

8.5 函数exit

五种正常退出,三种异常退出
(1)在main函数内执行return语句,等效调用exit
(2)调用exit函数.其操作包括各种终止处理程序,然后关闭所有标准I/O流
(3)调用_exit或_EXIT函数.其目的是为进程提供一种无需运作终止处理程序或信号处理程序而终止的方法.
(再大多数UNIX系统实现中,exit(3)是标准C库中的一个函数,而_exit(2)则是一个系统调用)
(4)进程的最后一个线程在其启动例程中执行return语句.
(5)进程的最后一个线程调用pthread_exit函数
三种异常退出
.....

8.6 函数wait和waitpid

当一个进程正常或异常终止是,内核就向其父母进程发送SIGCHLD信号
调用wait或waitpid的进程可能会发生
如果所有子进程都在运行,则阻塞
如果一个子进程已经终止,正在等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
如果它没有任何子进程,则立即出错返回
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);

这两个函数的区别如下:
在一个子进程终止前,wait使其调用者阻塞
waitpid并不期待在其调用之后的第一个终止子进程,它有若干选项
statloc标示终止状态

如果一个进程有几个子进程,那么只要有一个子进程终止,wait就返回.如果相指定一个子进程结束,使用waitpid
pid==-1 等待任一子进程.与wait 一样
pid>0 等待进程ID与pid相等的子进程
pid == 0 等待组ID等于调用进程组的任一子进程
pid<-1 等待组ID 等于pid绝对值的任一子进程

waitpid函数提供了wait函数没有提供的3个功能
(1)waitpid可以等待特定的进程
(2)waitpid提供了一个wait的非阻塞版本.
(3)waitpid 可以通过options 的WUNTRAED和WCONTINUED选项支持作用控制

8.7 函数waited

此函数类似waitpid,但提供了更多的灵活性
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int option);

8.8 函数wait3和wait4

这两个参数还提供了 返回由终止进程以及其所有子进程使用资源的情况
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/time.h>
#include<sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int options, struct rusage *rusage);

8.9 竞争条件

当多个进程都企图对共享数据进行某些处理,而最后的结果有取决于进程运行的顺序时,我们认为发生了 竞争条件(race condition)

如果一个进程希望等待一个子进程终止,则它必须调用wait函数中的一个.如果一个进程要等该父进程终止可用下面的循环
while(getppid()!=1)
        sleep(1);
这种循环称为轮询(polling)

8.10 函数exec

用fork函数创建新的子进程后,子进程调用一种exec函数以执行另一个程序.当进程调用一种exec函数时,该进程执行的程序完全替换成新程序
有7种不同的exec函数可供使用,它们常常被统称为exec函数
用fork可以创建新进程,用exec可以初始执行新的程序.exit函数和wait函数处理终止和等待终止


在很多UNIX实现中,这7个函数中只有execve是内核的系统调用.此外6个只是库函数,它们最终都要调用该系统调用


8.11 更改用户ID和更改组ID

可用setuid函数设置实际用户ID和有效用户ID,setgid设置组
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);

8.12 解释器文件(Interpreter Files)

这种文件是文本文件,起始行的形式是
#! pathname[optional-argument]
一定要将 解释器文件(文本文件,它以#!开头)和 解释器(由该解释器文件第一行中的pathname指定)区分开来

8.13 函数system

system 可以在程序中执行一个命令字符串
system在其实现中调用了fork,exec,和waitpid
#include<stdlib.h>
int system(const char *cmdstring);

8.14 进程会计(Process Accounting)

启动该选项后,每当进程结束时内核就写一个会计记录
会计记录所需的各个数据都由内核保存再进程表中,并再一个新进程被创建时初始化
1) 我们不能获取永远不终止的进程的会记记录
2) 在会记文件中记录的顺序对应于进程终止的顺序,而不是启动的顺序.

8.15 用户标记(User Identification)

任一进程都可以得到其实际用户ID以及组ID.我们有时希望找到运行该程序用户的登录名.
用getlogin函数可以获取此登录名
#include <unistd.h>
chhar * getlogin();


8.16 进程调度(Process scheduling)

进程可以通过调整nice值选择以更低优先级运行.只有特权进程允许提高调度权限.
进程可以通过nice函数获取或更改它的nice值.
#include<unistd.h>
int nice(int incr);
incr参数增加到调用进程进程的nice值上

getpriority函数可以像nice函数那样用于获取进程的nice值,但是getpriority还可以获取一组相关进程的nice值
#include <sys/resource.h>
int getpriority(int which, id_t who);
which参数可以取以下三个值:PRIO_PROCESS表示进程,PRIO_PGRP表示进程组,PRIO_USER表示用户ID.
which参数控制who参数是如何解释的

setpriority函数看用于为进程,进程组和属于特定用户ID的所有进程设置优先级
#include <sys/resource.h>
int setpriority(int which, id_t who, int value);
value为新的nice值

8.17 进程时间

我们可以度量3个时间:墙上时钟时间(wall clock time),用户CPU时间和系统CPU时间
任一进程都可以调用times函数获取它自己以及已经终止子进程的上述值
#include <sys/times.h>
clock_t times(struct tms *buf);

Notice:此结构没有包含墙上时钟时间,因为这个作为返回值了

8.18 小结

对于UNIX环境高级编程而言,完整地了解UNIX的进程控制是非常重要的.需要熟练掌握fork,exec系列,wait和waitpid.很多进程函数都使用这些函数,fork函数也给我们一个机会了解这些竞争条件.









猜你喜欢

转载自blog.csdn.net/youngyangyang04/article/details/48037855