Linux操作系统实验十一 进程管理(中)

简介: Linux操作系统实验十一 进程管理(中)

任务描述

在上一关我们学习使用fork函数创建新进程,本关我们将介绍如何另一种创建新进程的系统调用函数。

本关任务:学会使用C语言在Linux系统中使用vfork系统调用创建一个新的进程。

相关知识

在上一关卡中,我们介绍了fork的使用方法。使用fork创建的子进程的特点是:(1)子进程采用写时复制(COW)技术来为子进程创建地址空间;(2)子进程和父进程的执行顺序是由操作系统调度器来决定的。

本关将介绍Linux系统中另一个创建进程的系统调用函数vforkvfork函数是一个历史遗留产物。vfork创建进程与fork创建的进程主要有一下几点区别:

  1. vfork创建的子进程与父进程共享所有的地址空间,而fork创建的子进程是采用COW技术为子进程创建地址空间;
  2. 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。详细代码如下所示:

  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <errno.h>

  7. int main()
  8. {
  9.    pid_t pid;
  10.    pid = vfork();
  11.    if(pid == -1)
  12.    {
  13.        //创建进程失败
  14.        printf("创建进程失败(%s)!\n", strerror(errno));
  15.        return -1;
  16.    }
  17.    else if(pid == 0)
  18.    {
  19.        //子进程
  20.        sleep(2);  //睡眠2秒
  21.        printf("当前进程为子进程:pid(%d),ppid(%d)\n", getpid(), getppid());
  22.    }
  23.    else
  24.    {
  25.        //父进程
  26.        printf("当前进程为父进程:pid(%d),ppid(%d)\n", getpid(), getppid());
  27.    }
  28.    
  29.    //子进程和父进程分别会执行的内容
  30.    exit(0);
  31. }

将以上代码保存为vforkProcess.c文件,编译执行。可以看到vforkProcess创建的子进程尽管使用sleep函数睡眠了2秒,但是函数父进程的执行顺序在子进程后,这就是vfork的特性。

当我们将以上代码中的exit(0)换成return 0时,则会出现如下错误。

出现以上错误的原因是当子进程使用return退出时,操作系统也会把栈清空,那么当父进程继续使用return退出时,则会发现栈已经被清空了,这就相当于free两次同一块内存,因此会出现错误。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全createProcess函数,使用vfork函数创建进程,并在子进程中输出"Children"字符串(提示:需要换行),在父进程中输出"Parent"字符串(提示:需要换行)。

测试说明

本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

任务描述

在上一关我们学习使用vfork函数创建新进程,并且使用exit来结束子进程,本关我们将介绍Linux系统中结束进程的其它方法。

本关任务:学习终止进程的常见方法。

相关知识

在上一关以及看到,开发人员使用vfork创建出来的子进程可以用exit函数来结束。在 Linux 环境中,一个进程的结束,可以通过调用相应的函数实现,也可以是接收到某个信号而结束。

常见与退出进程相关的函数有:exit_exitatexiton_exitabortassert

  1. exit函数是标准C库中提供的函数,它用来终止正在运行的程序,并且关闭所有I/O标准流。
  2. _exit函数也可用于结束一个进程,与exit函数不同的是,_exit不会关闭所有I/O标准流。
  3. atexit 函数用于注册一个不带参数也没有返回值的函数以供程序正常退出时被调用。
  4. on_exit 函数的作用与atxeit函数十分类似,不同的是它注册的函数具有参数,退出状态和参数arg都是传递给该程序使用的。
  5. abort 函数其实是用来发送一个SIGABRT信号,这个信号将使当前进程终止。
  6. 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均无返回值。
atexiton_exit使用方法

atexiton_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函数;

  • 函数返回值说明: atexiton_exit调用成功返回0;调用失败返回一个非零值。

注意:atexiton_exit只有在程序使用exit或者main中正常退出时才会有效。如果程序使用_exitabortassert退出程序时,则不会执行被注册的函数。

案例演示1: 使用atexit注册一个退出函数,使其在调用退出函数前被执行,详细代码如下所示:

  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. void out()
  4. {
  5.    printf("程序正在被退出\n");
  6. }

  7. int main()
  8. {
  9.    if(atexit(out) != 0)
  10.    {
  11.        printf("调用atexit函数错误\n");
  12.    }
  13.    
  14.    return 0;   //或者exit(0)
  15. }

将以上代码保存为atexit.c文件,编译执行。可以看到执行atexit程序后,out函数被调用。

案例演示2: 使用on_exit注册一个退出函数,使其在调用退出函数前被执行,详细代码如下所示:

  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. void out(int status, void *arg)
  4. {
  5.    printf("%s(%d)\n", (char *s)arg, status);
  6. }

  7. int main()
  8. {
  9.    if(on_exit(out, "程序正在被退出") != 0)
  10.    {
  11.        printf("调用on_exit函数错误\n");
  12.    }
  13.    
  14.    exit(1);   //或者return 1
  15. }

将以上代码保存为on_exit.c文件,编译执行。可以看到执行on_exit程序后,out函数被调用,并且status变量的值就是exit函数退出的值。

abortassert使用方法

abort函数的具体的说明如下:

  • 需要的头文件如下:
  • i. #include <stdlib.h>
  • 函数族格式如下:
  • i. void abort(void);

assert宏的具体的说明如下:

  • 需要的头文件如下:
  • i. #include <assert.h>
  • 函数族格式如下:
  • i. void assert(scalar expression);

参数说明: expression:需要被判断的表达式;

注意:assert宏通常用于调试程序。

  • 函数返回值说明: abortassert无返回值。

案例演示1: 使用abort终止一个程序,详细代码如下所示:

  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. int main()
  4. {
  5.    printf("Hello world\n");
  6.    
  7.    abort();
  8. }

将以上代码保存为abort.c文件,编译执行。可以看到执行abort程序后,程序被强行终止。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 补全exitProcess函数,使用atexit函数注册一个函数,在注册函数中打印出当前进程的ID号。

测试说明

本关的测试需要用户在右侧代码页中补全代码,然后点击评测按钮,平台会自动验证用户是否按照要求去检测结果。

相关实践学习
CentOS 8迁移Anolis OS 8
Anolis OS 8在做出差异性开发同时,在生态上和依赖管理上保持跟CentOS 8.x兼容,本文为您介绍如何通过AOMS迁移工具实现CentOS 8.x到Anolis OS 8的迁移。
目录
相关文章
|
1月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
5天前
|
存储 Linux iOS开发
【Linux】冯诺依曼体系与操作系统理解
本文深入浅出地讲解了计算机体系的两大核心概念:冯诺依曼体系结构与操作系统。冯诺依曼体系作为现代计算机的基础架构,通过中央处理器、存储器和输入输出设备协同工作,解决了硬件性能瓶颈问题。操作系统则是连接硬件与用户的桥梁,管理软硬件资源,提供运行环境。文章还详细解析了操作系统的分类、意义及管理方式,并重点阐述了系统调用的作用,为学习Linux系统编程打下坚实基础。适合希望深入了解计算机原理和技术内幕的读者。
29 1
|
1月前
|
Linux
Linux 操作系统
在 Linux 中,UID(用户 ID)是标识用户身份的重要概念。UID 唯一标识每个用户,通过 UID 可区分不同用户类别:UID 0 为超级用户,1-999 为系统用户,1000 及以上为普通用户。因此,正确选项为:UID 标识用户、可区分用户类别、普通用户 UID 大于 1000。
|
4天前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
21 4
|
1月前
|
NoSQL Unix Linux
Linux 操作系统的诞生与发展历程
步探索与准备: 1991年初,林纳斯·托瓦兹开始在一台386sx兼容微机上学习minix操作系统。通过学习,他逐渐不能满足于minix系统的现有性能,并开始酝酿开发一个新的免费操作系统。
74 8
Linux 操作系统的诞生与发展历程
|
1月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
87 34
|
23天前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
30 7
|
22天前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
37 5
|
22天前
|
Linux 应用服务中间件 nginx
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep &lt;进程名&gt;` 查找特定进程;`ps -p &lt;PID&gt;` 查看指定 PID 的进程详情。终止进程可用 `kill &lt;PID&gt;` 或 `pkill &lt;进程名&gt;`,强制终止加 `-9` 选项。
24 3

热门文章

最新文章