系统api-进程

进程环境

进程终止

正常终止:
a. 从main返回/调exit/调_exit/调_Exit
b. 所有线程终止/从最后一个线程调用pthread_exit
异常终止:
a. abort/接到一个信号/最后一个线程对取消请求做响应.

注册终止处理程序

int atexit(void (*func)(void));

进程环境表

一个例子:
在这里插入图片描述

char* getenv(const char *name);
int setenv(const char *name, const char *value, int rewrite);

进程资源限制

int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);

在这里插入图片描述

进程控制

进程标识

ID的进程,通常是调度进程,常被称为交换进程.
ID的进程,通常是init进程,在自举过程结束由内核调用.
init通常读取与系统有关的初始化文件(/etc/rc*/etc/inittab及在/etc/init.d中的文件)并将系统引导到一个状态.

pid_t getpid();// 进程id,等价于线程组id.
pid_t getppid();// 父进程id.等价于父进程所在的线程组id.

uid_t getuid();// 用户id.
uid_t geteuid();// 有效用户id.
gid_t getgid();// 组id.
gid_t getegid();// 有效组id.

生成子进程

一次调用,两次返回

pid_t fork();

在这里插入图片描述
除了打开文件外,父进程的很多其他属性也由子进程继承
(1). 实际用户ID/实际组ID/有效用户ID/有效组ID
(2). 附属组ID
(3). 进程组ID
(4). 会话ID
(5). 控制终端
(6). 设置用户ID标志/设置组ID标志
(7). 当前工作目录
(8). 根目录
(9). 文件模式创建屏蔽字
(10). 信号屏蔽和安排
(11). 文件描述符的执行时关闭标志
(12). 环境
(13). 连接的共享存储段
(14). 存储映像
(15). 资源限制

exit

内核为每个终止的进程保存了一定量的信息,进程父进程调waitwaitpid时,可得到这些信息.一个已经终止,但其父进程尚未对其进程善后处理(获取退出信息,释放相关资源)的进程称为僵尸进程.

wait和waitpid

进程终止时,内核向其父进程发SIGCHLD信号

// 调用异常终止情形:
// 1.没子进程
// 2.信号中断
pid_t wait(int *statloc);
// 调用异常终止情形:
// 1,2
// 参数错误
pid_t waitpid(pid_t pid, int * statloc, int options);

在这里插入图片描述

竞争条件

多个进程/多个线程,相互间执行顺序没有保证的.
如某些操作有时序要求,则需要加上进程/线程同步机制来保证时序要求.

exec

int execve(const char * pathname, const *const argv[], char *const envp[]);

执行exec后,进程ID不变,以下属性也不变:
(1). 进程ID,父进程ID
(2). 用户ID,组ID
(3). 附属组ID
(4). 进程组ID
(5). 会话ID
(6). 控制终端
(7). 闹钟剩余时间
(8). 当前工作目录
(9). 根目录
(10). 文件模式创建屏蔽字
(11). 文件锁
(12). 进程信号屏蔽
(13). 未处理信号
(14). 资源限制
(15). 友好值
(16). tms_utime, tms_stime, tms_cutimetms_cstime
(17). 文件描述符是否保留依赖其FD_CLOEXEC标志是否设置
(18). 有效用户ID/有效组ID是维持不变,还是变为可执行文件所有者ID/组所有者ID,依赖可执行文件的设置用户ID,设置组ID位是否设置.

更改用户ID和更改组ID

int setuid(uid_t);
int setgid(gid_t);

(1). 进程的用户ID/组ID是登录是依据登录用户而设置的.如登录后,有更改需求,必须是超级用户来更改.
(2). 进程的有效ID/有效组ID
默认下与进程的用户ID/组ID一致.特殊情况是,可执行文件的权限模式包含设置用户ID/设置组ID时,则执行此可执行文件的进程有效ID/有效组ID采用此可执行文件的用户ID/组ID
(3). setuid/setgid用于设置有效ID,有效组ID
对普通用户,只能用于将有效ID/有效组ID设为和实际ID/实际组ID一致.
对超级用户,可随意设置.
(4). 保存的用户ID/组ID为后续添加的特性,暂不考虑.

system

// 相当于让shell执行命令字符串
int system(const char *cmdstring);

进程调度

// 友好值越小越优先
int nice(int incr);

提供类似功能的一对apigetpriority/setpriority

进程组

进程组是一个或多个进程的集合,同一进程组中的各个进程接收来自同一终端的各种信号.每个进程组有一个组长进程,进程组ID=组长进程进程ID.进程组由组长创建,在进程组添加新进程.组长进程终止,其关联的进程组依然可以存在.进程组开始创建,到组内所有进程离开组过程为进程组生命期.

pid_t getpgid(pid_t pid);
// 将pid进程的进程组id设为pgid,其结果可能是创建一个新的进程组或加入一个已经存在的进程组
// pid允许为进程自身,或其子进程
int setpgid(pid_t pid, pid_t pgid);

进程组按是否关联到终端,分为后台进程组,前台进程组.后台进程组读,写终端时,进程组将收到相应信号.前台进程组可正常读,写终端.

会话

会话是一个或多个进程组的集合.
在这里插入图片描述

pid_t setsid(void);
pid_t getsid(pid_t pid);

如调用setsid的进程不是一个进程组的组长,则此函数创建一个新会话:
发生3件事:
(1). 进程变为新会话的会话首进程.
(2). 进程变为一个新进程组的组长进程.
(3). 进程与控制终端的联系被切断.
如调用进程已是一个进程组的组长,函数出错返回

fork得到的子进程,继承了父进程进程组ID,故其不可能是进程组的组长进程.

控制终端

(1). 一个会话可有一个控制终端.
(2). 一个会话中的几个进程组可被分为一个前台进程组及一个或多个后台进程组.
(3). 前台进程组是拥有控制终端的进程组,控制终端的信号会发给前台进程组内各个进程.
(4). 终端断开时产生一个消息发给会话首进程(会话由此进程开始创建起来).
(5). 控制终端归属于一个进程组,这是控制终端的一个属性.
在这里插入图片描述

给会话分配控制终端.

tcgetpgrp, tcsetpgrp, tcgetsid

// 前提:
// 1.调用进程属于某会话,且该进程有控制终端
// 2.fd是控制终端对应的文件描述符
// 3.pgrpid是进程所在会话的某个进程组的id
// 结果:
// 将进程组pgrpid关联到控制终端fd,此进程组自动成为前台进程组
int tcsetpgrp(int fd, pid_t pgrpid);
// 前提:
// fd是一个会话的控制终端
// 结果:
// 返回关联到此终端的会话的前台进程组id
pid_t tcgetpgrp(int fd);
// 前提:
// fd是一个会话的控制终端
// 结果:
// 返回关联到此终端的会话的会话首进程进程ID
pid_t tcgetsid(int fd);

孤儿进程组

进程组属于某个会话.
若进程组每个进程,其父进程只能是 进程组内某进程/会话外进程时,称进程组是孤儿进程组.即此进程组内每个进程的父进程均不是同一会话内其他进程组的进程.孤儿进程组是一个和所属会话内其他进程组隔离开的进程组(通过父子建立进程组间联系).

进程组成为孤儿进程组时(一般在其父子进程构成的组,父进程终止,子进程尚未交付给init时),进程组内每个处于停止状态的进程(通过SIGTSTP让其停止)收到SIGHUP信号(默认处理是终止进程),若进程对SIGHUP进行处理而未终止,则此后收到SIGCONT信号(使停止状态的进程得以继续执行).
// 孤儿进程组自动断开与终端连接,变为后台进程组,终端重新选择一个会话内进程组绑定?

梳理

(1). 初始时
会话构建时,基于一个不是所在进程组组长的进程A发起构建.
构建后,A自身形成一个进程组BID和其进程ID一致).
A称为进程组B的组长.进程组B不与终端关联.
一个新会话产T生,会话ID为进程AID.会话此时只包含一个进程组,即为BA是会话T的会话首进程.

(2). 会话
会话包含一个或多个进程组,会话记录此会话首进程ID,会话关联到至多一个终端.

(3). 终端
终端关联到至多一个会话,终端关联到至多一个进程组.

(4). 进程组
关联到至多一个会话,关联到至多一个终端.

(5). 进程
关联到一个父进程,关联到一个进程组,关联到至多一个终端.

(6). 信号
终端按键产生的信号会发给终端关联的进程组的每个进程,终端断开连接时产生的信号会发给终端关联的会话首进程.

守护进程

常在系统引导装入时启动,系统关闭时才中止.
没有控制终端.多数守护进程以超级用户特权运行.
多数用户层守护进程是进程组的组长进程及会话的首进程,且是这些进程组和会话中的唯一进程.
用户层守护进程的父进程是init进程.

(1). 守护进程
在后台运行且不与任何控制终端关联的进程
a. 由系统初始化脚本启动
b. 终端下键入命令行启动
c, 通过inetd启动
inetd本身由系统初始化脚本启动

进程与终端断开联系的好处:不受终端管理,不接收终端信号.

(2). 把一个普通进程转变为守护进程
a. 执行fork,父亲进程退出.
意味着此时我们以子进程在运行,且此子进程并不是所属进程组的组长进程.
b. 对子进程执行setsid,我们可以对一个不是进程组组长的进程执行setsid
这会创建一个会话,且以调用进程进程ID创建一个进程组,调用进程也成为了此进程组的组长进程.调用进程也成为了所创建会话的会话首进程.
在会话首进程内若打开了终端的话,则终端控制信号无论该进程是前台运行,还是后台运行都是会发给此进程的.
为了排除这种情况(在原始父进程执行fork前打开的终端时,即会发生这种情况).执行c
c. 先忽略SIGHUP信号,再次fork
忽略SIGHUP是因为会话首进程终止时,会给会话内每个进程发SIGHUP
再次fork后父进程退出,此时留下了一个进程.
该进程属于创建的会话.该进程无法接收终端控制信号(因为该进程首先必然是后台进程,然后,该进程又不是会话首进程,所以即使在该进程内打开终端,也不受终端信号的控制).

终端信号不会发给既不是会话首进程又不是前台进程的进程.然后该进程所属的进程组内除了该进程也不会有其他进程,这意味着,也不用担心进程组内的其他进程给该进程发信号,控制其运行等.

这样,此时我们的进程既不受终端信号的控制,又不受进程组内其他进程的控制.这样的一个进程是可以按其自身逻辑来运行的,不用担心受到打扰.
d. 一些尾处理
包含设置进程工作目录,关闭进程内所有打开的文件描述符,并将描述符0/1/2定向到/dev/null
这一步有必要,因为如果此进程后续用户代码执行了如printf这样的调用.
这些调用内部会与0/1/2描述符有交互.如0/1/2未被定向到/dev/null,而是定向到后续客户打开的某个’文件’,则会造成prinf等和客户打开的’文件’进行交互的非预期场景.

文件的FD_CLOEXEC

可通过fcntl F_GETFD/F_SETFD FD_CLOEXEC来设置或删除.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值