1.进程创建
1-1 fork()
cpp
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,⽗进程返回⼦进程id,出错返回-1
创建fork()时:
• 分配新的内存块和内核数据结构给⼦进程
• 将⽗进程部分数据结构内容拷⻉⾄⼦进程
• 添加⼦进程到系统进程列表当中
• fork返回,开始调度器调度, 给父进程返回pid, 给子进程返回0
写时拷贝:父子独立性

例子:
cpp
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
运⾏结果:
root@localhost linux\]# ./a.out Before: pid is 43676 After:pid is 43676, fork return 43677 After:pid is 43677, fork return 0

fork之前⽗进程独⽴执⾏,fork之后,⽗⼦两个执⾏流分别执⾏。注意,fork之后,谁先执⾏完 全由调度器决定。
1-2 写时拷贝
通常,⽗⼦ 代码共享,⽗⼦ 再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷**⻉** 的**⽅** 式各**⾃**备⼀份副本。

父进程如果没子进程,那么数据段是可写的,一旦有了子进程,那么就会被修改为只读。
子进程对数据段进行写的时候,会报错,但意识到是数据段, 就会开启写时拷贝。
浅拷贝 写时拷贝
为什么要写时拷贝?
(1)只拷贝自己想改的,
(2)减少创建时间,减少内存浪费。
解释:因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证!写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率。
2.进程终止
本质就是释放系统资源,就是释放进程申请的相关内核数据节后和对应的数据和代码。
2-1进程退出场景
三种退出场景
1.代码完毕->结果正确
- ->结果错误,没有按预想进行
3.代码异常终止
2-2进程常⻅退出⽅法
正常终⽌(可以通过 echo $? 查看进程退出码):
从main返回
调⽤exit
_exit
异常退出:
• ctrl + c,信号终⽌
2-2-1:退出码
什么是退出码?
退出码(退出状态)可以告诉我们最后⼀次执⾏的命令的状态。在命令结束以后,我们可以知道命令 是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表⽰执⾏成功,没有问题。 代码 1 或 0 以外的任何代码都被视为不成功
问题:int main(),返回0,返回给谁?
答:返回给父进程bash.main函数返回值,通常表名程序的执行情况属于进程退出场景的前两种情况。
当返回0的时候,表示代码完毕,结果正确。
当返回非0的时候,不同的返回值来表示不同的出错原因。
如:

一些指令:
"echo $?",一条指令:表示打印最近一个程序退出时的退出码 -> 进程退出码 -> main函数返回值。
"strerror(i)":i表示任意数字,整个strror(i)可以表示各个数字对应的错误信息
"return errno":如果出现错误,但不知道是哪一种,可以用来查看错误信息
代码终止异常:退出码无意义。一旦出现异常,一般是进程收到了信号。
2-3 exit和_exit
2-3-1 _exit
cpp
#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值
2-3-2 exit(数字)
cpp
#include <unistd.h>
void exit(int status);
:使得一个进程终止。等价与return
任何地方调用exit()都表示进程结束。后续代码一律不执行。并返回给父进程bash,子进程退出码
exit最后也会调⽤_exit,但在调⽤_exit之前,还做了其他⼯作:
执⾏⽤⼾通过atexit或on_exit定义的清理函数。
关闭所有打开的流,所有的缓存数据均被写⼊(清空缓冲区)
调⽤_exit
_exit() (系统调用)vs exit((库函数))
:进程调用exit 退出的时候,会进行缓冲区的刷新
调用**_exit()** ,不会进行缓冲区的刷新
库函数会调用相关的系统调用来完成自己的功能。
库缓冲区:语言层会刷新缓冲区,系统层不会刷新缓冲区
3.进程等待
3-1进程等待的必要性

1.解决僵尸进程造成的内存泄漏问题
2.回收子进程资源(最重要的), 获取子进程退出信息(可选)。
3-2 进程等待方法
3-2-1 wait方法
cpp
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL
子进程退出:返回对应pid;
子进程没退出:父进程会阻塞在wait调用处。
waitpid:成功:返回子进程pid
失败:>0,子进程pid
-1:等待任意个子进程。
3-2-2 waitpid方法
cpp
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的⼦进程的进程ID;
如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;
如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;
参数:
pid:
Pid = -1,等待任⼀个⼦进程。与wait等效。
Pid > 0.等待其进程ID与pid相等的⼦进程。
status: 输出型参数
WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程
是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程
的退出码)
options:默认为0,表⽰阻塞等待
WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等
待。若正常结束,则返回该⼦进程的ID。
pid > 0可指定等待子进程。
pid = -1, 等待任意子进程
代码验证:

3-3退出码和终止信号
3-3-1 用status取出状态码

为什么不一样?

上面为退出状态:
下面为终止信号:前8位不用,core dump标志, 低7位表示终止信号(为什么被终止):
没有异常:低7各比特位全0
收到信号:低7各比特位不为0,表示位异常退出, 退出码无意义。
int整型变量一共32位,高16位我们不要,图中位低16位, 前8位位退出码,后8位如果属于征程退出,那么后面就都是0, 所以statux = 1 << 8 == 256,而不是 1.也就是说正常终止,我们要获取退出码需要把status >> 8,才可以得到
附加问题
拿到子进程信息可以使用全局变量码?

不行, 存在写时拷贝, 要拿到子进程信息,只能通过系统调用来获取。
3-3-2 终止信号


使用kill -9 pid后,子进程被异常退出。
退出码为0, 退出信号为9,终止信号位9。
没有异常?
1.低7个比特位,全0
2.一旦不为0,那么就是异常退出的,退出码无意义。
3-4三个子问题
1.waitpid怎么拿到的status
父进程调用系统调用,操作系统取子进程拿到,返回给父进程
2.宏封装

封装获取退出码,用调用,我们也可以实现

1.等待成功 --- rid > 0
2.子进程是否退出异常 --- WIFEXITED
3.options 非阻塞调用

WNOHANG:wait no hang ,等的时候不要挂住 --- 非阻塞调用。
例子: 一直打电话:多次打,循环完成
非阻塞轮询:父进程不断请求调用子进程
返回值大于0:等待结束->回收成功
等于0:调用结束,子进程没有退出
小于0:失败
非阻塞调用:可以在等待对方的时候对方做自己的事情,效率高
No Block
非阻塞代码:

一直等待,直到成功:

验证父进程在等待子进程的时候,可以做自己的事情:
使用函数指针,循环调用:
cpp
// 函数指针类型
typedef void (*func_t)();
#define NUM 5
func_t handlers[NUM+1];
// 如下是任务
void DownLoad()
{
printf("我是一个下载的任务...\n");
}
void Flush()
{
printf("我是一个刷新的任务...\n");
}
void Log()
{
printf("我是一个记录日志的任务...\n");
}
// 注册
void registerHandler(func_t h[], func_t f)
{
int i = 0;
for(; i < NUM; i++)
{
if(h[i] == NULL)break;
}
if(i == NUM) return;
h[i] = f;
h[i+1] = NULL;
}
int main()
{
registerHandler(handlers, DownLoad);
registerHandler(handlers, Flush);
registerHandler(handlers, Log);
pid_t id = fork();
if(id == 0)
{
//子进程
int cnt = 3;
while(1)
{
sleep(3);
printf("我是一个子进程, pid : %d, ppid : %d\n", getpid(), getppid());
sleep(1);
cnt--;
//int *p = 0;
//*p= 100;
// int a = 10;
// a /= 0;
}
exit(10);
}
// 父进程
while(1)
{
int status = 0;
pid_t rid = waitpid(id, &status, WNOHANG);
if(rid > 0)
{
printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid, (status>>8)&0xFF, status&0x7F) ; // rid
break;
}
//说明子进程好在调用,没有退出
else if(rid == 0)
{
//父进程在运行
// 函数指针进行回调处理
int i = 0;
for(; handlers[i]; i++)
{
handlers[i]();
}
printf("本轮调用结束,子进程没有退出\n");
sleep(1);
}
else
{
printf("等待失败\n");
break;
}
}



