任务描述
在上一关我们学习使用fork
函数创建新进程,本关我们将介绍如何另一种创建新进程的系统调用函数。
本关任务:学会使用C
语言在Linux
系统中使用vfork
系统调用创建一个新的进程。
相关知识
在上一关卡中,我们介绍了fork
的使用方法。使用fork
创建的子进程的特点是:(1)子进程采用写时复制(COW
)技术来为子进程创建地址空间;(2)子进程和父进程的执行顺序是由操作系统调度器来决定的。
本关将介绍Linux
系统中另一个创建进程的系统调用函数vfork
。vfork
函数是一个历史遗留产物。vfork
创建进程与fork
创建的进程主要有一下几点区别:
- vfork创建的子进程与父进程共享所有的地址空间,而fork创建的子进程是采用COW技术为子进程创建地址空间;
- vfork会使得父进程被挂起,直到子进程正确退出后父进程才会被继续执行,而fork创建的子进程与父进程的执行顺序是由操作系统调度来决定。
vfork
性能要比fork
高,主要原因是vfork
没有进行所有数据的复制,尽管fork
采用了COW
技术优化性能,但是也会为子进程的页表项进行复制,因此vfork
要比fork
快。
使用vfork
时要注意,在子进程中对共享变量的修改也会影响到父进程,因此vfork
在带来高性能的同时,也使得整个程序容易出错,因此,开发人员在使用vfork
创建进程时,一定要注意对共享数据的修改。
由于vfork
创建的子进程和父进程共享所有的数据(栈、堆等等),因此,采用vfork
创建的子进程必须使用exit
或者exec
函数族(下一关将介绍这些函数的功能)来正常退出,不能使用return
来退出。
exit
函数是用来结束正在运行的整个程序,exit
是系统调用级别,它表示一个进程的结束;而return
是语言级别的,它表示调用堆栈的返回。
vfork
函数是系统调用函数,man 2 vfork
来查看其使用方法。而exit
函数是库函数,因此使用man 3 exit
来查看其使用方法。
使用vfork
函数创建进程
vfork
函数的具体的说明如下:
- 需要的头文件如下:
- i. #include <sys/types.h>
- ii. #include <unistd.h>
- 函数格式如下:
pid_t vfork(void);
- 函数返回值说明: 调用成功,
vfork
函数两个值,分别是0
和子进程ID
号。当调用失败时,返回-1
,并设置错误编号errno
。
注意:vfork
函数调用将执行两次返回,它将从父进程和子进程中分别返回。从父进程返回时的返回值为子进程的 PID
,,而从子进程返回时的返回值为0
,并且返回都将执行vfork
之后的语句。vfork
创建的子进程必须调用exit
函数来退出子进程。
案例演示1
: 编写一个程序,使用vfork
函数创建一个新进程,并在子进程中打印出其进程ID
和父进程ID
,在父进程中返回进程ID
。详细代码如下所示:
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <string.h>
- #include <errno.h>
- int main()
- {
- pid_t pid;
- pid = vfork();
- if(pid == -1)
- {
- //创建进程失败
- printf("创建进程失败(%s)!\n", strerror(errno));
- return -1;
- }
- else if(pid == 0)
- {
- //子进程
- sleep(2); //睡眠2秒
- printf("当前进程为子进程:pid(%d),ppid(%d)\n", getpid(), getppid());
- }
- else
- {
- //父进程
- printf("当前进程为父进程:pid(%d),ppid(%d)\n", getpid(), getppid());
- }
- //子进程和父进程分别会执行的内容
- exit(0);
- }
将以上代码保存为vforkProcess.c
文件,编译执行。可以看到vforkProcess
创建的子进程尽管使用sleep
函数睡眠了2
秒,但是函数父进程的执行顺序在子进程后,这就是vfork
的特性。
当我们将以上代码中的exit(0)
换成return 0
时,则会出现如下错误。
出现以上错误的原因是当子进程使用return
退出时,操作系统也会把栈清空,那么当父进程继续使用return
退出时,则会发现栈已经被清空了,这就相当于free
两次同一块内存,因此会出现错误。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
- 补全
createProcess
函数,使用vfork
函数创建进程,并在子进程中输出"Children"字符串(提示:需要换行),在父进程中输出"Parent"字符串(提示:需要换行)。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。
任务描述
在上一关我们学习使用vfork
函数创建新进程,并且使用exit
来结束子进程,本关我们将介绍Linux
系统中结束进程的其它方法。
本关任务:学习终止进程的常见方法。
相关知识
在上一关以及看到,开发人员使用vfork
创建出来的子进程可以用exit
函数来结束。在 Linux
环境中,一个进程的结束,可以通过调用相应的函数实现,也可以是接收到某个信号而结束。
常见与退出进程相关的函数有:exit
、_exit
、atexit
、on_exit
、abort
和assert
。
exit
函数是标准C
库中提供的函数,它用来终止正在运行的程序,并且关闭所有I/O
标准流。_exit
函数也可用于结束一个进程,与exit
函数不同的是,_exit
不会关闭所有I/O
标准流。atexit
函数用于注册一个不带参数也没有返回值的函数以供程序正常退出时被调用。on_exit
函数的作用与atxeit
函数十分类似,不同的是它注册的函数具有参数,退出状态和参数arg
都是传递给该程序使用的。- abort 函数其实是用来发送一个
SIGABRT
信号,这个信号将使当前进程终止。 assert
是一个宏。调用assert
时,它将先计算参数表达式expression
的值,如果为0
,则调用abort
函数结束进程。
[exit
和_exit
区别]
以上关于退出处理函数中只有_exit
是系统调用函数,因此使用man 2 _exit
来查看其使用方法,而其余函数都是库函数,因此使用man 3
函数名
来查看其使用方法。
exit
和_exit
使用方法
exit
函数的具体的说明如下:
- 需要的头文件如下:
- i. #include <stdlib.h>
- 函数族格式如下:
- i. void exit(int status);
参数说明: status:设置程序退出码;
_exit
函数的具体的说明如下:
- 需要的头文件如下:
- i. #include <unistd.h>
- 函数族格式如下:
- i. void _exit(int status);
参数说明: status
:设置程序退出码;
- 函数返回值说明:
exit
和_exit
均无返回值。
atexit
和on_exit
使用方法
atexit
和on_exit
函数的具体的说明如下:
- 需要的头文件如下:
- i. #include <stdlib.h>
- 函数族格式如下:
- i. int atexit(void (*function)(void));
- ii. int on_exit(void (*function)(int , void *), void *arg);
参数说明: atexit
函数的function
参数是一个函数指针,指向无返回值和无参数的函数; on_exit
函数的function
参数是一个函数指针,指向无返回值和有两个参数的函数,其中第一个参数是调用exit()
或从main
中返回时的值,参数arg
指针会传给参数function
函数;
- 函数返回值说明:
atexit
和on_exit
调用成功返回0
;调用失败返回一个非零值。
注意:atexit
和on_exit
只有在程序使用exit
或者main
中正常退出时才会有效。如果程序使用_exit
、abort
或assert
退出程序时,则不会执行被注册的函数。
案例演示1
: 使用atexit
注册一个退出函数,使其在调用退出函数前被执行,详细代码如下所示:
- #include <stdlib.h>
- #include <stdio.h>
- void out()
- {
- printf("程序正在被退出\n");
- }
- int main()
- {
- if(atexit(out) != 0)
- {
- printf("调用atexit函数错误\n");
- }
- return 0; //或者exit(0)
- }
将以上代码保存为atexit.c
文件,编译执行。可以看到执行atexit
程序后,out
函数被调用。
案例演示2
: 使用on_exit
注册一个退出函数,使其在调用退出函数前被执行,详细代码如下所示:
- #include <stdlib.h>
- #include <stdio.h>
- void out(int status, void *arg)
- {
- printf("%s(%d)\n", (char *s)arg, status);
- }
- int main()
- {
- if(on_exit(out, "程序正在被退出") != 0)
- {
- printf("调用on_exit函数错误\n");
- }
- exit(1); //或者return 1
- }
将以上代码保存为on_exit.c
文件,编译执行。可以看到执行on_exit
程序后,out
函数被调用,并且status
变量的值就是exit
函数退出的值。
abort
和assert
使用方法
abort
函数的具体的说明如下:
- 需要的头文件如下:
- i. #include <stdlib.h>
- 函数族格式如下:
- i. void abort(void);
assert
宏的具体的说明如下:
- 需要的头文件如下:
- i. #include <assert.h>
- 函数族格式如下:
- i. void assert(scalar expression);
参数说明: expression
:需要被判断的表达式;
注意:assert
宏通常用于调试程序。
- 函数返回值说明:
abort
和assert
无返回值。
案例演示1: 使用abort
终止一个程序,详细代码如下所示:
- #include <stdlib.h>
- #include <stdio.h>
- int main()
- {
- printf("Hello world\n");
- abort();
- }
将以上代码保存为abort.c
文件,编译执行。可以看到执行abort
程序后,程序被强行终止。
编程要求
本关的编程任务是补全右侧代码片段中Begin
至End
中间的代码,具体要求如下:
- 补全
exitProcess
函数,使用atexit
函数注册一个函数,在注册函数中打印出当前进程的ID
号。
测试说明
本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。