apue:进程控制

    xiaoxiao2021-03-25  8

        UNIX的进程控制,包括创建新进程、执行程序和进程终止;进程的各种ID—实际、有效和保存的用户和组ID,以及它们如何受到进程控制原语的影响;解释器文件和system函数。

    进程标示

        每个进程都有一个非负整型的唯一进程ID。因为进程ID标识符总是唯一的,常将其用做其他标识符的一部分以保证其唯一性。有某些专用的进程:进程ID0是调度进程,常常被称为交换进程(swapper)。该进程并不执行任何磁盘上的程序—它是内核的一部分,因此也被称为系统进程。进程ID1通常是init进程,在自举过程结束时由内核调用。此进程负责在内核自举后起动一个UNIX系统。init通常读与系统有关的初始化文件(/etc/rc*文件),并将系统引导到一个状态。init进程决不会终止。它是一个普通的用户进程,但是它以超级用户特权运行。在某些UNIX的虚存实现中,进程ID2是页精灵进程(page daemon)。此进程负责支持虚存系统的请页操作。与交换进程一样,页精灵进程也是内核进程。     下列函数返回这些标识符。

    #include <sys/types.h> #include <unistd.h> 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内核创建一个新进程的唯一方法

    #include <sys/types.h> #include <unistd.h> pid_t fork(void); /*返回:子进程中为0,父进程中为子进程ID,出错为-1*/

        由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。     一个进程打开了三个不同文件,它们是:标准输入、标准输出和标准出错。在从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(或exit),于是也就不会存访该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。

    wait和waitpid函数

        当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件,所以这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数。对于这种信号的系统默认动作是忽略它。     调用wait或waitpid的进程可能会: - 阻塞(如果其所有子进程都还在运行)。 - 待子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态)。出错立即返回(如果它没有任何子进程)。 - 如果进程由于接收到SIGCHLD信号而调用wait,则可期望wait会立即返回。但是如果在一个任一时刻调用wait,则进程可能会阻塞。

    #include <sys/types.h> #include <sys/wait.h> pid_t wait(int* statloc); pid_t waitpid(pid_t pid, int* statloc, int options); /*两个函数返回:若成功则为进程ID,若出错则为-1*/

        这两个函数的区别是: - 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选择项,可使调用者不阻塞。 - waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的进程。     如果一个子进程已经终止,是一个僵死进程,则wait立即返回并取得该子进程的状态,否则wait使其调用者阻塞直到一个子进程终止。如调用者阻塞而且它有多个子进程,则在其一个子进程终止时,wait就立即返回。这两个函数的参数statloc是一个整型指针。如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。     对于waitpid的pid参数的解释与其值有关: - pid==-1等待任一子进程。于是在这一功能方面waitpid与wait等效。 - pid>0等待其进程ID与pid相等的子进程。 - pid==0等待其组ID等于调用进程的组ID的任一子进程。 - pid<-1等待其组ID等于pid的绝对值的任一子进程。     waitpid函数提供了wait函数没有提供的三个功能: - waitpid等待一个特定的进程(而wait则返回任一终止子进程的状态)。 - waitpid提供了一个wait的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。 - waitpid支持作业控制。

    wait3和wait4函数

        这两个函数提供的功能比函数wait和waitpid所提供的分别要多一个,这与附加参数rusage有关。该参数要求内核返回由终止进程及其所有子进程使用的资源摘要。

    #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); /*两个函数返回:若成功则为进程ID,若出错则为-1*/

        资源信息包括用户CPU时间总量、系统CPU时间总量、缺页次数、接收到信号的次数等。这些资源信息只包括终止子进程,并不包括处于停止状态的子进程。

    竞态条件

        当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,认为这发生了竞态条件(race condition)。如果在fork之后的某种逻辑显式或隐式地依赖于在fork之后是父进程先运行还是子进程先运行,那么fork函数就会是竞态条件活跃的孳生地。

    exec函数

        有六种不同的exec函数可供使用,它们常常被统称为exec函数。这些exec函数都是UNIX进程控制原语。用fork可以创建新进程,用exec可以执行新的程序。exit函数和两个wait函数处理终止和等待终止。这些是基本的进程控制原语。

    #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[]); /*六个函数返回:若出错则为-1,若成功则不返回*/

        这些函数之间的第一个区别是前四个取路径名作为参数,后两个则取文件名作为参数。当指定filename作为参数时: - 如果filename中包含/,则就将其视为路径名。 - 否则就按PATH环境变量,在有关目录中搜寻可执行文件。     PATH变量包含了一张目录表(称为路径前缀),目录之间用冒号(:)分隔。     如果execlp和execvp中的任意一个使用路径前缀中的一个找到了一个可执行文件,但是该文件不是由连接编辑程序产生的机器可执行代码文件,则就认为该文件是一个shell脚本,于是试着调用/bin/sh,并以该filename作为shell的输入。     第二个区别与参数表的传递有关(l表示表(list),v表示矢量(vector))。函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。对于另外三个函数(execv,execvp和execve),则应先构造一个指向各参数的指针数组,然后将该数组地址作为这三个函数的参数。     最后一个区别与向新程序传递环境表相关。以e结尾的两个函数(execle和execve)可以传递一个指向环境字符串指针数组的指针。其他四个函数则使用调用进程中的environ变量为新程序复制现存的环境。     在执行exec后,进程ID没有改变。除此之外,执行新程序的进程还保持了原进程的下列特征: - 进程ID和父进程ID。 - 实际用户ID和实际组ID。 - 添加组ID。 - 进程组ID。 - 对话期ID。 - 控制终端。 - 闹钟尚余留的时间。 - 当前工作目录。 - 根目录。 - 文件方式创建屏蔽字。 - 文件锁。 - 进程信号屏蔽。 - 未决信号。 - 资源限制。 - tms_utime,tms_stime,tms_cutime以及tms_ustime值。     在很多UNIX实现中,这六个函数中只有一个execve是内核的系统调用。另外五个只是库函数,它们最终都要调用系统调用。这六个函数之间的关系如下图:

    更改用户ID和组ID

        可以用setuid函数设置实际用户ID和有效用户ID。与此类似,可以用setgid函数设置实际组ID和有效组ID。

    #include <sys/types.h> #include <unistd.h> int setuid(uid_t uid); int setgid(gid_t gid); /*两个函数返回:若成功则为0,若出错则为-1*/

        有关改变用户ID的规则: - 若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID,以及保存的设置-用户-ID设置为uid。 - 若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置-用户-ID,则setuid只将有效用户ID设置为uid。不改变实际用户ID和保存的设置-用户-ID。 - 如果上面两个条件都不满足,则errno设置为EPERM,并返回出错。     关于内核所维护的三个用户ID,还要注意下列几点: - 只有超级用户进程可以更改实际用户ID。通常,实际用户ID是在用户登录时,由login程序设置的,而且决不会改变它。因为login是一个超级用户进程,当它调用setuid时,设置所有三个用户ID。 - 仅当对程序文件设置了设置-用户-ID位时,exec函数设置有效用户ID。如果设置-用户-ID位没有设置,则exec函数不会改变有效用户ID,而将其维持为原先值。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置-用户-ID。自然,不能将有效用户ID设置为任一随机值。 - 保存的设置-用户-ID是由exec从有效用户ID复制的。在exec按文件用户ID设置了有效用户ID后,即进行这种复制,并将此副本保存起来。

    setreuid和setregid函数

        setregid函数功能是交换实际用户ID和有效用户ID的值。

    #include <sys/types.h> #include <unistd.h> int setreuid(uid_t ruid, uid_t euid); int setregid(gid_t rgid, gid_t egid); /*两个函数返回:若成功则为0,若出错则为-1*/

        一个非特权用户总能交换实际用户ID和有效用户ID。这就允许一个设置-用户-ID程序转换成只具有用户的普通许可权,以后又可再次转换回设置-用户-ID所得到的额外许可权。

    seteuid和setegid函数

        两个函数seteuid和setegid,它们只更改有效用户ID和有效组ID。

    #include <sys/types.h> #include <unistd.h> int seteuid(uid_t uid); int setegid(gid_t gid); /*两个函数返回:若成功则为0,若出错则为-1*/

        一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存的设置-用户-ID。对于一个特权用户则可将有效用户ID设置为uid。

    组ID

        添加组ID不受setgid函数的影响。设置不同用户ID的函数如下:

    解释器文件

        这种文件是文本文件,其起始行的形式是:

    #!pathname[optional-argument]

        在惊叹号和pathname之间的空格是可任选的。     pathname通常是个绝对路径名,对它不进行什么特殊的处理(不使用PATH进行路径搜索)。对这种文件的识别是由内核作为exec系统调用处理的一部分来完成的。内核使调用exec函数的进程实际执行的文件并不是该解释器文件,而是在该解释器文件的第一行中pathname所指定的文件。

    system函数

        ANSIC定义了system函数,但是其操作对系统的依赖性很强。

    #include <stdlib.h> int system(const char* cmdstring);

        如果cmdstring是一个空指针,则仅当命令处理程序可用时,system返回非0值,这一特征可以决定在一个给定的操作系统上是否支持system函数。在UNIX中,system总是可用的。因为system在其实现中调用了fork、exec和waitpid,因此有三种返回值: - 如果fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,而且errno中设置了错误类型。 - 如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样。 - 否则所有三个函数(fork,exec和waitpid)都成功,并且system的返回值是shell的终止状态,其格式已在waitpid中说明。

    用户标示

        任一进程都可以得到其实际和有效用户ID及组ID。但是有时希望找到运行该程序的用户的登录名。如果一个用户有多个登录名,这些登录名又对应着同一个用户ID,用getlogin函数可以存取此登录名。

    #include <unistd.h> char* getlogin(void); /*返回:若成功则为指向登录名字符串的指针,若出错则为NULL*/

        如果调用此函数的进程没有连接到用户登录时所用的终端,则本函数会失败。通常称这些进程为精灵进程(daemon)。

    进程时间

        任一进程都可调用times函数以获得它自己及终止子进程的墙上时钟时间、用户CPU时间和系统CPU时间。

    #include <sys/times.h> clock_t times(struct tms* buf); /*返回:若成功则为经过的墙上时钟时间(单位:滴答),若出错则为-1*/

        此函数填写由buf指向的tms结构,该结构定义如下:

    struct tms { clock_t tms_utime; /*user CPU time*/ clock_t tms_stime; /*system CPU time*/ clock_t tms_cutime; /*user CPU time, teminated children*/ clock_t tms_cstime; /*system CPU time, terminated children*/ };

        此结构没有包含墙上时钟时间。作为代替,times函数返回墙上时钟时间作为函数值。

    转载请注明原文地址: https://ju.6miu.com/read-155465.html

    最新回复(0)