1.进程创建
1.1.再谈fork
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);//pid_t为整形
返回值:子进程中的fork()返回0,父进程中的fork()返回子进程的id (pid),出错时返回 -1
在前面创建子进程的时候就学过了fork函数,它能从已经存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。
当进程调用fork,当控制转移到内核中的fork代码后,内核做:
pid_t fork(void)
{
- 1.分配新的内存块和内核数据结构给子进程
- 2.将父进程部分数据结构内容拷贝至子进程
- 3.添加子进程到系统进程列表当中
- 4.fork返回,开始调度器调度
}
fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
子进程的特点:
1.将父进程的所有数据都 共享或拷贝 到了子进程中。(若子进程不对父进程的数据进行修改的话,父子进程的数据也是共享的。若子进程对父进程的数据进行修改时,会发生写时拷贝,将父进程的数据进行拷贝一份到子进程中)
写时拷贝 :(是一种延时申请技术,可以提高整机内存的使用率)
2.子进程和父进程的所有代码共享
3.由于 程序计数器 和 CPU中存储上下文数据的寄存器 原因,子进程虽然可以共享父进程的所有代码,但是在子进程中是从fork()创建子进程之后的代码开始执行的,fork()创建子进程之前的代码默认已经执行过,不会重复执行,所以子进程会执行子进程创建之后的代码。但是由于fork()进行返回值是在子进程创建之后进行返回的,所以子进程依然会执行fork()的返回值。
我们之前详细讲过fork函数,就不再讨论fork函数了
1.2.创建进程
使用fork函数创建子进程
2.进程退出
2.1. 退出码
通常main函数最后都要有一句return 0,这些数字有什么意义吗?
int main()
{
//...
return 0;
}
我们可以来看看
#include<stdio.h>
#include<string.h>
int AddtoTarget(int from,int end)
{
int sum=0;
for(int i=from;i<end;++i)
{
sum+=i;
}
return sum;
}
int main()
{
//写代码是为了完成某件事情,我们如何得知事情完成的怎么样呢?
//进程退出码
int num=AddtoTarget(1,100);
if(num == 5050)
return 0;
else
return 1;
return 0;
}
$? 该符号永远记录最近一个进程在命令行中执行完毕时对应的退出码
echo $? //查看进程退出码
这里下面的三个0是怎么回事呢?
这是因为echo 也是一个子进程,因此剩下的三个的是echo &?是一个echo子进程的退出码。
(main函数的返回值实际上是 进程的退出码,用于表示进程是否是正确返回。)
- 第一种:若退出码是0,则表示进程正确返回,0:success。
- 第二种:若退出码为非0,则表示进程不正确返回,并且每个退出码都对应一个报错信息,退出码:报错信息。
- 意义:将返回值返回给上一进程,用于监控进程的退出状态。出错时方便定位错误。
有人就说了退出码都是数字,我们怎么知道它们分别对应什么错误?
还好c语言自带了一个查询退出码的函数——strerror
#include<stdio.h>
#include<string.h>
int main()
{
for(int i=0;i<200;++i)
{
printf("%d: %s\n",i,strerror(i));
}
return 0;
}
strerror记录了对应的退出码的映射信息,总共135个,这里截取了一小部分。
2.2.进程退出场景
- 此时代码运行完毕,结果正确 (这个时候main函数的返回值为0则为正确,0:success)
- 此时代码运行完毕,结果不正确(这个时候main函数的返回值非0则为不正确,返回值:报错信息)
- 此时代码异常终止(这个时候main函数的返回值不具有意义,此时应该去看退出信号)
2.3.怎么退出进程
2.3.1.main函数return返回,其他函数return是调用结束。
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返 回值当做 exit的参数。
2.3.2.任意地方调用exit。
我们来使用一下
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int AddtoTarget(int from, int end)
{
int sum = 0;
for (int i = from; i <= end; ++i)
{
sum += i;
}
exit(12);
// return sum;
}
int main()
{
int ret = AddtoTarget(1, 100);
if (ret == 5050)
return 0;
else
return 1;
}
2.3.3.任意地方调用_exit(了解)
我们来使用一下
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int AddtoTarget(int from, int end)
{
int sum = 0;
for (int i = from; i <= end; ++i)
{
sum += i;
}
_exit(12);
// return sum;
}
int main()
{
int ret = AddtoTarget(1, 100);
if (ret == 5050)
return 0;
else
return 1;
}
对比exit和_exit发现,都可以使进程再任意地方结束。
2.3.4.exit()和_exit()的区别
我们先来看两段代码
#include<stdio.h> #include<string.h> #include<unistd.h> #include<stdlib.h> int main() { printf("hello linux"); sleep(2); exit(1); }
#include<stdio.h> #include<string.h> #include<unistd.h> #include<stdlib.h> int main() { printf("hello linux"); sleep(2); _exit(1); }
对比发现,exit会刷新缓存区,而_exit并不会刷新缓存区——说明exit可能会调用_exit。
事实上:
exit()和_exit()的底层区别:
- _exit():_exit()是系统调用接口的函数。
- exit():而exit()是把_exit()封装在内,并且增加了其他的函数,共同组合成exit()——是一个库函数。
exit是库函数,_exit是系统调用。库函数在系统调用上面。
如果缓存区在内存,exit调用_exit去终止进程,exit/_exit都应该会刷新缓冲区。
所以缓冲区在用户空间,是用户级的缓冲区。
2.3.5.异常退出
·ctrl + c,信号终止