山东大学操作系统实验一(Linux虚拟机实现)

news2024/12/25 22:20:35

目录

实验题目

实验要求

示例程序 

主程序

头文件 

 重点代码解析

一、main函数的参数

参数介绍

参数输入方式

本块代码

二、信号处理 

本块代码

原理介绍

实现效果

三、kill函数

功能介绍

使用方式

本块代码

四、头文件处理

本块代码

代码作用 

实验程序 

运行效果 

实验反思 

总结


实验题目

参考以上示例程序中建立并发进程的方法,编写一个父子协作进程,父进程创建一个子进程并控制它每隔3秒显示一次当前目录中的文件名列表

实验要求

1、说明它们反映出操作系统教材中进程及处理机管理一节讲解的进程的哪些特征和功能?

2、在真实的操作系统中它是怎样实现和反映出教材中讲解的进程的生命期、进程的实体和进程状态控制的?

3、你对于进程概念和并发概念有哪些新的理解和认识?

4、子进程是如何创建和 执行新程序的?

5、信号的机理是什么?怎样利用信号实现进程控制?

示例程序 

主程序

#include "pctrl.h"

int main(int argc, char *argv[]) {
    printf("参数有%d个\n", argc);
    int pid; //存放子进程号
    int status; 存放子进程返回状态
    char *args[] = {"/bin/ls", "-al", NULL};
    signal(SIGINT, handler);
    pid = fork();
    if (pid < 0) {
        printf("创建进程失败!\n");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        printf("我是子进程 %d , 我的父进程是 %d\n", getpid(), getppid());
        // 暂停, 知道受到信号,(受到信号后可以默认处理,也可以自定义处理函数)
        printf("子进程pause了\n");
        pause();
        sleep();
        printf("%d 子进程要继续执行: ", getpid());
        printf("我是子进程 %d , 现在我的父进程是 %d\n", getpid(), getppid());
        // 执行命令行的命令
        if (argv[1] != NULL) {
            for (int i = 1; argv[i] != NULL; i++) {
                printf("%s ", argv[i]);
            }
            printf("\n");
            status = execve(argv[1], &argv[1], NULL);
        } else {
            for (int i = 0; args[i] != NULL; i++) {
                printf("%s ", args[i]);
            }
            printf("\n");
            status = execve(args[0], args, NULL);
        }
        // 下面这行代码不会执行到,因为地址空间已经被替换了
        printf("子进程退出\n");
    } else {
        sleep(2);
        printf("父进程睡眠2秒结束\n");
        printf("我是父进程 %d\n", getpid());
        //如果在命令行上输入了子进程要执行的命令, 则父进程等待子进程执行结束
        if (argv[1] != NULL) {
            printf("等待子进程结束\n");
            waitpid(pid, &status, 0);
            printf("我的子进程的退出状态是 %d\n", status);
        } else {
            //如果在命令行上没输入子进程要执行的命令,唤醒子进程,与子进程并发执行不等待子进程执行结束
            if (kill(pid, SIGINT) >= 0) {
                printf("%d 父进程唤醒了 %d 子进程\n", getpid(), pid);
            }
            printf("%d 进程没有等待子进程运行结束\n", getpid());
        }
    }
    printf("父进程退出\n");
    return EXIT_SUCCESS;
}

头文件 

#ifndef PCTRL_H
#define PCTRL_H

#include <sys/types.h>
#include <wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//进程自定义的键盘中断信号处理函数
void handler(int sig){
    printf("收到信号 %d\n",sig);
    printf("%d 进程继续\n",getpid());
}

#endif //PCTRL_H

示例程序来源 :山东大学《操作系统原理实用实验教程》张鸿烈老师编著

 重点代码解析

一、main函数的参数

参数介绍

在C语言中,main函数是程序的入口点,它可以接受两个参数

  1. int argc: 这个参数是一个整数,用来表示命令行参数的数量argc是“argument count”的缩写,其中argc的值至少为1,因为第一个参数总是程序的名称(完整路径的名称)

  2. char *argv[]: 这是一个字符指针数组,用来存储命令行参数的字符串。argv是“argument vector”的缩写,它是一个以空指针结尾的数组。每个argv[i]指向一个字符串,其中argv[0]通常指向本地main函数所在程序的名称argv[1]指向第一个命令行参数,依此类推

参数输入方式

这两个参数是由操作系统传递给程序的,当你从命令行运行一个C程序时,你可以提供一些参数,这些参数会被操作系统传递给main函数。如下:

./example arg1 arg2 arg3

此时 arg1 arg2 arg3 三个参数就被传递给 example函数中的main的argv中

本块代码
char *args[] = {"/bin/ls", "-al", NULL};
status = execve(args[0], args, NULL);
status = execve(argv[1], &argv[1], NULL);

1、execve():用于替换当前进程映像、数据和堆栈等信息,加载并执行新的程序

第一个参数:新程序的路径

第二个参数:传递给新程序的参数

第三个参数:NULL

返回值:如果execve函数成功执行,execve函数的返回值为0。并且它将不会返回到调用者程序,因为整个进程的上下文已经被替换。如果发生错误,execve函数将返回-1,并设置errno变量以指示具体的错误类型

注:exec函数成功执行,则写在exec函数后面的代码段不会执行(全部被替换成新程序)

2、从命令行执行程序时,会按照如下提供argv参数

./example /bin/ls '-la'
//-la 和 -al是相同的

二、信号处理 

本块代码

void handler(int sig){
    printf("收到信号 %d\n",sig);
    printf("%d 进程继续\n",getpid());
}
signal(SIGINT, handler);

原理介绍

signal函数:注册函数,为信号绑定对应的处理函数。

  1. SIGINT 是一个信号,通常由用户通过键盘(如按下 Ctrl+C)发送给进程,表示中断信号
  2. handler 是一个函数指针,指向用户定义的信号处理函数,该函数将在接收到 SIGINT 信号时被调用

实现效果

用户通过键盘按下 Ctrl+C——>发送SIGINT信号给进程——>进程激活运行(如果本来是pause的话)——>调用handler函数

三、kill函数

功能介绍

int kill(pid_t pid, int sig);

kill函数的主要功能:向指定的进程或进程组发送一个特定的信号,这个信号可以是终止进程的信号(如SIGTERM),也可以是其他类型的信号,例如暂停进程(SIGSTOP)、恢复进程(SIGCONT)、中断信号(SIGINT)等

使用方式

1、第一个参数:pid指的是操作目标进程的进程标识符pid值

2、第二个参数:sig是要发送的信号代码,所谓的信号代码就指代具体的信号

3、返回值:如果成功发送信号给目标进程或进程组,则返回值为0。如果发生错误,则返回值为-1

4、常见的信号代码: 

  • SIGTERM:终止信号,可以被捕获和处理。
  • SIGKILL:强制终止信号,不能被捕获或忽略。
  • SIGSTOP:暂停信号,使进程停止执行。
  • SIGCONT:继续执行信号,使暂停的进程恢复执行。
  • SIGINT:中断信号,使进程收到中断信号

本块代码

if(kill(pid, SIGINT)) >= 0

向进程号为pid的子进程发送一个中断信号,子进程原处于等待状态,收到中断信号后转为ready状态并迅速进入running状态

四、头文件处理

本块代码

#ifndef PCTRL_H
#define PCTRL_H
//~~头文件具体内容
#endif

代码作用 

1、#ifndef PCTRL_H如果预处理器没有找到名为"PCTRL_H"的宏定义,那么它会执行后续的代码,直到遇到#endif为止。这通常用于检查一个头文件是否已经被包含过,以防止重复包含导致的编译错误

2、#define PCTRL_H ——— #endif———表示ifn条件满足时具体要执行的头文件的内容

3、PCTRL_H:表示这个头文件的名称为:pctrl.h。调用该头文件时用 include "pctrl.h"

注意:头文件与调用程序必须在同一个文件目录下,gcc编译运行才会成功 

实验程序 

其实,深刻理解前面的示例程序后,完成实验的程序就非常轻松了。下面给出本人写的实验程序代码:

(切记!!不要直接抄过去,必须要自己进行修改,老师会一个个看过来查重的。还有可能被当面提问~~)

(猫猫希望大家都好好自己独立完成操作系统实验,而不是cv上交。所以不打算剪切代码块) 

运行效果 

一、提供argv参数,运行参数为:

./expr_1 /bin/ls '-la' 

二、不提供argv参数,采用程序默认的参数

特别解释:

1、父进程不等待子进程,当父进程运行结束时,子进程会被交给init接管,而shell会认为子进程是父进程的一部分,父进程结束了整体也就结束了。这就是为什么“子程序交给init接管”语句前面会出现“chen@chenVBoxLinux:~$ ” 

2、之后子进程仍然将独立的完成一整个任务,但是子进程运行结束后并不会和父进程一样在shell中自己跳出“chen@chenVBoxLinux:~$ ” ,而是光标一直闪烁,如下:

这个原因是在shell看来这个进程已经运行结束了,所以它不再监视子进程的情况,完全交给init来处理。而init处理结束后,并不会通知shell这个子进程结束了,所以这里光标就会停留在这边,显得子进程一直无法结束

注:文中用来makefile对项目的编译、运行进行了管理和简化,在下一篇文章中我再细讲 

实验反思 

本块重点来回答开头提到的几个问题:

1、它们反映出操作系统教材中进程及处理机管理一节讲解的进程的哪些特征和功能?

  • 独立性:每个进程都有自己的代码和数据以及堆栈等资源,进程之间的地址空间相互独立。

  • 动态性:进程的创建、执行、暂停、终止等状态随着时间不断变化。

  • 并发性:在多任务操作系统中,多个进程可以并发执行。

  • 异步性:进程的执行速度取决于自身与外界事件且以不可预知的速度向前推进。想要实现上文程序的结果,需要在两个进程的程序中合适的地方添加sleep()函数。

  • 交互性:进程可以通过系统调用与其他进程通信,如上面程序父进程通过kill函数和子进程通信

  • 可调度性:进程可以被操作系统调度,以便在多任务环境中共享CPU和其他资源。

  • 生命周期:进程从创建到终止有一个生命周期,包括就绪、运行、阻塞等状态。如上面程序父进程通过wait()函数从running变为waiting,子进程通过pause()函数由running变为waiting等

  • 优先级:进程可能有优先级,用于调度程序决定哪个进程获取CPU时间。

  • 家族关系:进程可以创建其他进程,形成父子关系,父进程负责子进程的管理。

  • 状态转换:进程在其生命周期内会在不同状态之间转换,如从运行状态到等待状态,再到就绪状态。这个转化上文程序通过中断来进行(包括pause、kill两个函数引发的中断)

  • 上下文:进程有自己的执行上下文,包括寄存器值、CPU状态等,这是在进程切换时必须保存和恢复的信息。

  • 隔离性:正常情况下,一个进程的执行不会影响其他进程。

  • 可中断性:进程执行可以被中断,例如因为I/O操作、外部信号或时间片用尽。

2、上面程序对进程的生命期、进程的实体和进程状态控制的理解

进程的生命期包括以下阶段:

  1. 创建:进程通过系统调用(上面程序父进程通过fork创建子进程)或某种形式的初始化被创建。

  2. 就绪:新创建的进程进入就绪状态,等待被操作系统调度以占用CPU执行。

  3. 运行:当操作系统调度器选择该进程运行时,它进入运行状态。

  4. 阻塞/等待:如果进程需要等待某些事件(子进程通过pause开始等待中断信号,父进程通过wait来等待子进程完成),它将进入阻塞或等待状态。

  5. 终止:一旦进程完成其任务或者由于某种原因(父进程因为return结束自己的生命周期,子进程通过运行结束ls函数结束自己的生命周期)而结束,它将进入终止状态。

进程实体是指与进程关联的所有资源和结构,其中包括:

  • 代码段:程序代码在内存中的部分(上述代码中利用exec函数切换了整个子进程的代码段、数据段、堆栈等)(exec改变了进程的PCB中与执行相关的部分,而没有改变整个PCB的存在)
  • 数据段:用于存储全局变量等数据的内存区域。
  • :用于存储动态分配的数据的内存区域。
  • :用于存储局部变量和函数调用信息的内存区域。
  • 进程控制块(PCB):包含进程ID、优先级、状态、程序计数器、CPU寄存器、CPU使用时间、I/O状态等信息的数据结构。
  • 打开文件列表:记录进程当前打开的文件(上面代码中利用bin中的ls函数打开了文件夹)
  • 用户ID和组ID:标识进程所有者的安全信息。

操作系统通过以下方式控制进程的状态转换:

  1. 调度器:负责选择下一个要运行的就绪状态进程,并分配CPU给它。

  2. 时钟中断:周期性中断使得操作系统可以实施时间片轮转,导致当前运行的进程被暂停,另一个就绪进程被选中执行。

  3. I/O中断:当进程等待I/O操作完成时,它会转入阻塞状态;一旦I/O完成,它会被转移到就绪状态。

  4. 信号:接收到特定信号的进程可能会被杀死、进入暂停状态或执行其他行为。

  5. 系统调用:例如,exit会导致进程终止,wait会改变进程状态,等等。

  6. 异常处理:硬件异常或软件异常可能导致进程状态的改变。

  7. 用户干预:用户可以通过命令行工具或系统调用来影响进程的状态,如使用kill命令终止进程。

  8. 父进程控制:父进程可以创建、终止和监控子进程的状态。

3、你对于进程概念和并发概念有哪些新的理解和认识?

  • 进程是程序在计算机中运行的实例
  • 进程本身也是一个程序,特殊的程序
  • 进程的管理依赖于进程控制块(PCB)
  • 进程本身存储在内存中,有特定的存储形式
  • 并发并不是真正的同时执行,而是利用内核的调度,在逻辑上让观察者感觉许多程序在按顺序同时的执行。实际上CPU上每一刻只有一个程序资源在运行
  • 并行是程序真正的在CPU上同时执行

 4、子进程是如何创建和执行新程序的?

利用execve()函数将一个新程序装入并替代子进程中的原程序

5、 信号的机理是什么?怎样利用信号实现进程控制?

本质:是一种软件层次上对中断机制的模拟,用于在进程和内核之间进行异步通信

(即其中一个进程发送信号给内核,内核再根据信号的类型对另一个进程调用系统调用,从而完成模拟完成中断)

上面程序利用:kill()函数实现父进程控制子进程。在子进程阻塞后,父进程利用kill函数向子进程发送了SIGINT信号(中断信号),从而激活了子进程

总结

本文到这里就结束啦~~

本篇文章重点在于利用linux系统的完成操作系统的实验,巩固课堂知识

本篇文章的撰写+实验代码调试运行+知识点细致化学习,共花了本人9h左右的时间

个人觉得已经非常详细啦,如果仍有不够希望大家多多包涵~~如果觉得对你有帮助,辛苦友友点个赞哦~

知识来源:山东大学《操作系统原理实用实验教程》张鸿烈老师编著

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1613021.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python数据可视化:频率统计条形图countplot()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 Python数据可视化&#xff1a; 频率统计条形图 countplot() [太阳]选择题 请问关于以下代码表述正确的选项是&#xff1f; import seaborn as sns import matplotlib.pyplot as plt data { …

二、python+前端 实现MinIO分片上传

python前端 实现MinIO分片上传 一、背景二、流程图三、代码 一、背景 问题一&#xff1a;前端 -> 后端 ->对象存储 的上传流程&#xff0c;耗费带宽。 解决方案&#xff1a;上传流程需要转化为 前端 -> 对象存储&#xff0c;节省上传带宽 问题二&#xff1a;如果使用…

Leetcode 第394场周赛 问题和解法

题目 统计特殊字母的数量 I 给你一个字符串word。如果word中同时存在某个字母的小写形式和大写形式&#xff0c;则称这个字母为特殊字母。 返回word中特殊字母的数量。 示例 1: 输入&#xff1a;word "aaAbcBC"输出&#xff1a;3解释&#xff1a;word 中的特殊…

【Entity Framework】聊一聊EF如何使用数据库函数

【Entity Framework】聊一聊EF如何使用数据库函数 文章目录 【Entity Framework】聊一聊EF如何使用数据库函数一、数据库函数的类型二、内置函数与用户定义的函数四、聚合函数、标量函数和表值函数五、Niladic函数六、EF Core 中的数据库函数映射6.1 内置函数映射6.2 EF.Functi…

【iOS开发】(四)react Native第三方组件五个20240419-20

react native 外的 第三方组件 目录标题 react native 外的 第三方组件&#xff08;一&#xff09;与rn核心组件的使用步骤区别&#xff1a;&#xff08;二&#xff09;第三方组件概览1 WebView2 Picker3 Swiper4 AsyncStorage5 Geolocation6 Camera (三)详细学习1 WebViewCoco…

ROS1快速入门学习笔记 - 01Linux基础

目录 一、Linux极简基础 二、C与Python极简基础 1. for循环 2. while循环 3. 面向对象 一、Linux极简基础 终端快捷键&#xff1a;ctrlaltt 命令行的操作方式 查看当前终端所在路径&#xff1a;pwd切换路径cd&#xff1b;例如cd /home/ 进入home文件夹&#xff1b;cd …

Oracle Hint 语法详解

什么是Hint Hint 是 Oracle 提供的一种 SQL 语法&#xff0c;它允许用户在 SQL 语句中插入相关的语法&#xff0c;从而影响 SQL 的执行方式。 因为 Hint 的特殊作用&#xff0c;所以对于开发人员不应该在代码中使用它&#xff0c;Hint 更像是 Oracle 提供给 DBA 用来分析诊断问…

2024数学建模时间汇总与竞赛攻略

目录 2024数学建模汇总&#xff08;时间、报名费、获奖率、竞赛级别、是否可跨校&#xff09; 中国高校大数据挑战赛 “华数杯”国际大学生数学建模竞赛 美国大学生数学建模竞赛&#xff08;美赛&#xff09; 数学中国&#xff08;认证杯&#xff09;数学建模网络挑战赛 …

从国九条的颁布简单看待未来的因子轮动

上周4月12日《关于加强监管防范风险推动资本市场高质量发展的若干意见》又称国九条出台后&#xff0c;除了本周五中东局势对大盘的影响&#xff0c;本周一波三折的行情很大程度上都是围绕着国九条展开的。一个很有意思的现象是前两次国九条发布后&#xff0c;市场都诞生了波澜壮…

【Linux开发 第八篇】定时任务

定时任务 crond任务调度at定时任务 crond任务调度 任务调度&#xff1a; 是指系统在某个时间执行特定的命令或程序 任务调度分类&#xff1a; 系统工作&#xff1a;有些重要的工作必须周而复始地执行&#xff0c;如病毒扫描等 个别用户工作&#xff1a;个别用户可能希望执行某…

107页 | 企业数字化转型规划设计(免费下载)

【1】关注本公众号&#xff0c;转发当前文章到微信朋友圈 【2】私信发送 【企业数字化转型规划设计】 【3】获取本方案PDF下载链接&#xff0c;直接下载即可。 如需下载本方案PPT原格式&#xff0c;请加入微信扫描以下方案驿站知识星球&#xff0c;获取上万份PPT解决方案&…

JAVA-服务器搭建-创建web后端项目

首先打开IDEA 点击新建项目 写好名称-模板选择 Web应用程序 -语言选择 Java 构建系统选择 Maven 然后点击下一步 选择版本-选择依赖项 Web Profile 点击创建 点击当前文件-选择编辑配置 选择左上角的加号-选择Tomcat服务器-选择本地 点击配置-选择到Tomcat目录-点击确定 起个…

Postman之安装

Postman工具之介绍与安装 Postman是什么&#xff1f;Postman有几种安装方式&#xff1f; Postman是什么&#xff1f; postman是一款http客户端的模拟器&#xff0c;它可以模拟发出各种各样的网络请求&#xff0c;用于接口测试。 Postman有几种安装方式&#xff1f; 两种&…

【数学建模】优劣解距离法Topsis模型(含MATLAB代码)

TOPSIS法&#xff0c;全称 Technique for Order Preference by Similarity to an Ideal Solution&#xff0c;是由C.L.Hwang和K.Yoon于1981年首次提出的 。这是一种多目标决策分析中常用的有效方法&#xff0c;也被称作优劣解距离法 。 TOPSIS法的基本原理是通过检测评价对象与…

Abaqus python二次开发2-扭转弹簧刚度计算

Abaqus python二次开发2-扭转弹簧刚度计算 1、定义弹簧参数2、绘制弹簧2.1、绘制弹簧截面2.12、绘制弹簧实体part&#xff08;螺旋旋转截面&#xff09; 3、设置材料、截面属性、并赋给弹簧&#xff08;set&#xff09;4、创建组件的坐标系、参考点和instance&#xff08;弹簧&…

政安晨:【Keras机器学习示例演绎】(五)—— 利用视觉变换器进行物体检测

目录 导言 导入和设置 准备数据集 实施多层感知器&#xff08;MLP&#xff09; 实施补丁创建层 显示输入图像的补丁 实施补丁编码层 构建 ViT 模型 运行实验 评估模型 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与…

4.9 启动系统任务❤❤❤

有一些特殊的任务需要在系统启动时执行&#xff0c;例如配置文件加载、数据库初始化等操作。 Spring Boot对此提供了两种解决方案&#xff1a;CommandLineRunner和ApplicationRunner。 CommandLineRunner和ApplicationRunner基本一致&#xff0c;差别主要体现在参数上。 1. Co…

【Linux】在centos快速搭建K8S1.18集群

使用 kubeadm 创建集群帮助文档 如果您需要以下几点&#xff0c;该工具是很好的选择&#xff1a;kubeadm 一种简单的方法&#xff0c;让你尝试 Kubernetes&#xff0c;可能是第一次。现有用户自动设置群集并测试其应用程序的一种方式。其他生态系统和/或安装程序工具中的构建…

sublime text的json快捷键

系统 macos 配置 sublime Text->Settings->Key Bindings 效果 可以看到&#xff0c;按&#xff1a;shiftcommandp&#xff0c;会出现快捷键窗口&#xff0c;打pretty&#xff0c;会出现Format JSON&#xff0c;最右侧显示⌘J&#xff0c;说明只需要macos的⌘和J同时按…

目标检测YOLO数据集的三种格式及转换

目标检测YOLO数据集的三种格式 在目标检测领域&#xff0c;YOLO&#xff08;You Only Look Once&#xff09;算法是一个流行的选择。为了训练和测试YOLO模型&#xff0c;需要将数据集格式化为YOLO可以识别的格式。以下是三种常见的YOLO数据集格式及其特点和转换方法。 1. YOL…