系统编程-进程控制

特殊进程:
0号进程,调度进程,也成为交换进程 swapper
1号进程,init进程,读取与系统有关的初始化文件(/etc/rc* 或者 /etc/inittab 或者 /etc/init.d),他是一个用户进程,但是进程有效ID是超级用户。init进程是所有孤儿进程的父进程

创建进程

//进程
pid_t getpid(void)
pid_t getppid(void)
uid_t getuid(void) //获取进程实际id,谁启动的
uid_t geteuid(void) //获取进程有效id,谁的权限
gid_t getgid(void) //获取进程实际gid,
gid_t getegid(void) //获取进程有效组id,

创建进程

#include <unistd.h>
pid_t fork(void)

if((pid = fork()) < 0)
	//error;
else if(pid == 0)
	//son
else
	//father

调用一次,返回两次, 子进程的返回值是0,父进程的返回值是子进程的pid
子进程获得父进程的代码段,数据段,堆,栈副本。
处于效率考虑,子进程并不会复制父进程的所有数据,采用写时复制(copy on write,COW),当对某一段(“页”)进行修改时才真正复制,否则共享同一段数据(“页”)。
fork之后是父进程先执行还是子进程先执行是不确定的,取决于内核的调度算法

文件共享:
fork的一个特点:父进程的所有打开文件描述符都被复制进子进程,即父子进程的文件描述符共享一个文件表

fork后父子进程的区别:
fork值不同,进程id不同,父进程id不同
子进程tms_utime、tms_stime、tms_cutime、tms_ustime均被设置为0;
子进程不继承文件锁
子进程不继承未处理的alarm
子进程不继承未处理的信号集

fork失败:整个系统中有太多的进程, 当前用户拥有的进程超过CHILD_MAX

fork应用场景:
1.父进程希望复制自己,使父子进程同时执行不同的代码段,比如网络服务进程,父进程等待请求到达,子进程处理请求
2.一个进程要执行不同的程序,比如shell,这种情况下,fork后,子进程通常直接进行exec,某些操作系统将fork之后exec组合成一个,称为spawn

vfork:用于fork后exec的场景,功能和fork类似,创建一个子进程,
区别在于:vfork保证子进程先运行,在子进程调用exec或者exit之后父进程才运行。 vfork由于预设了子进程会exec,不会完全复制父进程地址空间

进程的终止

五种正常的终止方式:
1.main函数return。 2.调用exit()。3.调用_exit()或者_Exit()。
4.最后一个线程的启动历程return 5.最后一个线程调用pthread_exit

三种异常终止方式:
1.调用abort。2.当进程接收到某些信号。3.最后一个线程对"取消"请求做出响应

进程的退出状态:
对于正常终止的进程,退出状态来自exit的入参,
对于异常终止的进程,退出状态是内核产生
父进程可以用wait 或者 waitpid 取得子进程的终止状态

为什么会有僵死进程:
这是为了保留子进程的进程信息,(进程id,终止状态,进程使用的cpu时间), 当进程终止,但这些资源未被获取,则称为僵死进程
通过wait调用可以回收僵死进程,init进程被设计为,只要有子进程终止,就会调用wait回收

子进程终止时,内核向父进程发送SIGCHLD信号,该信号默认行为是忽略。

调用wait和waitpid
如果其所有子进程都还在运行,则阻塞
如果一个子进程已终止,则立刻获取其终止状态并返回
如果没有子进程,则立即出错返回

#include <sys/wait.h>
pid_t wait(int *statloc)
pid_t waitpid(pid_t pid, int *statloc, int options)
ret:如果成功返回终结进程id,出错返回-1,0其他

waitpid可以选择是否阻塞的去等待子进程终止
statloc时一个整形指针,进程的终止状态信息就存放在指向他的单元里,如果不关心返回状态,可以置为NULL
如果要检查退出状态,可以使用下列宏:

WIFEXITED(status) //正常退出
WIFSIGNALED(status) //异常终止
WIFSTOPPED(status) //暂停执行
WIFCONTINUED(status) //暂停后继续执行

如果是wait,任何一个子进程退出,wait就会返回, 如何等待指定进程呢?
1.还是使用wait,每次wait返回后,与预期的目标对比。
2.使用waitpid(pid, statloc, option)
pid == -1,则等待任意进程,和wait相同
pid > 0 等待目标pid进程
pid == 0 等待组id等于调用进程组id的进程 (进程组的概念还没讲)
pid < -1 等待组id等于pid绝对值的任一子进程

option:宏WNOHANG,如果没有进程终止,不会阻塞,立刻返回, 如果系统支持作业控制还有两个别的,WCONTINUED,如果进程暂停后继续且未报告过则返回,WUNTRACED,如果进程暂停后未报告过则返回

总之waitpid提供额外3个功能:1.等待特定进程,2.wait的非阻塞版本,3.支持作业控制

#include <sys/wait.h>
int waitid(idtype_T idtype, id_t id, siginfo_t *infop, int options);
//其作用和waitpid类似,不过将pid和进程组pid通过idtype分开。
//idtype P_PID, 则id为等待进程pid。  P_PGID,则id为等待进程组ID,  P_ALL 等待任意进程,忽略id
//option:宏WNOHANG,如果没有进程终止,不会阻塞,立刻返回, 如果系统支持作业控制还有两个别的,WCONTINUED,如果进程暂停后继续且未报告过则返回,WUNTRACED,如果进程暂停后未报告过则返回
//option:WNOWAIT,虽然获取子进程内容,但是不会对其进行回收。

//注意,只有POSIX.1 的XSI支持该接口,使用的linux可能不支持

wait3,wait4

#inlcude <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

pid_t wait3(int *statloc, int option, struct rusage *rusage)
pid_t wait4(pid_t pid, int *statloc, int option, struct rusage *rusage)
//相比于wait,waitpid,新的两个接口增加rusage,用于记录进程的资源使用信息(用户CPU时间总量,系统CPU时间总量,页面出错次数,接收到信号)

进程竞争(race condition) (运行结果取决于运行顺序)同步

常见的父子进程同步方式:
父进程等待子进程结束,调用wait()
子进程等待父进程结束,while(getppid() != 1) sleep(1);,这种形式的循环称为轮询,浪费CPU时间
可以使用信号或者IPC解决同步问题

父子进程协作完成任务

if((pid = fork()) < 0){
	printf("fork error\n");
}else if(pid == 0){
	...
	TEEL_PARENT(getppid()); //告诉父进程,子进程已经准备完毕,父进程可执行
	WAIT_PARENT();
}
WAIT_CHILD();
...
TELL_CHILD(pid); //告诉子进程,父进程准备完毕。
exit(0);

exec切换进程环境

exec会用全新的程序替换当前进程的正文,数据,堆,栈

6个不同的exec函数可以使用

#include <unistd.h>
int execl(path, arg0, ...)
int execv(path, argv[]);
int execle(path, arg0,...,)
int execve(path, argv[], envp[])
int execlp(filename, arg0, ...)
int execvp(filename, argv[])
ret: 成功不反回,失败返回-1;

l标识list, v 标识vector, e标识environment, p标识path

前4个和后两个的区别:当指定为filename时,会从PATH中,寻找可执行文件。
PATH环境变量,用冒号分隔路径 PATH=/bin:/user/bin:.
如果filename找到的文件不是可执行文件,则将其认为是shell,用/bin/bash作为解释工具
用0标识空指针,必须用 char *强转

参数列表和环境列表的总长度有限制,ARG_MAX,此值至少是4096字节

exec前后,实际用户id不变,但是有效id取决于执行的程序是否设置了 设置用户id和设置组id

system

#include <stdlib.h>
int system(const char *cmdstring)
//为了方便执行shell的命令

其使用fork exec waitpid
如果在一个设置用户ID的程序中,使用system(), 这意味着,可以用该用户ID执行任何命令,是一个安全漏洞

进程会计(process accounting)

用户标识

获取登录用户的用户名

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

进程时间

进程有三种可测量的时间:实际时间,用户CPU时间,系统CPU时间

#include <sys/times.h>
clock_t times(struct tms *buf);
ret: 成功返回滴答树, 出错返回 -1;

struct tms {
	clock_t tms_utime;
	clock_t tms_stime;
	clock_t tms_cutime;
	clock_t tms_cstime;
}

注意,由于此时间的起始值是一个随机值,所以作为时间的度量时,必须使用其相对值(t2 - t1),而不能用绝对值
posted @ 2022-07-11 09:44  木瓜粉  阅读(47)  评论(0编辑  收藏  举报