APUE(第八章)进程控制

进程标识

  • 每个进程都有一个非负整型表示的唯一ID。

    • 由于进程ID总是唯一的,可将其用作其他标识符保证唯一性,比如用进程ID作为名字的一部分创建一个唯一的文件名
  • 进程ID是唯一的但是可以复用。当一个进程终止后,其进程ID就成为复用的候选者大多数系统都有延时算法,使得赋予新建进程得ID不同于最近终止进程所使用得ID。防止将新进程误认为使用同一ID的某个已终止进程

  • 系统中有一些专用进程

    • ID为0的进程称为调度进程,或者叫交换进程,该进程是内核的一部分,并不执行磁盘上的程序,也被称为系统进程

    • ID为1的通常是init进程,在自举计算机科学导论:第七章 操作系统_什么是 操作系统 自举?_)过程结束后由内核调用,该进程早期是/etc/init,较新的版本是/sbin/init,此进程负责在内核自举后启动一个unix系统

#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

创建一个新的进程

#include <unistd.h>
pid_t fork(void);//返回值:子进程返回0,父进程返回子进程ID 若出错-1
  • 由fork创建的新进程被称为子进程

  • fork被函数调用一次,但返回两次

  • 子进程返回0

  • 父进程返回子进程ID

  • 子进程是父进程的副本,并不共享存储空间部分

  • 父进程与子进程共享正文段

  • 读时共享,写时复制

文件共享

  • 父进程重定向标准输出时,子进程的输出也被重定向
  • 父进程的所有打开文件描述符都被复制到子进程里
  • 父子进程与子进程每个相同的打开描述符共享一个文件表项
  • 父子进程共享同一个文件偏移量

fork处理后两种常见情况

子进程继承父进程的东西

  • 实际用户ID,实际组ID,有效用户ID,有效组ID
  • 附属组ID
  • 进程组ID
  • 会话ID
  • 控制终端
  • 设置用户ID标志和设计组ID标志
  • 当前工作目录
  • 根目录
  • 文件模式和创建屏蔽字
  • 信号屏蔽和安排
  • 对任一打开文件描述符关闭标志
  • 环境
  • 连接共享存储段
  • 存储影像
  • 资源限制

父子进程区别如下

  • fork返回值不同
  • 进程ID不同
  • 这两个进程父进程ID不同:子进程的父进程ID是创建它的进程ID,而父进程的父进程ID不变
  • 子进程tms_utime、tms_stime、tms_cutime和tms_ustime的值设置为0、
  • 子进程不继承父进程的文件锁
  • 子进程的未处理闹钟被清除
  • 子进程未处理信号集设为空集

fork失败原因

  • 系统已经有太多进程
  • 该进程实际用户ID进程总数超过了系统限制
  • CHILD_MAX规定,每个实际用户可拥有的最大进程数

fork有以下两种用法

  • 父进程希望复制自己父进程和子进程执行不同的代码段。例如,网络服务父进程等待客户端的服务请求,请求到达时,父进程调用fork使子进程处理此请求,父进程继续等待请求
  • 一个进程要执行不同的程序。通常方法是调用exec函数族,这种方法称为spawn

函数vfork

vfork函数的调用序列和fork相同,但二者语义不同

函数exit

详细介绍了进程终止

孤儿进程

  • 如果父进程在子进程之前终止,该父进程的所有子进程的父进程都改变为init(ID 为1)进程,我们称这些进程有init收养,具体过程如下
    • 子进程结束时也会由init进程完成对它的状态收集工作

僵死进程

简单来说,父进程未结束,子进程已结束,但父进程未给子进程收尸

函数wait和waitpid

  • 子进程在终止时内核向其父进程发送SIGHLD信号
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc,int options);
													//两个函数返回值,成功返回ID,出错返回0

调用上述函数会发生如下情况

  • 如果其所有子进程都还在运行,则阻塞
  • 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
  • 如果他没有任何子进程,则立即出错返回

这两个函数区别

  • 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞
  • waitpid并不等待在其调用之后的第一个终止子进程,有若干个选项,可以控制他所等待的进程
  • 如果子进程已终止,并且是一个僵死进程,则wait立即返回并取得该子进程的状态,否则wait使调用者阻塞,直到一个子进程终止。
  • statloc是一个整型指针,若statloc不为空指针,则终止状态就存放在它所指向的单元内,若不关心终止状态,该参数可指定为空指针

下面4个互斥宏可用来取得进程终止原因,这四个宏哪一个为真就可以选用其他宏来取得退出状态、信号编号等

等待特定进程的函数waitpid

  • pid==-1waitpid与wait等效

  • pid>0 等待进程ID与pid相等的进程

  • pid==0等待组ID等于调用进程组ID的任一子进程

  • pid<-1等待组ID等于pid绝对值的任一子进程

  • options为0或者下标按位或

函数waitid

SUS提供,此函数类似于waitpid

#include <sys/wait.h>
int waitid(idtype_t idtype,id_t id,siginfo_t *infop,int options);
																//成功返回0,出错-1

idtype常量

options常量

函数wait3和wait4

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

#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);

rusage结构体如下

竞争条件

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

  • fork后不能确定哪个进程先运行

  • 为避免竞争条件和轮询,在多个进程之间需要有某种形式的信号和接收方法

函数exec

当进程调用exec函数时,该进程执行的程序完全替换新程序

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

7种不同的exec

#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,char *const argv0,.../*(char *)0*/);
int execvp(const char *filename,char *const argv[]);
int fexecve(int fd,char *const argv[],char *const envp[]); //成功不返回,出错-1
  • 如果filename中包含/,则就将其视为路径名

  • 否则就按PATH(PATH是一个环境变量,名为PATH)环境变量,在它所指定的个目录搜寻可执行文件
    *

  • execlp或execvp使用路径前缀的一个找到了可执行文件,但是该文件不是由连接编辑器产生的可执行文件,则就认为该文件是一个可执行脚本,尝试用/bin/sh解释,并以该filename作为shell输入

  • fexecve

  • 结尾l表示list,v表示矢量vector

    • l表示新程序的每个命令行参数都作为一个单独的参数,这种参数以空指针结尾
    • v表示用argv[]表示每个命令行参数
  • setenv与putenv,可更改当前环境和后面生成的子进程环境,但不影响父进程的环境

  • 新程序从调用进程继承了以下属性

    • 进程ID和父进程ID
    • 实际用户ID和实际组ID
    • 附属组ID
    • 进程组ID
    • 会话ID
    • 控制终端
    • 闹钟尚余留时间
    • 当前工作目录
    • 根目录
    • 文件模式创建屏蔽字
    • 文件锁
    • 进程信号屏蔽
    • 未处理信号
    • 资源限制
    • nice值
    • tms_utime、tms_stime、tms_cutime以及tms_cstime
  • posix.1明确要求在exec时关闭打开目录流,这通常是由opendir函数实现的,

更改用户ID和更改组ID

  • 最小特权模型,我们程序应当只具有为完成给定任务所需的最小特权,降低由恶意用户试图哄骗我们的程序以未预料的方式使用特权造成的安全性风险
#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
    • 以上条件都不满足,则errno设置为EPERM,并返回-1

解释器文件

一种文本文件,起始行是#!pathname[optional-argument],比如#!/bin/sh

函数system

执行字符串命令行

#include <stdlib.h>
int system(const char *cmdstring);
  • cmdstring是要执行的命令
  • fork失败或者waitpid返回除EINTR之外的错误,system返回-1,并且设置errno以指示错误类型
  • 如果exec失败,则其返回值如同shell执行了exit(127)一样
  • 若所有的三个函数(fork,exec和waitpid)都成功,那么system的返回值是shell的终止状态,其格式已在waitpid中说明

进程会计

用户标识

系统通常记录用户登录时的名字,用getlogin函数获得此登录名

#include <unistd.h>
char *getlogin(void);//返回指向登录名字符串指针,出错null

进程调度

#include <unistd.h>
int nice(int incr);//成功返回新的nice值NZERO,出错-1;

getpriority函数像nice函数那样获取进程nice值,但getpriority还可以获取一组相关进程的nice值

#include <sys/resource.h>
int getpriority(int which,id_t who);//成功返回-NZERO~NZERO-1 nice值,出错-1

进程时间

任一进程都可以调用times函数获得他自己以及已终止子进程的墙上时钟时间,用户cpu和系统cpu时间

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

tms结构如下

参考:

《APUE》

计算机科学导论:第七章 操作系统_

什么是 操作系统 自举?_

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值