第八章 进程控制

来源:互联网 发布:一般淘宝客佣金是多少 编辑:程序博客网 时间:2024/05/18 01:25

内容提要:
本章介绍unix系统的进程控制,包括创建新的进程、执行程序、进程终止。另外还介绍了进程属性的各种ID以及他们如何受到进程原语的影响。
8.2进程的标实
每一个进程都有一个非负整型表示的唯一进程ID,但是进程ID可以复用。
系统中的专用进程:
调度进程:ID为0,常被称为交换进程,该进程是内核的一部分,并不执行磁盘上的任何程序,也被称为系统进程。
init进程:ID为1,init通常读取与系统相关的初始化文件,init进程不会终止,是所有孤儿进程的父进程。
页守护进程:ID为2 ,此进程负责支持虚拟存储系统的分页操作。
进程的其他标示符:

pid_t getpid(void);  //返回值:调用进程的进程IDpid_t getppid(void); //返回值:调用进程的父进程IDuid_t getuid(void);   //返回值:调用进程的实际用户IDuid_t geteuid(void) ; //返回值:调用进程的有效用户IDgid_t getgid(void ) ; //返回值:调用进程的实际组IDgid_t getegid(void) ; // //返回值:调用进程的有效组ID 

8.3函数fork

//返回值:子进程返回0,父进程返回子进程的ID 若出错,返回-1;fork被调用一次,返回两次;
pid_t fork(void)//创建一个子进程、

子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本,例如,子进程获得父进程的数据空间、堆、栈的副本。注意,这是子进程所拥有的副本。父进程和子进程并不共享这些存储空间部分。父进程和子进程共享正文段(CPU执行的机器指令部分)。

子进程对变量所做的改变,并不影响父进程中变量的值

文件的共享
在重定向父进程的标准输出时,子进程的标准输出也被重定向,实际上fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中,父进程和子进程每个相同的打开描述符共享一个文件表项。

在fork之后处理文件描述符有一下两种情况:
1、父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾经进行过读写操作的任意共享文件描述符的文件偏移量已做出了相应的更新。
2、父进程和子进程各自执行不同的程序段。在这种情况下,在fork之后,父进程和子进程各自关闭他们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符(经常用于网络服务程序)

子进程和父进程的区别:
1、fork的返回值不同
2、进程ID不同
3、两个进程的父进程ID不同
4、子进程不继承父进程设置的文件锁
5、子进程的未处理闹钟会被清除
6、子进程的未处理信号集会被设置为空集。

8.4函数vfork

vfork函数的调用序列和返回值与fork相同,也是用于创建一个新的进程

vfork和fork函数的区别:
1、vfork不将父进程的地址空间完全复制到子进程中(因为子进程会立即调用exec,于是也就不会引用该地址空间),不过在调用exec之前,它在父进程的空间中运行,但是如果子进程修改数据、调用函数、或者没有调用exec都会带来一些未知的结果。
2、vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

8.5函数exit

1、调用exit函数,其操作包括调用各种终止程序,关闭所有标准I/O流等
2、调用_exit 或_Exit 函数其目的是为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法,对标准I/O流是否进行冲洗取决于实现。
3、如果子进程正常终止,则父进程可以获得子进程的退出状态。
孤儿进程:父进程在子进程之前终止
对于所有的孤儿进程,他们的父进程都改变为init进程

子进程完全消失,父进程在最终准备好检查子进程是否终止时无法获取它的终止状态。此时内核为每一个终止的子进程保存了一定量的信息,这些信息包括(进程ID、该进程的终止状态、该进程使用的CPU时间总量),内核可以释放终止进程使用的所有存储区,关闭其所有打开的文件。
僵死进程:一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占有的资源)的进程

8.6函数wait和waitpid

调用wait和waitpid可能发生:
1、如果其所有子进程还在运行,则阻塞。
2、如果一个子进程已终止,正等待父进程获取其终止态,则取得该子进程的终止状态立即返回。
3、如果没有任何子进程,则立即返回出错。

//若成功,返回进程ID;出错返回0或-1;pid_t wait(int  * statloc);如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针pid_t waitpid(pid_t pid, int *statloc , int options);

waitpid函数中的pid参数:
pid=-1 等待任一子进程,waitpid与wait等效
pid > 0 等待进程ID与pid相等的子进程
pid == 0 等待组ID等于调用进程组ID的任一子进程
pid < -1 等待组ID等于pid绝对值的任一子进程

options参数:
0、WCONTINUED、WNOHANG 、WUNTRACED
waitpid函数提供了wait函数没有提供的功能:
1、waitpid可以等待一个特定的进程;
2、waitpid提供了一个wait的非阻塞版本
3、waitpid通过WUNTRACED和WCONTINUED选项支持作业控制。

8.9竞争的条件

竞争的条件:当多个进程企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序,则发生了竞争条件

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

8.10函数exec

当fork函数创建了新的子进程后,子进程往往调用一种exec函数以执行另一个程序。当调用一种exec函数时,该进程执行的程序完全被新程序替换,该新程序从新程序的main函数开始执行。

调用exec并不创建新的进程,所以前后进程ID并不改变,exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段、栈段。

int execl(const char *path, const char *arg, ...);  int execlp(const char *file, const char *arg, ...);  int execle(const char *path, const char *arg,                  ..., char * const envp[]);  int execv(const char *path, char *const argv[]);  int execvp(const char *file, char *const argv[]);  int execve(const char *file, char *const argv[], char *const envp[]);  

//系统调用
int fexecve(int fd ,char const argv [], char const envp []);
参数说明:path代表可执行文件的路径名;file代表文件名(默认在环境变量表中寻找可执行文件,也可以指定具体的路径名);fd表示文件描述符; envp[ ]代表环境变量表,arg和argv[]代表要传递的命令行参数以(char*)0 和NULL结尾。

参数巧记:p代表取file文件名做参数;f表示取文件描述符;l取参数表;v表示取argv[ ]; e表示envp[ ];

8.11更改用户ID 和组ID

组ID:组ID就是创建该文件的用户ID所在的组
附加组ID:现在的操作系统中,往往一个用户不仅仅在一个组中,可以允许存在还几个组中,所以现在的操作系统中提供了附加的组ID,供文件访问检查。
详情:http://blog.csdn.net/woods2001/article/details/4753718

在unix系统中,特权(更改当前的日期表示法)以及访问控制(能否读写一个特定的文件)是基于用户ID和组ID的。当程序需要增加权限允许访问资源或者降低特权阻止对某些资源的访问时,需要更换用户ID和组ID。

设置实际用户ID和有效用户ID

成功返回0,出错返回-1int setuid(uid_t uid);//设置实际用户ID和有效用户IDint setgid(uid_t uid);//设置实际用户I组D和有效用户组ID

更改用户ID的规则:
1、若进程拥有超级用户权限,则setuid函数将实际用户ID、有效用户ID以及保存的设置用户ID设置为uid

2、若进程没有超级用户权限,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid。不更改实际用户ID和保存的设置用户ID。

3、如果上面条件都不满足,则errno设置为EPERM,并返回-1;
思考:有效用户ID、实际用户ID、保存设置用户ID的区别?
答案:http://www.2cto.com/os/201304/203706.html

关于内核维护的3个用户ID?
1、只有超级用户进程可以更改实际用户ID。通常实际用户ID是在用户登录时,由login程序设置,而且不会不会改变。只有超级用户才有权改变。
2、仅当对程序文件设置了用户ID位时,exec函数才设置有效用户ID,任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置用户ID。
3、保存的设置用户ID是由exec复制有效用户ID得到。

注意:getuid和geteuid函数只能获得实际用户ID和有效用户ID的当前值。我们没有可移植的方法获得保存的设置用户ID的当前值。

//成功返回0,出错返回-1
功能:交换实际用户ID和有效用户ID的值。
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

8.12解释器文件

解释器文件,这种文件是文本文件,起始行的形式是:
# ! pathname
! 和pathname之间的空格是可选的,pathname通常是绝对路径名,对于这种文件的识别是由内核作为exec系统调用处理的一部分来完成的。
内核使用调用exec函数的进程实际执行的并不是该解释器文件,而是在该解释器文件第一行中pathname所指定的文件。而且该解释器文件一定要具有可执行的权限。
图片

解释器问件和解释器的区别:
解释器文件:文本文件,它以# !开头。
解释器:有该解释器文件第一行中的pathname指定。

8.13函数system

int system(const char *cmdstring)
system函数在其中实现中调用fork、execl、waitpid 函数;使用system函数而不是直接使用fork、execl、waitpid 函数的有点是:
system进行所需的各种出错和信号处理。

设置用户ID或者设置组ID程序决不能调用system函数:这样会把有效用户ID设置为0(root用户),这种程序就可以访问root才能访问的资源。

8.14进程会计

1、每当进程结束时内核就会写一个会计记录,该会计记录一般包括命令名、所使用的CPU时间总量、用户ID、组ID、启动时间等。
2、会计记录所需要的各个数据都会由内核保存在进程表中,并在一个新的进程被创建时初始化,进程终止时写一个会计记录。这将导致两个结果:
a、我们不能获取永远不终止的进程的会计记录。如init进程和内核守护进程,
b、在会计文件中记录的顺序对应于进程终止的顺序,而不是它启动的顺序。
会计记录对应于进程而不是程序,在fork之后,内核会为子进程初始化一个记录,而不再一个新的程序执行时初始化,如调用exec函数并不会创建一个新的会计记录,但是会改变相应记录中的命令名。

char * getlogin(void);//获取用户登录时使用的名字
如果调用此函数的进程没有连接到用户登录时所使用的终端,则函数会失败,返回NULL;通常这类进程称为守护进程。

8.16进程的调度

unix系统中,调度的策略和调度的优先级是由内核确定,进程可以通过调整nice值选择以更低优先级运行,只有特权进程允许提高调度权限。
函数
//成功返回新的nice值,出错返回-1
int nice(int incr)//通过nice函数获取或者更改它的nice值
incr参数被增加到调用进程的nice值上

//成功返回nice值,若出错返回-1
getpriority(int which, id_t who)//获取进程的nice值或者一组相关进程的nice值
which可以选择三个函数:PRIO_PROCESS表示进程 PRIO_PGRP表示进程组, PRIO_USER表示用户ID

//成功返回0,出错返回-1;int setpriority(int which, id_t who, int value); //可用于为进程、进程组、属于特定用户ID的所有进程设置优先级。value增加到NZERO上变为新的nice值。

8.17进程时间

3个时间:墙上时钟时间、用户CPU时间、系统CPU时间,任何进程可以通过调用times函数获得它自己以及终止子进程的3个时间值。

//成功返回流逝的墙上时钟时间(以时钟的滴答数为单位),出错返回-1;clock_t times(struct tms * buf);

:所有由此函数返回的clock_t 值都用_SC_CLK_TCK(由sysconf函数返回每秒时钟的滴答数)转换成秒数。

时钟时间(墙上时钟时间wall clock time):从进程从开始运行到结束,时钟走过的时间,这其中包含了进程在阻塞和等待状态的时间。
用户CPU时间:就是用户的进程获得了CPU资源以后,在用户态执行的时间。
系统CPU时间:用户进程获得了CPU资源以后,在内核态的执行时间。

进程的三种状态为阻塞、就绪、运行。

时钟时间 = 阻塞时间 + 就绪时间 +运行时间
用户CPU时间 = 运行状态下用户空间的时间
系统CPU时间 = 运行状态下系统空间的时间。

用户CPU时间+系统CPU时间=运行时间。

来源: http://blog.chinaunix.net/uid-27629574-id-3880991.html

0 0