第八章 进程控制
引言:
如何创建进程?执行程序?进程终止?
讲述进程属性的各种ID---实际、有效、保存的用户和ID,他们是如何受到进程控制原语的影响。
解释器文件和system函数,进程会计机制。
进程标识符
每一个进程都有一个非负整数表示的唯一进程ID,这个进程ID是唯一的,他的使用机制和文件描述符是不相同的。文件打开的都是最小的整数(未打开),进程ID是采用延迟重用算法。
init 0表示关机 init 6表示重新启动
ID位0的是一个交换进程,该进程是内核的一部分。
ID为1的是init进程
pid_t getpid() ;
pid_t getppid();
uid_t getuid();
uid_t geteuid();
gid_t getgid();
gid_t getegid();
fork
创建进程fork,返回两次,子进程返回0,父进程返回子进程的ID,
pid_t fork(); //出错返回-1
fork的一个特性是父进程的所有打开的文件描述符都被复制到子进程中,父子进程的每一个相同的打开文件描述符共享一个文件表项。重定向父进程对的标准输出的时候,子进程的标准输出也重定向。
子进程会复制父进程的数据空间。
子进程会继承父进程的
实际用户ID、有效用户ID、实际组ID、有效组ID
附加组ID
会话ID
控制终端
设置用户ID标志和设置组DI标志
当前工作目录、根目录
文件模式的创建屏蔽字
子进程不会继承父进程的
进程ID
父进程设置的文件锁
vfork
pid_t vfork()
在调用exec或者exit之前,子进程在父进程的地址空间运行,子进程修改的变量会修改父进程的值。还有一个特点是vfork保障子进程先运行,在他调用exec或者exit之后父进程才可能被调度运行。
exit函数
五种进程正常终止
1 main函数中return返回
2 调用exit()
3 调用_Exit()、_exit()
4 最后一个线程返回
5 最后一个线程调用phread_exit()
异常终止:
1 调用abort
2 进程收到某些信号,使进程退出
3 最后一个线程对取消做出反应 pthread_cancel()
不管进程如何终止,都会关闭文件描述符和释放他使用的存储器
退出状态:是return value、exit(value) 中的value
终止状态:是wait(int &value)中的value
关于父子进程谁先终止的问题
如果父进程先终止,那么子进程的父进程就变成init(pid是1)的进程,子进程终止的时候,init为它收尸。不会僵死占用空间。
如果子进程先终止,那么父进程必须为子进程收尸(wait),不然子进程就会一直占用空间,导致出现僵死进程。
子进程终止的时候,会向父进程发送SIGCHLD信号。这个时候可以对子进程进行处理。
wait和waitpid可能的情况
如果子进程正在运行,则阻塞
如果没有子进程就直接出错返回
如果子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态时立即返回。
pid_t wait(int &status) pid_t waitpid(pid_t pid, int *status, intoptions)
如果pid == 0 等待其组ID等于调用进程组ID的任意子进程
pid== -1 等待任意子进程
pid》0 等待pid位pid的子进程
pid《0 等待pid为|pid|的子进程。
exec*函数
#include <unistd.h> 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 *constargv[]); int execvp(const char *file, char *constargv[]); int execve(const char *path, char *constargv[], char *const envp[]);
最终调用的是execve,其他都是在这个函数的基础上进行封装的。
更改用户ID和组ID
int setuid(uid_t uid) int setgid(gid_t gid)
如果进程具有超级用户权限,那么设置成功。
如果进程不具有超级用户权限,并且uid等于实际用户ID或者设置用户ID,那么仅仅能把有效用户ID设置为实际用户ID
如果上面都不满足就直接返回-1.
以上规则也适用于组ID
man命令切换的原理
man程序可能必须执行几个其它命令来处理包含要显示的手册页的文件。为了避免被欺骗以致运行错误的命令或覆写错误的文件,man命令必须在两个权限集里交换:运行man命令的用户和拥有man可执行文件的用户。下面的步骤会发生:
1、假设man程序文件被用户名man拥有并设置了设置用户ID,当我们exec它时,我们有:真实用户ID=我们的用户ID;有效用户ID=man;保存的设置用户ID=man。
2、man程序访问所需的配置文件和手册页。这些文件被用户名man拥有,但是因为有效用户ID是man,所以文件访问被允许。
3、在man为我们执行任何一个程序时,它调用setuid(getuid())。因为我们不是超级用户进程,所以这只改变有效用户ID。我们有:真实用户ID=我们的用户ID(不变);有效用户ID=我们的用户ID;保存的设置用户ID=man(不变)。现在man进程用我们的用户ID作为它的有效用户 ID运行。这意味着我们只能访问我们有普通权限的文件。我们没有更多的权限。它可以为我们执行任何过滤器。
4、当过滤完成时,man调用setuid(euid),这里euid是用户名man的数值用户ID。(这是我们为什么需要保存的设置用户ID。)现在我们有:真实用户ID=我们的用户ID(不变);有效有用ID=man;保存的设置用户ID=man(不变)。
5、man程序现在可以在它的文件上进行操作,因为它的用效用户ID是man。
解释器文件、
system函数、
system调用fork execl waitpid这些函数其中fork、waitpid出错返回-1 execl出错返回127
正常返回是执行shell的终止状态
进程会计
调用accton + filename 文件名称
就会把进程执行完毕的一些信息(使用的CPU、用户ID和组ID、启动时间)记录到文件里面
下面这个程序就是解析这个文件的。程序是固定的
/* *===================================================================================== * * Filename: accton.cpp * * Description: accton * * Version: 1.0 * Created: 2012年05月04日 21时13分45秒 * Revision: none * Compiler: gcc * * Author: LeoK, * Organization: * *===================================================================================== */ #include <stdlib.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/acct.h> #include <unistd.h> #include <errno.h> //#include "errorout.h" #define err_sys perror #define err_quit perror #ifdef HAS_SA_STAT #define FMT "%-*.*s e = %6ld, chars =%7ld, stat = %3u; %c %c %c\n" #else #define FMT "%-*.*s e = %6ld, chars =%7ld, %c %c %c\n" #endif #ifndef HAS_ACORE #define ARORE 0 #endif #ifndef HAS_AXSIG #define AXSIG 0 #endif static unsigned long compt2ulong(comp_tcomptime) { unsignedlong val; int exp; val= comptime & 0x1fff; //13-bit fraction exp= (comptime >> 13) & 7; //3 -bit exponent (0-7) while(exp-- >0) val*=8; returnval; } int main(int argc,char *argv[]){ structacct acdata; FILE *fp; if(argc!= 2) err_quit("usage:pracct filename"); if((fp= fopen(argv[1],"r")) == NULL) err_sys("can'topen accton " ); while(fread(&acdata,sizeof(acdata), 1,fp) ==1){ printf(FMT,(int)sizeof(acdata.ac_comm) ,(int)sizeof(acdata.ac_comm), acdata.ac_comm ,compt2ulong(acdata.ac_etime), compt2ulong(acdata.ac_io) #ifdef HAS_SA_STAT ,(unsignedchar) acdata,ac_stat #endif ,acdata.ac_flag& ACORE ? 'D' : ' ' ,acdata.ac_flag& AXSIG ? 'X' : ' ' ,acdata.ac_flag& AFORK ? 'F' : ' ' ,acdata.ac_flag& ASU ? 'S' : ' ' ); } if(ferror(fp)) err_sys("readerror"); exit(0); }
调用execl不会增加进程会计的个数