进程创建
进程:内核数据结构(task_struct ,mm_struct ,页表…)+代码和数据
每一个进程在内核都有一个对应的test_struct结构体,包含了进程的所有信息,(如:进程ID(PID)、父进程ID(PPID)、进程状态、进程地址空间、打开的文件描述符等等。) 也称PCB(进程控制块)。
mm_struct是Linux内核用来描述进程虚拟地址空间的结构体。
页表:用于实现虚拟内存到物理内存映射的数据结构。
fork函数
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中(PCB移动到调度队列)
4.fork返回,开始调度器调度
当一个进程调用fork之后,就有两个二进制代码相同的进程。
子进程和父进程是相互独立的,他们有各自的内核数据结构,共享代码,子进程写时拷贝父进程的数据,当子进程要执行一个全新的程序时(进程的程序替换),代码也会进行写时拷贝。
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;
}
我们能看到有三行输出,一行before,两行after。
进程43677没有before输出。所以我们能推断:fork前父进程独立执行,fork后,父子进程分别执行。
fork函数返回值
子进程返回0
父进程返回子进程的pid
为什么父进程的返回值是子进程的pid呢?
为了方便父进程对子进程进行标识,进而管理。
写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
当进行只读操作时,父子进程的页表内容一样,指向的物理地址空间也一样,当要进行写操作时,发生写时拷贝,复制当前物理空间的值,开新空间,并指向新空间。
子进程功能
1.做和父进程相同的事
2.执行不同的程序(程序替换)
fork调度失败的原因
1.系统中有太多的进程
2.实际用户的进程数超过了限制
进程终止
进程终止的概念
进程=内核数据结构+代码和数据
创建进程时,先创建内核数据结构再创建它的代码和数据
进程终止:
1.释放代码和数据所占空间
2.释放内核数据结构
进程终止的情况
1.代码跑完,结果正确
2.代码跑完,结果不正确
3.代码执行时出现异常,提前退出了
退出码&&退出信号
子进程退出后,父进程要知道子进程的退出结果,所以子进程要给父进程传一个退出码。0表示退出成功,非0表示退出失败。退出码可以自己定义或是使用系统自带的退出码。
代码执行时,编译崩溃了,操作系统会杀死进程,从而出现异常,一旦出现异常,退出码就没有意义了。
出现异常的本质:进程收到了OS发给进程的信号。
所以衡量一个进程的退出,只需知道退出码+退出信号。
子进程退出时,代码和数据都被释放掉了,但子进程的PCB会保留一段时间(变成Z状态),退出时,退出码(exit_code)和退出信号(exit_signal)写入PCB中,方便父进程查看。
进程退出方法
正常终止:
1.main函数中直接return
2.代码任意位置调用exit
3._exit()
异常退出:
ctrl+c ,信号终止
exit与_exit的区别
exit()会先刷新缓冲区,再调用_exit(),这使得exit函数在结束进程时能够确保数据的完整性和资源的正确释放。
_exit则是立即进入内核,释放进程所占用的资源并关闭所有打开的文件描述符,直接终止进程。