进程的创建与终止

19 篇文章 2 订阅

进程创建

fork函数初识

在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

fork创建子进程是以父进程为模板的

进程调用fork,当控制转移到内核中的fork代码后,内核(可以理解为操作系统)做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

image-20211101084815595

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以
开始它们自己的旅程,看如下程序

int main( void )
{
    pid_t pid;
    printf("Before: pid is %d\n", getpid());//父进程
    
    if ( (pid=fork()) == -1 )
        perror("fork()"),exit(1);//fork()失败
    	
    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

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它又打印after。下一行after消息是由43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示

image-20211101085218104

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。

注意,fork之后,谁先执行完全由调度器决定

fork函数返回值

子进程返回0,父进程返回的是子进程的pid

#fork为什么有两个返回值

image-20211003124729969

创建好子进程后,return ret是父子进程都要执行的,所以会返回两次

理解:子进程以父进程为模板,拿到了父进程的数据,其中包含一个程序计数器pc,可以理解为pc指向return指令,所以父子进程都从这开始执行,因此有两个返回值,且父子进程获取的返回值是不同的

写时拷贝

通常,父子代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自拷贝一份副
本。具体见下图:

image-20211003131304689

为什么代码共享,数据私有

默认情况下,父子进程共享代码,但数据各自私有一份(如果父子只会对数据进行读取,不需要进行私有化)

程序 = 代码(逻辑) + 数据
代码共享:所有代码共享,不过子进程一般都是执行fork()之后的代码。

  • 代码共享的原因就是代码不可被修改,所以如果代码各自私有,就会浪费空间
  • 数据私有的原因是进程之间具有独立性

但是,数据是很多的,不是所有数据都要立马使用,也不是所有的数据都需要进行拷贝。但如果立马要独立,就需要将数据全部拷贝。如果把本来可以在之后才拷贝的数据,甚至不需要拷贝的数据,都拷贝了,就会浪费空间和时间。

因此,写时拷贝就是来解决这个问题的。

写时拷贝的触发

一开始数据都是只读,当有进程想写入时,就会引发错误,操作系统检测到错误,就触发了写时拷贝,将数据拷贝,并把权限改为可读可写

image-20211003131417288

写时拷贝的识别,操作是由OS完成的

既然有了写时拷贝的机制,那么如果父进程共有10M数据,但只有1M数据需要修改,那么就只会拷贝1M,剩下的9M共享

如何理解子进程的创建

子进程的创建本质是系统多了一个进程,子进程以父进程为模板,fork将父进程的PCB、虚拟地址、页表等内容大部分都拷贝给子进程,但不是完全拷贝,有些私有的数据不会拷贝,比如pid。

image-20211003132557725

进程的独立性体现:各自的PCB、地址空间、页表,代码是共享的,但不会对代码进行修改,所以彼此不会造成影响,而数据是各自私有的

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子
    进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

  1. 系统中有太多的进程
  2. 实际用户的进程数超过了限制

进程终止

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

return 的结果是进程的退出码:用echo$?可以查看,$?表示最近一条命令的退出码

#include<stdio.h>

int main()
{

  printf("hello world\n");
  return 123;//123就为该程序的退出码
}

退出码查看:

image-20211003135635787

0表示执行结果正确

为什么main函数的return一般写成0呢?

因为0在函数设计中,一般表示正确,非0表示错误,而1,2,3,4,5,6分别对应一种错误的原因

所以代码运行完毕,结果正确,返回的是0
而代码运行完毕,结果不正确,返回的是非0(但一般都是大于0的数)

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  1. 从main返回
  2. 调用exit
  3. _exit

异常退出:

  • ctrl + c,信号终止

exit函数

#include <unistd.h>
void exit(int status);

exit的参数就是进程的退出码,调用exit就是终止进程

例:

//return:
int test1()
{
	printf("hello\n");
    return 0;
}

int main()
{
    test1();
    printf("haha\n");
	return 0;
}

//exit:
int test2()
{
	printf("hello\n");
    exit(1);//直接退出进程
}

int main()
{
    test2();
    printf("haha\n");//不会执行,因为在test2函数里,进程已经退出了
	return 0;
}

这两者的结果是不同的:

image-20211003140808042

test1里的return是终止test1函数,而test2中的exit是终止进程

所以第一个打印的是

hello
haha

而第二个打印的是

hello

_exit函数

与exit基本一样,但属于系统调用的函数

#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255

_exit与exit的一个区别:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void test1()
{
    printf("hello world");
    sleep(3);
    exit(1);
}

void test2()
{
    printf("hello world");
    sleep(3);
    _exit(1);
}

int main()
{
    test1();
    //test2();
    return 0;
}

test1结果:

exit

test2结果:

_exit

我们看到进程退出后,使用exit退出打印了hello world,而是用_exit没有打印,说明exit退出进程时会完成刷新缓冲区等工作,而 _exit不会做这些工作。

exit和_exit的工作:

exit最后也会调用*_exit,* 但在调用_exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

image-20211003142301786

return退出

return是一种更常见的退出进程方法。main函数执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数

exit() vs return

exit:终止整个进程,任何地方调用,都会终止。当然,仅限于调用的进程

return:终止函数。main函数里return,就代表进程退出

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WoLannnnn

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

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

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

打赏作者

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

抵扣说明:

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

余额充值