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 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
4天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
19 9
|
3天前
|
消息中间件 安全 算法
深入理解操作系统:进程管理的艺术
【10月更文挑战第38天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是硬件与软件的桥梁,更是维持计算机运行秩序的守夜人。本文将带你走进操作系统的核心——进程管理,探索它是如何协调和优化资源的使用,确保系统的稳定与高效。我们将从进程的基本概念出发,逐步深入到进程调度、同步与通信,最后探讨进程安全的重要性。通过这篇文章,你将获得对操作系统进程管理的全新认识,为你的计算机科学之旅增添一份深刻的理解。
|
3天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
18 2
|
6天前
|
算法 调度 UED
深入理解操作系统:进程管理与调度策略
【10月更文挑战第34天】本文旨在探讨操作系统中至关重要的一环——进程管理及其调度策略。我们将从基础概念入手,逐步揭示进程的生命周期、状态转换以及调度算法的核心原理。文章将通过浅显易懂的语言和具体实例,引导读者理解操作系统如何高效地管理和调度进程,保证系统资源的合理分配和利用。无论你是初学者还是有一定经验的开发者,这篇文章都能为你提供新的视角和深入的理解。
23 3
|
7天前
|
缓存 网络协议 Linux
Linux操作系统内核
Linux操作系统内核 1、进程管理: 进程调度 进程创建与销毁 进程间通信 2、内存管理: 内存分配与回收 虚拟内存管理 缓存管理 3、驱动管理: 设备驱动程序接口 硬件抽象层 中断处理 4、文件和网络管理: 文件系统管理 网络协议栈 网络安全及防火墙管理
29 4
|
8天前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
6天前
|
安全 网络协议 Linux
Linux操作系统的内核升级与优化策略####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统内核升级的重要性,并详细阐述了一系列优化策略,旨在帮助系统管理员和高级用户提升系统的稳定性、安全性和性能。通过实际案例分析,我们展示了如何安全有效地进行内核升级,以及如何利用调优技术充分发挥Linux系统的潜力。 ####
24 1
|
9天前
|
物联网 Linux 云计算
Linux操作系统的演变与未来趋势####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统从诞生至今的发展历程,分析了其在服务器、桌面及嵌入式系统领域的应用现状,并展望了云计算、物联网时代下Linux的未来趋势。通过回顾历史、剖析现状、预测未来,本文旨在为读者提供一个全面而深入的视角,以理解Linux在当今技术生态中的重要地位及其发展潜力。 ####
|
7天前
|
消息中间件 算法 调度
深入理解操作系统:进程管理的艺术
【10月更文挑战第33天】本文旨在揭示操作系统中进程管理的神秘面纱,带领读者从理论到实践,探索进程调度、同步以及通信的精妙之处。通过深入浅出的解释和直观的代码示例,我们将一起踏上这场技术之旅,解锁进程管理的秘密。
13 0
|
9天前
|
算法 Linux 调度
深入理解操作系统之进程调度
【10月更文挑战第31天】在操作系统的心脏跳动中,进程调度扮演着关键角色。本文将深入浅出地探讨进程调度的机制和策略,通过比喻和实例让读者轻松理解这一复杂主题。我们将一起探索不同类型的调度算法,并了解它们如何影响系统性能和用户体验。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇理解操作系统深层工作机制的大门。
19 0