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的迁移。
目录
相关文章
|
6天前
|
算法 调度 UED
探索操作系统核心:进程管理与调度
【9月更文挑战第28天】在数字世界的心脏跳动着无数进程,它们像是细胞一样构成了操作系统的生命体。本文将深入探讨操作系统中进程管理与调度的奥秘,揭示如何通过精心设计的数据结构和算法来维护系统的稳定性和效率。我们将从进程的基本概念出发,逐步解析进程状态转换、进程同步机制,以及进程调度策略,旨在为读者呈现一幅清晰、生动的操作系统内部工作机制图景。
|
10天前
|
算法 调度 UED
探索操作系统中的进程调度:理论与实践
【9月更文挑战第24天】 在数字世界的心脏跳动着的是操作系统,它像一位精明的指挥家,精心安排每个音符的演奏。本文将带你进入操作系统的内核,一探进程调度的秘密。我们将从简单的批处理系统谈起,穿越时间隧道,见证现代多道程序设计系统的复杂性与优雅。你将看到代码如何赋予理论以生命,理解调度算法背后的哲理。让我们一起跟随甘地的指引,成为我们希望在世界上看到的改变。
|
2天前
|
算法 调度 UED
探索操作系统的心脏:进程调度算法
【9月更文挑战第32天】在数字世界的每一次心跳中,都隐藏着一个不为人知的英雄——进程调度算法。它默默地在后台运作,确保我们的命令得到快速响应,应用程序平稳运行。本文将带你走进操作系统的核心,一探进程调度的奥秘,并通过代码示例揭示其背后的智慧。准备好跟随我一起深入这趟技术之旅了吗?让我们开始吧!
|
4天前
|
算法 Linux 调度
深入理解操作系统的进程调度
【9月更文挑战第30天】本文将带你进入操作系统的核心—进程调度。我们将探讨其工作原理,分析几种常见的调度算法,并通过实际代码示例来揭示这些理论是如何在真实系统中实现的。无论你是初学者还是有经验的开发者,这篇文章都能帮助你更好地理解操作系统的这一关键组成部分。
|
4天前
|
消息中间件 算法 调度
探索操作系统核心:进程管理与调度策略
【9月更文挑战第30天】在数字化时代的心脏,操作系统扮演着至关重要的角色。本文将深入探讨操作系统的基石之一——进程管理,以及如何通过调度策略优化系统性能。我们将从进程的基本概念出发,逐步解析进程状态、进程控制和进程间通信等关键要素。同时,我们会探讨几种常见的进程调度算法,并分析它们的优缺点。最后,文章将展示一个简单的代码示例,以加深对理论部分的理解和应用。
|
5天前
|
算法 调度 UED
探索操作系统的心脏:进程管理与调度
【9月更文挑战第29天】在数字世界的海洋中,操作系统是支撑软件与硬件和谐共舞的桥梁。本文将深入探讨操作系统的核心功能—进程管理及其调度机制,揭示它们是如何影响计算机性能和用户体验的。通过浅显易懂的语言和生动的比喻,我们将一起遨游在进程的生命周期、调度算法以及优先级等概念之间,旨在为读者呈现一个清晰的操作系统内部运作图景。
17 6
|
4天前
|
算法 调度 开发者
深入理解操作系统之进程管理与调度
【9月更文挑战第30天】本文旨在通过浅显易懂的语言和具体代码示例,带领读者探索操作系统中进程管理的奥秘。我们将从进程的生命周期出发,逐步解析进程调度的核心概念,并通过实例展示如何实现简单的进程调度算法。无论你是初学者还是有一定基础的开发者,都能在这篇文章中找到有价值的信息,帮助你更好地理解和掌握进程管理与调度的知识。
14 4
|
6天前
|
算法 调度
操作系统的心脏:深入解析进程调度算法
本文旨在深入探讨现代操作系统中的核心功能之一——进程调度。进程调度算法是操作系统用于分配CPU时间片给各个进程的机制,以确保系统资源的高效利用和公平分配。本文将详细介绍几种主要的进程调度算法,包括先来先服务(FCFS)、短作业优先(SJF)、时间片轮转(RR)以及优先级调度(PS)。我们将分析每种算法的基本原理、优缺点及其适用场景。同时,本文还将讨论多级反馈队列(MFQ)调度算法,并探讨这些算法在实际应用中的表现及未来发展趋势。通过深入解析这些内容,希望能够为读者提供对操作系统进程调度机制的全面理解。
|
7天前
|
算法 调度 UED
探索操作系统中的进程调度
【9月更文挑战第27天】操作系统是计算机的灵魂,而进程调度则是其跳动的心脏。本文将深入浅出地探讨进程调度机制,从理论到实践,带你领略这一技术的魅力和复杂性。我们将通过代码示例,揭示调度算法如何影响系统性能和用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇理解操作系统深层工作原理的大门。
17 6
|
9天前
|
存储 算法 前端开发
深入理解操作系统:进程调度与优先级队列算法
【9月更文挑战第25天】在操作系统的复杂世界中,进程调度是维持系统稳定运行的核心机制之一。本文将深入探讨进程调度的基本概念,分析不同的进程调度算法,并着重介绍优先级队列算法的原理和实现。通过简洁明了的语言,我们将一起探索如何优化进程调度,提高操作系统的效率和响应速度。无论你是计算机科学的初学者还是希望深化理解的专业人士,这篇文章都将为你提供有价值的见解。
下一篇
无影云桌面