一、fork函数

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

#include <unistd.h> // 意为uninx 标准接口库
pid_t for(void);
// 返回值:子进程返回0,父进程返回pid值,错误返回-1

进程调用fork以后,当控制转移到内核中的fork代码后,内核做:
分配新的内存和内核数据接口给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表中
fork返回,开始调度器调度

看一个过去的例子

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

int main()
{
	int num = 100;
	printf("我是父进程\n");
	
	pid_t id = fork();
	
	if(id < 0)
		printf("fork error\n");
	else if(id == 0) // 子进程
	{
		num++;
		printf("pid = %d ppid = %d,num = %d\n",getpid(),getppid(),num);
	}
	else  // 父进程
	{	
		printf("pid = %d ppid = %d,num = %d",getpid(),getppid(),num);
	}
	return 0;
}
[benjiangliu@VM-4-8-centos lesson15]$ gcc fork.c -o fork
[benjiangliu@VM-4-8-centos lesson15]$ ./fork
我是父进程
pid = 21491 ppid = 19423,num = 100
pid = 21492 ppid = 21491,num = 101

这个例子非常经典
fork以后,子进程共享父进程的代码和数据,当子进程和父进程的代码数据一致时,两个进程的虚拟内存通过页表共享同一段物理内存,当子进程试图进行num++的时候,CPU触发缺页中断,因为CPU试图写页表中只读存储区(父子进程共享的物理内存),发生写实拷贝,子进程重新开辟一块内存区域,存储发生改变的内存区域,其余字段不变,仍然共享。

再看一个例子:

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

int main()
{
	pid_t pid;
	
	printf("Before:pis 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;
}
[benjiangliu@VM-4-8-centos lesson15]$ ./fork1
Before:pis is 29388
After:pid is 29388,fork return 29389 // 通过pid的返回值29389可以看出这个是父进程
After:pid is 29389,fork return 0     // 通过pid的返回值0 可以看出这个是子进程

我来来捋一捋共享是什么意思
在这里插入图片描述

当一个进程fork以后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方,但是每一个进程都将可以开始他们自己的旅程。
这句话的意思是,fork以后,子进程会共享父进程的代码段,通常这个代码段是只读的,父进程只读,对于子进程同样也是只读的,所以就没有必要去浪费内存复制两份了,直接共享一份代码即可。
再看看,fork以后,子程序从哪运行?是从fork函数后面的那条指令,而不是从main函数的第一行开始跑,更不会重新执行#include ,它就像父进程的一个分身,子进程虽然有完整的代码段,在诞生的一瞬间,子进程继承了父进程在 fork() 那一瞬间的内存镜像(堆、栈、全局变量),直接跳到了fork()这一句执行完毕以后开始执行。

二、写时拷贝

之前我们已经说过了,在fork以后,父子进程共享代码和数据,当任何一方试图写入时,会触发缺页中断,以写时拷贝的方式各自一份副本,如图:
在这里插入图片描述
左图可以看出,父子进程的虚拟内存通过页表映射到物理内存时,物理内存是共享的。
右图可以看出,父进程被修改了部分变量后,OS为该进程的页表项重新映射了新的内存页,从而与另一方分离。
因为有写时拷贝技术的存在,所以父子进程得以彻底分离离!保证了进程虚拟地址空间的独立性,同时在未发生写操作前极大地节约了物理内存。写时拷贝是⼀种延时申请技术,可以提高整机内存的使⽤率。

三、进程终止

1、进程终止的场景

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

正常终止(可以通过echo $?查看进程退出码),注意只能查看到最后一个进程的退出码

[benjiangliu@VM-4-8-centos lesson15]$ ./fork1
Before:pis is 29388
After:pid is 29388,fork return 29389
After:pid is 29389,fork return 0

[benjiangliu@VM-4-8-centos lesson15]$ echo $?
0

1、从main返回
其实就是 main()函数中的return 返回值

2、调用exit()

3、_exit

4、异常终止
ctrl + c ,信号终止

2、什么是退出码?

退出码(退出状态)可以告诉我们最后一次执行的命令的状态,在命令结束以后,我们可以知道命令是成功完成还是以错误结束,其基本思想是程序返回代码为0的时候,没有问题,代码是1 或 0以外的任何代码都被视为不成功。
在这里插入图片描述
命令正确返回 0

int main()
{
	// 程序代码
	return 0;
}

命令错误返回 1

int main()
{
	// 程序代码
	return 1;
}

3、_exit函数

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

虽然status是int,但是仅有低8位可以被父进程所用,所以_exit(-1)时,在终端执行$发现返回值是255
特点:
它不讲武德,直接终止进程,不做任何清理工作。
它不做什么:
不刷新缓冲区:如果数据还在缓冲区里没写出去,直接丢弃。
不关闭文件:打开的文件描述符直接由内核回收,不会执行 fclose 的逻辑。
不调用清理函数。
典型场景:通常在 fork() 产生的子进程中使用。特别是当子进程紧接着要调用 exec 执行新程序时,前面的清理工作是多余的,直接 _exit() 能提高效率。

4、eixt函数

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

喜闻乐见的例子:

if(pid=fork() == -1)
{
	perror("fork()");
	exit(-1); // fork错误后,直接带错误码退出进程
}

而且exit()会对进程进行善后,
刷新缓冲区:将 printf 等函数留在缓冲区(Buffer)里的数据真正写入屏幕或文件。
关闭流:关闭 fopen 打开的文件流。
调用清理函数:执行通过 atexit() 注册的“善后”函数。
最后:它会进一步调用系统调用 _exit() 来真正杀死进程。
在这里插入图片描述
_exit()与exit()比较

int main
{
	printf("hello");   // 缓冲区里有 "hello",因为没有 \n,且缓冲区没满,暂时不输出
	exit(0);           // C 标准库介入,执行清理工作 -> 强制刷新缓冲区 -> "hello" 被打印到屏幕上
}

所以执行代码后输出“hello”

int main
{
	printf("hello");   // 缓冲区里有 "hello",因为没有 \n,且缓冲区没满,暂时不输出
	_exit(0);          // 直接进入内核终止进程,标准库的清理代码根本没机会执行,缓冲区被直接丢弃
}

所以执行代码后,什么输出都没有

5、return退出

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

四、进程等待

1、进程等待必要性

之前说过,子进程退出,父进程如果不管不顾,就有可能造成“僵尸进程”的问题,而造成内存泄漏。
另外,如果进程变为僵尸进程,那就算是KILL -9也无能为力,因为谁也杀不死一个已经死去的进程。
最后,我们需要知道,父进程派给子进程的任务完成的如何,如子进程运行完成,结果是对的还是错误的。
父进程通过进程等待的方式,回收子进程的资源,获取子进程退出信息。

2、进程等待的方法

1、wait方法

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);

// 返回值: 成功返回等待进程pid,失败返回 -1
// 参数:输出型参数,获取子进程退出状态,不关心则可以设置为NULL

一个例子:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("我是父进程:%d\n",getpid());

    pid_t id = fork();

    if(id == 0) // child
    {
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程:%d\n",getpid());
            sleep(1);
        }
        exit(0);
    }
    else
    {
        sleep(10);
        // 父进程很重要的工作是需要回收子进程
        // 也就是子进程执行完了,进入僵尸状态时,父进程登台接受子进程的返回状态
        printf("父进程睡眠10s结束\n");
		
		// wait函数的形参给了NULL意味着忽略了子进程的退出状态
		// 其实就是没有个status这个变量
        pid_t rid = wait(NULL);
        printf("子进程的返回值是:%d\n",rid);
        exit(0);
    }
    
    // 本例子通过另一个bash查看
    // ps axj | head -1 && ps axj | grep wait 产看进程状态
    // 可以看到在子进程运行结束后,父进程仍然在sleep中
    // 此时子进程处在Z僵尸状态,等待父进程接受返回值
    // 父进程睡眠结束后,开始通过wait函数接受子进程的返回值

    // 为什么子函数的返回值不是 exit(0) 中的0
    // 因为在wait函数的形参给了NULL意味着忽略了子进程的退出状态
    // 此时rid的返回值是退出子进程ID,而不是子进程的退出码
    
    return 0;
}
[benjiangliu@VM-4-8-centos lesson20]$ ./wait
我是父进程:10974
我是子进程:10975
我是子进程:10975
我是子进程:10975
我是子进程:10975
我是子进程:10975

父进程睡眠10s结束 
// 父进程sleep了10s,这10s中前5s子进程在运行,后5s子进程退出后,主进程还在sleep,导致子进程处在僵尸模式
子进程的返回值是:10975

打开另一个bash

// 子进程还在运行时
[benjiangliu@VM-4-8-centos ~]$ ps axj | head -1 && ps axj | grep wait
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1  6380  6380  6380 ?           -1 Ssl   1001   0:00 /home/benjiangliu/.VimForCpp/nvim wait函数.c
 7302 10974 10974  7302 pts/2    10974 S+    1001   0:00 ./wait
10974 10975 10974  7302 pts/2    10974 S+    1001   0:00 ./wait
10742 10987 10986 10742 pts/3    10986 S+    1001   0:00 grep --color=auto wait

// 父进程还在sleep,子进程运行结束,进入僵尸模式
[benjiangliu@VM-4-8-centos ~]$ ps axj | head -1 && ps axj | grep wait
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1  6380  6380  6380 ?           -1 Ssl   1001   0:00 /home/benjiangliu/.VimForCpp/nvim wait函数.c
 7302 10974 10974  7302 pts/2    10974 S+    1001   0:00 ./wait
10974 10975 10974  7302 pts/2    10974 Z+    1001   0:00 [wait] <defunct>
10742 11013 11012 10742 pts/3    11012 S+    1001   0:00 grep --color=auto wait

// 父进程sleep结束,调用wait函数,接受子进程的退出码
[benjiangliu@VM-4-8-centos ~]$ ps axj | head -1 && ps axj | grep wait
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1  6380  6380  6380 ?           -1 Ssl   1001   0:00 /home/benjiangliu/.VimForCpp/nvim wait函数.c
10742 11024 11023 10742 pts/3    11023 R+    1001   0:00 grep --color=auto wait

2、waitpid方法

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid,int* status,int options);

// 返回值: 当正常返回的时候waitpid返回收集到的子进程的id值;
// 如果设置了WNOHANG,而调用中waitpid发现没有已退出的子进程可以收集,则返回0;
// 如果调用出错,则返回 -1,这是error会被设置为相应的值,以指示对应错误所在

/* 参数:
pid: 
pid = -1,等待任意一个子进程,与wait等效。
pid > 0,等待其进程id与pid相等的子进程

status: 输出型参数
WIFEXITED与WEXITSTATUS为2个宏定义,可以解析status退出码的意义。
WIFEXITED(status):若正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options:默认为0,表示阻塞等待。父进程会挂起,直到 pid 指定的子进程发生状态改变(无论是正常退出还是被信号终止)。
WNOHANG 的含义:WNOHANG 的字面意思是“不要挂起(Don't Hang)”。
options为WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待(非阻塞等待)。若正常结束,则返回该子进程的id。
注意这个是有区别的,父进程不是简单的检查一次后就退出了,子进程变成孤儿进程。父进程只是继续干活,继续循环,而不是阻塞式的在那里傻等子进程运行结束。
*/

所以我们得到一个结论:
如果子进程已经退出,父进程调用wait/waitpid函数会立刻返回,并释放资源,获得子进程的退出信息。
如果在任意的时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立刻错误返回。
在这里插入图片描述

3、获取子进程status

wait和waitpid,都有一个核心参数status,该参数是一个输出型参数,由该操作系统填充。
如果传递NULL,则不关心子进程的退出状态,否则操作系统将根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当做整体来看,可以当做位图来看,具体如下,我们只研究status的低16bit位。
在这里插入图片描述
看status的低16位,其中低7位是终止信号,如果低7位是0的话,说明进程是正常退出(比如调用exit函数或者return返回值的),第7位是 core dump 标志位(1表示生成了core文件,0表示未生成)。
看status高8位为退出状态,对应exit函数或者main函数的return值。

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>

int main()
{
	pid_t pid;
	if((pid=fork()) == -1)
	{
		perror("fork");
		exit(1)
	}
	if(pid == 0) // 子进程
	{
		sleep(5);
	}
	else // 父进程
	{
		int st;
		int ret = wait(&st); 
		// ret是子进程的pid值
		// st因为取了地址,所以被传入wait函数,被修改为status返回值
	
		if(ret > 0 && (st & 0x7f) == 0) 
		// ret>0说明是子进程的pid值
		// st是wait函数返回的status值,st&0111 1111,相当于检查低7位是不是为0
		// 两者同事成立时,说明子进程是正常退出的
		{
			printf("child exit code:%d\n",(st >> 8)&0xff);
		}
		else if(ret > 0) //此处只有ret的返回值 > 0,说明是信号终止的进程
		{
			printf("sig code: %d\n",st&0x7f);
			// 说明进程是被信号kill的
		}
	}
	return 0;
}
// 注意:
// 这段代码对于初学者,它能建立基本认知。
// 建议在实际编程中,永远使用 <sys/wait.h> 提供的宏(如 WEXITSTATUS, WTERMSIG, WCOREDUMP)来解析 status,而不是自己写位运算,因为不同系统可能有差异。

再看一个例子:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("这是一个waitpid函数的例子\n");
    pid_t id = fork();

    if(id == 0)//child 子进程运行部分
    {
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程 pid:%d\n",getpid());
            sleep(1);
        }
        exit(2);
    }
    else // parrent 父进程运行部分
    {
        sleep(10); // 先sleep10秒,。
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        // waitpid 的返回值为waitpid收集到的子进程的进程ID
        
        // waitpid三个参数:
        // pid_t pid:为-1时等待任意一个子进程,>0 等待与进程id值相等的子进程
        // statu: 输出型参数 int* status,函数要写入并能读取必须要传址传参进函数
        // options: 默认为0 表示阻塞等待
    
        printf("子进程的返回值是%d\n",rid);

        if(rid == id) // 说明子进程正常返回
        {
            int exit_code = (status >> 8)&0xFF; // 位图右移8位
            // status 只有后十六位位有用
            // 高8位为退出码 低8位为信号码
            int exit_sig = status & 0x7F;
            // 0-6位为信号码,第7位为core dump标志位
            // 所以与上 0111 1111 获取低七位数据
            
            printf("pid:%d,wait sucess!status = %d,exit_code = %d,exit_sig = %d\n",getpid(),status,exit_code,exit_sig);
        // 所以我们可以将父进程waitpid的返回值分为2不分
        // 一是函数返回值rid,是子进程的pid值
        // 二是status可以被解析为2部分,1部分是返回值,1部分是信号值
        // 32比特位,高16位未用,其中低16位中,高8位是返回值,低8位是信号值
        }
    }

    return 0;
}

4、进程等待方式

1、几个解析子进程退出状态的宏

1、WIFEXITED(status)

功能:判断子进程是否正常退出(如调用 exit()、_exit() 或 return 从 main 函数返回)。
返回值:若正常退出,返回非0(真);否则返回0(假)。
使用场景:在调用 wait()/waitpid() 后,先用此宏判断子进程是否正常结束,再提取退出码。

2、WEXITSTATUS(status)

功能:获取子进程正常退出时的退出码(exit status)。
依赖条件:必须在 WIFEXITED(status) 为真时调用,否则结果未定义。
原理:退出码存储在 status 变量的高8位(第8~15位),此宏通过位运算提取该值。

3、WIFSIGNALED(status)

功能:判断子进程是否被信号终止(如被 kill 命令、段错误等信号杀死)。
返回值:若被信号终止,返回非0(真);否则返回0(假)。
使用场景:若 WIFEXITED 为假,可用此宏进一步判断是否为信号终止。

4、WTERMSIG(status)

意思是:Wait Terminate(终止) Signal,从 wait 的状态值中,提取导致进程终止的信号
功能:获取导致子进程被信号终止的信号编号(如 SIGSEGV 为11,SIGKILL 为9)。
依赖条件:必须在 WIFSIGNALED(status) 为真时调用,否则结果未定义。
原理:信号编号存储在 status 变量的低7位(第0~6位)。

5、WCOREDUMP(status)

功能:判断子进程是否生成了核心转储文件(core dump)。
返回值:若生成core文件,返回非0(真);否则返回0(假)。
依赖条件:仅在 WIFSIGNALED(status) 为真时有效。
原理:core dump 标志位是 status 的第7位。

2、什么是阻塞式等待

什么叫阻塞式等待,其实就是父进程什么都不干,一直在傻傻等着子进程运行结束,然后接受子进程的退出码。
具体表现:当父进程调用 wait() 或 waitpid()(不带 WNOHANG 选项)时,如果子进程还没有结束,父进程就会立刻停下来,卡在那一行代码上。
期间行为:在这段等待的时间里,父进程不会去执行后续的代码,看起来就像是“卡死”了一样,直到子进程发出“我干完了”的信号,父进程才会“醒”过来,去拿退出码并继续运行。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
    printf("我是父进程:pid = %d\n",getpid());

    pid_t id = fork();

    if(id < 0)
    {
        perror("fork");
        exit(1);
    }
    else if(id == 0) // child
    {
        printf("子进程的pid=%d\n",getpid());
        int cnt = 5;
        while(cnt--)
        {
            printf("child process is runing\n");
            sleep(1);
        }
        exit(111); // 子进程的返回值是111
    }
    else// parrent
    {
        int status = 0;
        int ret = waitpid(id,&status,0);
        // pid=-1等待任意一个子进程,pid>0等待指定的子进程,option=0,表示阻塞等待 
        // 相当于子进程在运行时,父进程什么都没干,就是在傻等
        
        if(WIFEXITED(status) && ret == id) // 判断是不是正常退出,waitpid函数返回值是子进程的id值
        {
			printf("exit_code = %d\n",WEXITSTATUS(status)); // 是正常退出的,所以打印退出码
		}

		if(WIFSIGNALED(status)) // 判断是不是因为信号退出的
		{
			printf("sig_code = %d\n",WTERMSIG(status)); // 打印信号码
		}
    }
	return 0; 
}
[benjiangliu@VM-4-8-centos lesson21]$ ./waitpid阻塞版本 
我是父进程:pid = 3707
子进程的pid=3708
child process is runing
child process is runing
child process is runing
child process is runing
child process is runing
child pid ret =  3708
exit_code = 111 // 此处与子进程的退出码一致

当我在程序运行起来,而另一终端输入kill -2 +子进程的pid值时

[benjiangliu@VM-4-8-centos ~]$ kill -2 21859

[benjiangliu@VM-4-8-centos lesson21]$ ./宏定义运用 
我是父进程:pid = 21857
子进程的pid=21859
child process is runing
child process is runing
child process is runing
child process is runing
child process is runing
child process is runing
child process is runing
child process is runing
child process is runing
sig_code = 2 // 可以看到退出码是2

因为waitpid函数的参数status低7位是信号码,高8位是退出码
我们之前使用的是位运算:
1、将status的值整体右移8位(status >> 8),得到的退出码。
2、将status的值整体与上0x7f (&0111 1111),得到信号码。
但是在实际编程中,永远使用 <sys/wait.h> 提供的宏(如 WEXITSTATUS, WTERMSIG, WCOREDUMP)来解析 status,而不是自己写位运算,因为不同系统可能有差异。

3、什么是非阻塞式等待

很简单,其实就是主进程干自己的任务,是不是的回头瞥一眼子进程有没有运行结束,如果运行结束了,就使用wait函数接受子进程的退出码\信号码,父进程不会傻傻得啥都不干,就等着子进程运行结束。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("现在创建一个进程:pid:%d,ppid:%d\n",getpid(),getppid());

    pid_t id = fork();
    if(id < 0)
    {
        perror("fork\n");
        exit(1);
    }

    if(id == 0)
    { // child
        int cnt = 5;
        while(cnt--)
        {
            printf("我是子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
        }

        int* ptr = NULL;
        *ptr = 10;
        // 修改后,实现空指针解引用报错,查看退出码和信号码
        exit(1);
    }
    else if(id > 0)
    {
        int status = 0;
        int ret = 0;
        int sum = 0;
        int num = 0;
        do
        {
            ret = waitpid(id,&status,WNOHANG);// 读取子进程的状态
            if(ret == 0)
            {
                printf("child is running.\n");
                usleep(100000);
            }// WNOHANG == 1,意味着非阻塞等待

            num = 2+num;
            sum +=num; // 父进程一直在计算偶数的和,相当于不耽误父进程的工作 
        }while(ret == 0);
        // 只要返回值==0,父进程就认为子进程没有运行结束结束
   
        if(WIFEXITED(status) && ret==id) 
        {
            printf("wait child 5s sucess,child return code is %d,sum = %d\n",WEXITSTATUS(status),sum); 
        }
        else if(WIFSIGNALED(status))
        {
            printf("wait child failed,exit_code is %d,sig_code = %d,sum = %d\n",WEXITSTATUS(status),WTERMSIG(status),sum); 
        }
        // WIFEXITED(status) 在子进程退出时就是默认就是 1
        // ret == id ,waitpid函数返回值为子进程的pid
        // 这两点同时存在,说明子进程已经成功返回了
        // 若不存在进入else执行故障打印
    }
    return 0;
}

```bash
[benjiangliu@VM-4-8-centos lesson21]$ ./waitpid_非阻塞 
现在创建一个进程:pid:1206,ppid:27032
child is running.
我是子进程,pid:1207,ppid:1206
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
我是子进程,pid:1207,ppid:1206
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
我是子进程,pid:1207,ppid:1206
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
我是子进程,pid:1207,ppid:1206
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
我是子进程,pid:1207,ppid:1206
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
child is running.
wait child failed,exit_code is 0,sig_code = 11,sum = 2652

代码中设置了空指针解引用操作,所以导致了段错误,引发错误码11

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐