UNIX环境高级编程 进程控制

 

 

相关函数列表

//下列函数返回一个进程的标识符
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);

//创建新进程
#include <unistd.h>
pid_t fork(void);
pid_t vfork(void);


//当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号,wait或waitpid的进程会
//1.如果其所有子进程都还在运行则阻塞
//2.如果一个子进程已停止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
//3.如果它没有任何子进程,则立即出错返回
//两个函数的区别
//1.在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞
//2.waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);

//Single UNIX Specification包括了另一个取得进程终止状态的函数--waitid,此函数类似于waitpid
#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

//大多数UNIX系统实现提供了另外两个函数,这两个函数是从UNIX系统BSD分支延袭下来的
#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 *statloc, int options, struct rusage *rusage)


//fork函数创建新进程,用exec可以初始执行新的程序,exit函数和wait函数处理终止和等待终止
//函数exec系列,一共有7个,这些函数的区别
//1.前4个函数是用路径做参数,后两个用文件名,最后一个用文件描述符
//2.与参数传递有关,(l表示list,v表示矢量vector),函数execl,execlp和execle要求将新程序的
//  每个命令参数都说明一个单独的参数。另外四个函数(execv,execvp,execve,fexecve)则应先
//  构造一个指向各参数的指针数组,然后将数组作为参数
//3.与新程序传递环境表相关,以e结尾的三个函数(execle,execve和fexecve)可以传递一个指向环境
//  字符串指针数组。其他4个函数则使用调用进程中的environ变量为新程序复制现有的环境
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char*const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[];
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(const char *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);


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

//历史上BSD支持下列函数,其功能是交换实际用户ID和有效用户ID的值
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

//POIX.1包含的两个函数类似于setuid和setgid,但只更改有效用户ID和有效组ID
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);


//下面函数用来在程序中执行一个命令字符串,因为system实际调用了fork,exec和waitpid,因此有
//三种返回值
//1.for失败或者waitpid返回除EINTR之外的出错,则system返回-1,并且设置errno以指示错误类型
//2.如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样
//3.柔则所有3个函数(fork,exec和waitpd)都成功,那么system的返回值是shell的终止状态
#include <stdlib.h>
int system(const char *cmdstring);


//进程会计,每个系统实现不同,但是基本结构如下
#include <sys/acct.h>
typedef u_short comp_t;
struct acct {
    char ac_flag;      //flag
    char ac_stat;      //termination status
    uid_t ac_uid;      //real user ID
    gid_t ac_gid;      //read group ID
    dev_t ac_tty;      //controlling terminal
    time_t ac_btime;   //starting calendar time
    comp_t ac_utime;   //user CPU time
    comp_t ac_stime;   //system CPU time
    comp_t ac_etime;   //elapsed time
    comp_t ac_mem;     //average memory usage
    comp_t ac_io;      //bytes transferred(read and write)
    comp_t ac_rw;      //blocks on BSD system
    char ac_comm[8];   //command name
};


//一个用户可能有多个登陆名,系统会记录用户登陆时的名字,下面函数可以获取
#include <unistd.h>
char *getlogin(void);

//可以用nic函数获取和更改它的nic值,这个函数只能影响到自己的nice值,不能影响任何其他进程的
//nice值
#include <unistd.h>
int nice(int incr);

//下面函数可以像nice一样获得进程的nice值,而且还可以获得一组相关进程的nice值
//which值可以取以下三个值之一:
//1.PRIO_PROCESS表示进程
//2.PRIO_PGRP表示进程组
//3.PRIO_USER表示用户ID
#include <sys/resource.h>
int getpriority(int which, id_t who);

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

//任何一个进程都可以调用下面函数获得它自己的系统CPU时间,用户CPU时间,以及子进程的系统CPU
//时间,子进程的用户CPU时间
#include <sys/times.h>
clock_t times(struct tms *buf);
//tms结构体如下
struct tms {
    clock_t tms_utimes;      //user cpu time
    clock_t tms_stime;       //system cpu time
    clock_t tms_cutime;      //user cpu time,terminated children
    clock_t tms_cstime;      //system cpu time,terminated children
};

 

 

fork函数

其创建新进程为子进程,fork函数被调用一次,但是返回两次。

子进程返回的是0

父进程返回的是子进程的进程ID

子进程和父进程继续执行fork之后的指令,子进程是父进程的副本,列如子进程获得父进程数据空间,堆和栈 

的副本(注意是子进程所拥有的副本)。父进程和子进程并不共享这些存储空间部分,父进程和子进程共享正文段。

返回子进程ID的原因是一个进程可以有多个子进程,但是没有函数可以获得所有子进程的ID,而父进程只有一个可以通过函数getppid()获得

fork之后是父进程先执行还是子进程先执行是不确定的,取决于操作系统的调度,如果要求父进程和子进程之间相互同步,则要求某种形式的进程间通信。

父进程和子进程共享相同的文件描述符

 

fork之后父进程和子进程之间对打开文件的共享

UNIX环境高级编程 进程控制_第1张图片
 

 

在fork之后处理文件描述符有以下两种情况

1)父进程等待子进程完成,在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读,写操作的任一共享描述符的文件偏移量已做了相应的更新

2)父进程和子进程各自执行不同的程序段,这种情况下,在fork之后,父进程和子进程各自关闭他们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符,这种方法是网络服务器进程经常使用的

 

fork有以下两种用法

1)一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段,这在网络服务进程中是常见的--父进程等待客户度端的服务请求,当请求到来时父进程调用fork使子进程处理此请求,父进程继续等待下一个请求

2)一个进程要执行一个不同的程序,这对shell是常见的,在这种情况下,子进程从fork返回后立即调用exec

 

fork和vfork的区别

1)子进程并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec,这种优提高了效率

2)vfork保证子进程优先运行,在它调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。

 

 

进程有5种正常终止及3种异常终止方式

1)在main函数内执行return语句

2)调用exit函数

3)调用_exit或_Exit函数

4)进程的最后一个线程在其启动例程中执行return语句

5)进程的最后一个线程调用pthread_exit函数

 

3种异常终止具体如下

1)调用abort,产生SIGABRT信号,这是下一种异常终止的一种特列

2)当进程接收到某些信号时,信号可由进程自身(如调用abort函数),其他进程或内核产生

3)最后一个线程对"取消"(cancellation)请求作出响应

所有子进程退出后,将终止状态返回给父进程,如果父进程已经退出,则将父进程改为init进程

一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放他仍占用的资源)的

进程被称为 僵死进程(zombie)

 

 

检查wait和waitpid锁返回的终止状态的宏

说明
 WIFEXITED(status)

若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(

status),获取子进程传给exit或_exit参数的低8位

WIFSIGNALED(status)

若为异常终止进程返回的状态,则为真(接到一个不捕捉的信号),对于这种情况,

可执行WTERMSIG(status),获取使子进程终止的信号编号。另外,有些实现

(非Single UNIX Specification)定义宏WCOREDUMP(status),若已产生终止进程

的core文件,则它返回真

WIFSTOPPED(status)

若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行

WSTOPSIG(status),获取使子进程暂停的信号编号

WIFCONTINUED(status)

若在作业控制暂停后已继续的子进程返回了状态,则为真(POSIX.1的XSI扩展,

仅用于waitpid)

 

waitpid的options常量

常量 说明
WCONTINUED

若是先支持作业控制,那么由id指定的任一子进程在停止后已经继续,但其状态尚未

报告,则返回其状态(POSIX.1的XSI扩展)

WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
WUNTRACED

若某是先支持作业控制,而由pid指定的任一子进程已处于停止状态,并且其状态自

停止以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应用与一个

停止的子进程

 

竞争条件

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,我们认为发生了

竞争条件(race condition)

 

7个exec函数之间的关系

函数 pathname filename fd 参数表 argv[] environ envp[]
execl        
execlp        
execle          
execv        
execvp        
execve          
fexecve        
名字中的字母   p f l v   e

  

UNIX环境高级编程 进程控制_第2张图片
 

 

 

 

解释器文件

现在所有的UNIX系统都支持解释器文件(interpreter file),它是文本文件

//形式是
#! pathname [optional-argument]

//比如
#! /bin/sh

pathname通常是绝对路径名,对它不进行什么特殊的处理(不适用PATH进行路径搜索)对这种文件的识别是由

内核作为exec系统调用处理的一部分来完成的。内核调用exec函数的进程世纪之星的并不是该解释器文件,而

是在该解释器文件第一行中 pathname所指定的文件。一定要将解释器文件(文本文件,它以 #! 开头)和解释器

(由该解释器我呢间第一行中的pathname指定)区分开来

比如一段程序调用一个解释器

cat /home/sar/bin/testinterp
#! /home/sar/bin/echoarg foo

//这里echoarg是一段程序,echoarg又exec了testinterp并传入了一些参数
//执行结果
//第一个打印的是解释器的pathname,然后是参数
//接着是解释器执行后调用的命令和命令参数
./a.out  
argv[0]: /home/sar/bin/echoarg
argv[1]: foo
argv[2]: /home/sar/bin/testinterp
argv[3]: myarg1
argv[4]: MY ARG2

  

 

 

进程会计

大多数UNIX系统提供了一个选项以进程会计(process accounting)处理。启动该选项后,每当进程结束时内核

就会写一个会计记录。典型的会计记录包含总量较小的二进制数据,一般包括命令名,所使用的CPU时间

总量,用户ID和组ID,启动时间等

函数acct启动和禁用进程会计,唯一使用这个函数的命是accton。root执行一个带路径名参数的通常是

/var/account/acct。  在linux中该文件是/var/account/pacct

会计记录结果定义在<sys/acct.h>中

 

进程会计,结构体,会计记录所需的各个数据 (CPU时间,传递的字符数等)都由内核保存在进程表中,并在一个新进程被创建时初始化(如在fork之后在子进程中)。进程终止时写一个会计记录

1.我们不能获取永不终止的进程会计记录,如init进程

2.会计文件记录的顺序对应于进程终止的顺序,而不是他们的启动顺序,为了确定启动顺序需要读全部的会计文件,并按照启动日历时间排序,但这样并不能保证完全精确

 

会计记录中的ac_flag值

ac_flag 说明
AFORK 进程是由fork产生的,但从未调用exec
ASU 进程使用超级用户特权
ACCORE 进程转存core
AXSIG 进程由一个信号杀死
AEXPND 扩展的会计条目
ANVER 新格式记录

 

 

 

参考

实际用户ID,有效用户ID及设置用户ID

什么是实际用户ID、有效用户ID和设置用户ID

一个fork的谜题

 

 

你可能感兴趣的:(unix)