第八章-进程控制

一、进程标识

每个进程都有一个非负整型表示唯一的进程ID。但是进程ID时可复用的,当一个进程终止后,其进程ID就成为复用的候选者。

1.1 系统专用进程
  • 0号进程。ID为0的通常为调度进程,常被称为交换进程。该进程是内核的一部分,并不执行任何磁盘上的程序,因此也被称为系统进程。
  • 1号进程。ID为1的通常是init进程。该进程的程序文件在新版本中是/sbin/init。此进程负责在自举内核后启动一个UNIX系统。init通常读取与系统有关的初始化文件,并将系统引导到一个状态(如多用户)。init进程决不会终止。它是一个普通的用户进程(与交换进程不同),但是它以超级用户特权运行。
1.2 获取标识符的函数
#include <unistd.h>
pid_t getpid(void); //返回调用进程的进程ID
pid_t getppid(void); //返回调用进程的父进程ID
pid_t getuid(void); //返回调用进程的实际用户ID
pid_t geteuid(void); //返回调用进程的有效用户ID
pid_t getgid(void); //返回调用进程的实际组ID
pid_t getegid(void); //返回调用进程的有效组ID

二、函数fork

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

子进程返回0,父进程返回子进程ID;若出错返回-1.

  • 将子进程ID返回给父进程的理由是:一个进程可以有多个子进程,且没有一个函数使一个进程可以获得其所有子进程的进程ID。
  • 使子进程返回0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid获得其父进程的进程ID。

注:写时复制(copy-on-write)。进程区域由父进程和子进程共享,而且内核将它们的访问权限改变为只读。若父进程和子进程中的一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一“页”。

1.2 父子进程之间的文件共享

fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父进程和子进程每个相同的打开文件描述符共享同一个文件表项。
在这里插入图片描述
重要的是,父子进程共享同一个文件偏移量。如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步,那么它们的输出就会相互混合。

fork失败的两个主要原因是:(1)系统中已经有了太多进程(2)该实际用户ID的进程总数超过了系统限制。
fork有以下两种用法:(1)父进程希望复制自己,使父进程和子进程执行不同的代码段。这在网络服务进程中是常见的。(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

三、函数vfork

vfork函数用于创建一个新进程,该进程目的是exec一个新程序,如fork用法二。两者区别是:
(1)vfork并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit),于是也就是不会引用该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。
(2)vfork保证子进程先运行,在它调用exec或exit后父进程才可能被调度运行(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁)。

四、函数exit

进程的8种终止方式。
5种正常终止:
(1)在main函数中执行return语句。等效于调用exit
(2)调用exit函数。ISO C定义,其操作包括调用各类终止程序,然后关闭所有标准I/O流等。因为ISO C并不处理文件描述符、多进程以及作业控制,所以这一定义对UNIX系统是不完整的
(3)调用_Exit或_exit函数。ISO C定义_Exit, 其目的是为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。对标准I/O流是否进行冲洗,取决于实现。在UNIX中,_Exit和exit是同义的,并不冲洗标准I/O流。_exit函数由exit调用
(4)进程的最后一个线程调用return。但是该线程的返回值不作为进程返回值,最后一个线程返回时,该进程以终止状态0返回
(5)进程的最后一个线程调用pthread_exit函数。十一章进行详细说明

3种异常终止:
(1)调用abort。它产生SIGABRT信号,这是信号终止的一种特例
(2)当进程接收到某些信号时。信号可由自身(如调用abort)、其他进程或内核产生
(3)最后一个线程对“取消(cancellation)”请求做出响应。十一、十二章详细说明

无论进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对于上述任意一种情形,我们都希望进程能够通知其父进程它是如何终止的。

  • 对于三个终止函数(exit、_exit和_Exit),将其退出状态(exit status)作为参数传送给函数。
  • 对于异常终止。内核产生一个指示其异常终止原因的终止状态(termination status)。
    在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。

五、函数wait和waitpid

当一个进程终止时(正常或异常),内核就向其父进程发送SIGCHLD信号,父进程默认忽略该信号。

调用wait或waitpid的进程可能会发生什么:

  • 如果所有子进程均在运行,则阻塞
  • 如果一个子进程已终止,则获取其终止状态并立即返回
  • 如果没有子进程,则立即出错返回
#include <sys/wait.h>
pid_t wait(int* statloc);
pid_t waitpid(pid_t pid, int* statloc, int options);

成功,返回进程ID;出错则返回0或-1。两个函数的区别如下

  • 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞
  • waitpid并不等待在其调用之后的第一个终止子进程,它有若干选项,可以控制它所等待的进程

这两个函数的参数是一个整型指针。若statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。
waitpid中有一些额外参数,用于等待一个特定进程。
(1)参数pid
pid == -1 : 等待任一子进程,与wait等效
pid > 0 :等待进程ID 与pid相等的子进程
pid == 0:等待组ID等于调用进程组ID的任一子进程
pid < -1 :等待组ID等于pid绝对值的任一子进程

(2)参数options:进一步控制waitpid的操作
此参数为0或者为以下常量按位或的结果
WNOHANG:非阻塞模式,若pid子进程不可用,则返回0
WCONTINUED:
WUNTARCED:
上面两个选项与支持作业相关

六、函数waitid

类似waitpid,但提供了更多的灵活性

#include <sys/wait.h>
int waitid(idtype_t idtype, id_t id, siginfo_t* infop, int options);

成功返回0,出错返回-1。与waitpid不同,waitid使用两个单独参数表示要等待的子进程所属的类型。

(1)参数idtype
P_PID:等待特定进程
P_PGID:等待一特定进程组中的任一子进程
P_ALL:等待任一子进程

(2)参数options,为以下常量按位或的结果
WCONTINUED:等待一进程,以前曾被停止,此后又继续,但其状态尚未报告
WEXITED:等待已退出的进程
WNOHANG:如无可用的子进程退出状态,立即返回而非阻塞
WNOWAIT:不破坏子进程退出状态。该子进程退出状态仍可被wait等捕捉
WSTOPPED:等待一进程,已经停止但未报告状态

WCONTINUED, WEXITED, WSTOPPED必须在options中指定其一。

infop参数时指向siginfo结构的指针,包含造成子进程状态改变有关信号的详细信息。第十章详细讨论。

七、wait3和wait4

允许内核返回由终止进程及其所有子进程使用的资源概况。

八、函数exec

用fork创建新的子进程后,子进程往往要调用exec函数以执行另一个程序。调用exec会将该进程的执行程序完全替换为新程序,而新程序从其main函数开始执行。即exec用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
一共有7中exec函数:

#include <unistd.h>
int execl(const char* pathname, const char* arg0, ...);
int execv(const char* pathname, char *const argv[]);
int execle(const char* pathname, const char* arg0, ..);
int execve(const char* pathname, char *const argv[], char* const evnp[]);
int execlp(const char* filename, const char* arg0, ...);
int execvp(const char* filename, char* const argv[]);
int fexecve(int fd, char* const argv[], char* const envp[]);

其中pathname代表路径名,filename为文件名。当filename中包含"/",则视为路径名,否则按PATH环境变量所指路径中搜索。
记忆方法:
p:代表pathname,PATH环境变量
l:代表list,参数列表
v:与l互斥,代表argv[]
e:代表evnp[]数组,不使用当前环境
注:在exec前后实际用户组ID保持不变,而对于有效ID,若新程序的设置用户ID位已设置,则有效用户ID变成程序文件所有者的ID,否则有效ID不变。

在这里插入图片描述
只有exeve是系统调用,另外6个只是库函数,最终都要调用该系统调用。
execlp和execvp使用PATH环境变量,查找第一个包含名为filename的可执行文件的路径名前缀。fexecve库函数使用/proc把文件描述符参数转换为路径名,execve用该路径名执行程序。

九、更改用户ID和组ID

#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);

十、解释器文件

十一、函数system

很方便地在程序中执行一个命令字符串

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

system在其视线中调用了fork、exec和waitpid,因此有三种返回值
(1)fork失败或者waitpid返回除EINTR之外的错,则system返回-1,并设置errno
(2)exec失败(表示不能执行shell),则返回值等同于shell执行了exit
(3)三个函数都成功,则system的返回值是shell的终止状态

十二、进程会计

大多数UNIX系统提供了一个选项进行进程会计(process accounting)处理。启用该选项后,每当进程结束时内核就会写一个会计记录。一般包括命令名。所使用的CPU时间总量、用户ID和组ID、启动时间等。

十三、用户标识

获取运行程序的用户的登录名

#include <unistd.h>
char* getlogin(void);

十四、进程调度

调度策略和调度优先级是由内核确定的。进程可以调小nice值,nice值越小(越不友好),优先级越高。只有特权进程允许提高调度权限

#include <unistd.h>
int nice(int incr);

incr参数被增加到调用进程的nice值上。

十五、进程时间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值