操作系统实战(二)(linux+C语言)

实验内容

通过Linux 系统中管道通信机制,加深对于进程通信概念的理解,观察和体验并发进程间的通信和协作的效果 ,练习利用无名管道进行进程通信的编程和调试技术。

管道pipe是进程间通信最基本的一种机制,两个进程可以通过管道一个在管道一端向管道发送其输出,给另一进程可以在管道的另一端从管道得到其输入。管道以半双工方式工作,即它的数据流是单方向的。因此使用一个管道一般的规则是读管道数据的进程关闭管道写入端,而写管道进程关闭其读出端。

示例程序

效果为:两个进程交替分别对X进行+1操作

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    int pid; //进程号
    int pipe1[2]; //存放第一个无名管道标号
    int pipe2[2]; //存放第二个无名管道标号
    int x; // 存放要传递的整数
    //使用pipe()系统调用建立两个无名管道。建立不成功程序退出,执行终止
    if(pipe(pipe1) < 0)
    {
        perror("pipe not create");
        exit(EXIT_FAILURE);
    }
    if(pipe(pipe2) < 0)
    {
        perror("pipe not create");
        exit(EXIT_FAILURE);
    }
    //使用fork()系统调用建立子进程,建立不成功程序退出,执行终止
    if((pid=fork()) <0)
    {
        perror("process not create");
        exit(EXIT_FAILURE);
    }
    //子进程号等于0 表示子进程在执行
    else if(pid == 0)
    {
        //子进程负责从管道1的0端读,管道2的1端写
        //所以关掉管道1的1端和管道2的0端。
        close(pipe1[1]);
        close(pipe2[0]);
        //每次循环从管道1 的0 端读一个整数放入变量X 中,
        //并对X 加1后写入管道2的1端,直到X大于10
        do
        {
            read(pipe1[0],&x,sizeof(int));
            printf("child %d read: %d\n",getpid(),x++);
            write(pipe2[1],&x,sizeof(int));
        }while( x<=9 );
        //读写完成后,关闭管道
        close(pipe1[0]);
        close(pipe2[1]);
        //子进程执行结束
        exit(EXIT_SUCCESS);
    }
    //子进程号大于0 表示父进程在执行
    else
    {
        //父进程负责从管道2的0端读,管道1的1端写,
        //所以关掉管道1 的0 端和管道2 的1端。
        close(pipe1[0]);
        close(pipe2[1]);
        x=1;
        //每次循环向管道1 的1 端写入变量X 的值,并从
        //管道2的0 端读一整数写入X 再对X加1,直到X 大于10
        do
        {
            write(pipe1[1],&x,sizeof(int));
            read(pipe2[0],&x,sizeof(int));
            printf("parent %d read: %d\n",getpid(),x++);
        }while(x<=9);
        //读写完成后,关闭管道
        close(pipe1[1]);
        close(pipe2[0]);
    }
    //父进程执行结束
    return EXIT_SUCCESS;
}

执行结果:

几个关键点 

一、pipe系统调用的使用

  1. 创建管道两个端口 :int pipe[2]
  2. 调用pipe系统调用在两个端口间建立管道
  3. 后续可利用read、write通过管道端口,利用管道进行进程间通信
  4. 为了防止出现死锁以及消息冲突,需要进行close处理
  5. 读写操作传输的值都是实际地址

pipe管道端口不与进程绑定,而是可以更改的;pipe管道端口的作用是固定的,0端口读,1端口写

二、perror函数的使用

perror()是一个C语言标准库函数,用于打印错误信息。它接受一个字符串参数作为错误信息的前缀,并将系统的错误消息附加到该前缀后面

一般用于打印系统调用的错误,能够自动输出系统调用错误的编码。见下面示例代码:

#include <stdio.h>
#include <errno.h>

int main() {
    FILE *file = fopen("nonexistent_file.txt", "r");
    if (file == NULL) {
        perror("Error opening file: ");
        return 1;
    }

    // 其他文件操作...

    fclose(file);
    return 0;
}

其输出是:

Error opening file: No such file or directory

三、read、write函数的使用 

(1)读取时:要先关闭管道的写入端口,才能从输出端口进行读出

read函数的三个参数分别为:

close(port[1]);
read(port[0],数据,要传输的数据长度);

 (2)输出时:

write函数的三个参数分别为:

close(port[0]);
write(port[1],数据,要传输的数据长度);

本次实验

实验内容

实验代码

#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <wait.h>

//三个递归函数的定义,每个函数用一个进程来运行,运行结果利用pipe通信
//f(x)
int fx(int x){
    if(x <= 0){
        printf("the number you input must be positive!");
        return 0;
    }
    else if(x == 1){
        return 1;

    }
    else if(x > 1){
        return fx(x-1) * x;
    }
}
//f(y)
int fy(int y){
    if(y <= 0){
        printf("the number you input must >2!");
        return 0;
    }
    else if(y == 1 || y == 2){
        return 1;
    }
    else if(y > 2){
        return fy(y-1) + fy(y-2);
    }
}
//f(x,y)
int fxy(int fx, int fy){
    return fx + fy;
}

int main(int argc,char* argv[]){
    int pid1;  //子进程1
    int pid2;  //子进程2
    int pid;  //父进程
    int pipe1[2];  //第一个管道:父进程写,子进程读
    int pipe2[2];  //第二个管道:父进程读,子进程写
    int result1;  //保存f(x)和f(y)的计算结果
    int result2;
    int result;
    int status;  //记录子进程状态
    int x;
    int y;

    //从键盘输入x和y
    printf("please input number x: ");
    scanf("%d",&x);
    printf("\n");
    printf("please input number y: ");
    scanf("%d",&y);
    printf("\n");

    //开始创建管道
    if(pipe(pipe1)<0){
        perror("pipe1 not create");
        exit(EXIT_FAILURE);
    }
    if(pipe(pipe2)<0){
        perror("pipe2 not create");
        exit(EXIT_FAILURE);
    }
    //创建子进程开始执行操作
    pid1=fork(); 
    if(pid1<0){  //第一个子进程,注意以下!!!!我在这里踩了坑
        perror("process1 not create");
        exit(EXIT_FAILURE);
    }
    //子进程1在执行
    if(pid1==0){
        //子进程负责在管道1的1端写,父进程在管道1的0端读
        //所以关掉管道1的0端
        close(pipe1[0]);
        result1=fx(x);
        printf("子进程1完成了运算,f(x)=%d\n",result1);
        //将运行结果发送出去
        write(pipe1[1],&result1,sizeof(int));
        //写完成后,关闭管道
        close(pipe1[1]);
        //子进程执行结束
        exit(EXIT_SUCCESS);
    }

    //父进程运行
    else{
        waitpid(pid1, &status, 0);  //等待子进程运行结束再执行父进程(主动阻塞父进程,也可以让其因为read被动阻塞)
        printf("我是父进程%d,已经等待子进程%d完成,现开始运行\n",getpid(),pid1);
        close(pipe1[1]);  //在访问共享资源前都要避免互斥
        //从管道1的0端口获得数值
        read(pipe1[0],&result1,sizeof(int));
        close(pipe1[0]);

        //创建另一个进程2执行f(y)程序
        printf("父进程%d已获取结果1,先创建新子进程运行f(y)\n ",getpid());
        pid2=fork();
        //使用fork()系统调用建立子进程,建立不成功程序退出,执行终止
        if(pid2 <0){
            perror("子进程2没有创建成功");
            exit(EXIT_FAILURE);
        }
        //第二个子进程,pipe2[1]用来写
        if(pid2 == 0){
            //关掉pipe2[0]端
            close(pipe2[0]);
            //计算f(y)
            result2 = fy(y);
            printf("子进程2完成了运算,f(y)=%d\n",result2);
            //发送消息
            write(pipe2[1],&result2,sizeof(int));
            close(pipe2[1]);
        }
        //父进程
        else{
            waitpid(pid2, &status, 0);
            close(pipe2[1]);
            //接受第二个子进程从管道里发来的信息
            read(pipe2[0],&result2,sizeof(int));
            result = fxy(result1,result2);
            printf("f(x) = %d\n",result1);
            printf("f(y) = %d\n",result2);
            printf("f(x,y) = %d\n",result);
            //读完成后关闭管道
            close(pipe2[1]);
            //父进程执行结束
            return EXIT_SUCCESS;
        }

    }
}

运行结果 

踩的坑 

1、读只能从端口0进行,写从端口1进行

2、编程思路:对于一个进程它必须只要要完成一个操作单位体,计算一个递归函数就是一个操作单位体

3、

赋值运算优先级小于比较运算:所以if(pid1=fork()>0)此时执行的是if(pid1=(fork()>0)),也就是说pid1并未得到fork()返回的子进程pid而是得到比较运算结果1。

解决方案:1、可以把pid=fork,与pid>0分成两步去实现;2、可以修改if(pid1=fork()>0)为if((pid1=fork())>0)

makefile文件编写 

# DEPEND   代替  依赖文件

# CC       代替  gcc

# CFLAGS   代替  编译命令

# PARA     代替  参数

# OBJS     代替 目标文件



DEPEND=expr_2.c

OBJS=expr_2

CC=gcc

CFLAGS=-o





expr_1:$(DEPEND)

	$(CC) $(DEPEND) $(CFLAGS) $(OBJS)

	

run:$(OBJS)

	./$(OBJS) 



clean:

	rm *.o $(OBJS) -rf

实验感悟 

一、进程协作的特点:

  • 共享资源:进程协作和通信允许多个进程共享资源,本示例中父子进程共享变量x
  • 数据传输:进程可以通过通信机制相互传输数据,以实现信息交换和共享。本实验代码中进程之间传输不同函数运行的结果,从而实现协作
  • 进程间控制:进程协作可以通过管道、消息队列、共享内存等实现进程间的控制和协调。本实验中采用管道控制

二、进程通信机制:

目前我们已经学习的有四种类型,如下:

  • 管道:管道是一种单向通信机制,用于在具有亲缘关系的进程之间传递数据。它可以通过创建一个管道文件描述符来实现进程间的通信

  • 消息队列:消息队列是一种存放消息的容器,进程可以通过发送和接收消息来实现通信。消息队列提供了一种异步通信的方式

  • 共享内存:共享内存允许多个进程共享同一块内存区域,进程可以通过读写共享内存来交换数据

  • 信号量(Semaphore):信号量是一种用于进程间同步和互斥访问共享资源的机制。进程可以使用信号量来控制对共享资源的访问

其中管道主要用于父子两个进程之间的简单通信,是单向的。实现起来也简单快捷,但是无法处理多个进程之间的复杂协作

 三、进程管道通信的具体流程:

  1. 创建管道:通过调用系统的管道函数,创建一个管道,它会返回两个文件描述符,一个用于读取数据,一个用于写入数据

  2. 创建子进程:使用系统调用(如fork())创建一个新的子进程

  3. 父子进程通信:父进程可以通过写入管道的文件描述符将数据发送给子进程,子进程可以通过读取管道的文件描述符接收数据

  4. 关闭管道:当通信结束后,父进程和子进程都需要关闭管道的文件描述符,释放相关的资源

总结


本文到这里就结束啦~~

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

618必买好物清单来袭,这些数码产品值得你考虑!

是不是很多朋友和我一样&#xff0c;已经迫不及待地为618好物节做好了准备&#xff0c;准备开启一场购物盛宴&#xff01;作为一名资深家居与数码爱好者&#xff0c;每年618好物节时我都会尽情挑选心仪的物品&#xff0c;因此今天我想和大家分享一下我的618购物清单&#xff0c…

不是,有你们这么卖东西的?涨价是肯定的,我苟住不浪也是必然的!——早读(逆天打工人爬取热门微信文章解读)

大家说我苟&#xff0c;我笑他人看不穿 引言Python 代码第一篇 洞见 晕船法则&#xff08;深度好文&#xff09;第二篇 九边 宅男之死结尾 理性的讨论能够促进理解 而不仅仅是赢得争论 我们追求的是通过讨论增进理解 而非仅仅证明自己的正确 引言 最近的言论似乎控制得更加严格…

LSS(Lift, Splat, Shoot)算法解析

1.简介 LSS(Lift, Splat, Shoot) 是一个比较经典的自下而上的构建BEV特征的3D目标检测算法&#xff0c;通过将图像特征反投影到3D空间生成伪视锥点云&#xff0c;通过Efficientnet算法提取云点的深度特征和图像特征并对深度信息进行估计&#xff0c;最终将点云特征转换到BEV空…

Minio(官方docker版)容器部署时区问题研究记录

文章目录 感慨&概述补充&#xff1a;MINIO_REGION和容器时间的关系 问题一&#xff1a;minio容器和本地容器时间不一致问题说明原因探究解决方法结果验证 问题二&#xff1a;minio修改时间和本地查询结果不一致具体问题原因探究解决办法时间转化工具类调用测试和验证上传文…

计算机组成结构—虚拟存储器

目录 一、虚拟存储器的基本概念 二、页式虚拟存储器 1.页表 2.快表(TLB) 3.具有 TLB 和 Cache 的多级存储系统 三、段式虚拟存储器 四、段页式虚拟存储器 五、虚拟存储器和Cache比较 早期的计算机&#xff0c;CPU 是直接操作主存的&#xff0c;也就是运行程序时&#xf…

深度学习之基于Vgg16卷积神经网络书法字体风格识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 书法是中国传统文化的重要组成部分&#xff0c;具有深厚的历史底蕴和独特的艺术魅力。在数字化时代&…

第50期|GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以找…

损失函数详解

1.损失函数 是一种衡量模型与数据吻合程度的算法。损失函数测量实际测量值和预测值之间差距的一种方式。损失函数的值越高预测就越错误&#xff0c;损失函数值越低则预测越接近真实值。对每个单独的观测(数据点)计算损失函数。将所有损失函数&#xff08;loss function&#xf…

Baidu Comate:你的智能编码助手,编程效率倍增的秘密武器

Baidu Comate智能编码助手 Baidu Comate 智能编码助手简单介绍安装使用查看Comate插件功能智能代码提示使用飞浆和百度智能小程序进行智能问答使用AutoWork插件实现二次函数图像的生成引用Comate知识库存在的问题结束语 Baidu Comate 智能编码助手简单介绍 Baidu Comate&#x…

设计模式(十一):外观模式

设计模式&#xff08;十一&#xff09;&#xff1a;外观模式 1. 外观模式的介绍2. 外观模式的类图3. 外观模式的实现3.1 创建一个接口3.2 创建接口的实现3.3 创建一个外观类3.4 测试 1. 外观模式的介绍 外观模式&#xff08;Facade Pattern&#xff09;属于结构型模式&#xf…

Jupyter Notebook输入python代码没智能提示

1、在Jupyter中打开控制台 2、再控制台中执行以下两个命令&#xff1a; pip install jupyter_contrib_nbextensions jupyter contrib nbextension install --user pip install jupyter_contrib_nbextensions命令需要下载文件&#xff0c;请耐心等待。 3、执行完成后&#xff0…

202003青少年软件编程(Python)等级考试试卷(二级)

第 1 题 【单选题】 运行下方代码段,输出的结果是(   )。 a=(1,2,3)print(type(a))A :<class ‘float’> B :<class ‘int’> C :<class ‘str’> D :<class ‘tuple’> 正确答案:D 试题解析: 第 2 题 【单选题】 content.txt中原来的内容…

第11篇:创建Nios II工程之控制多个七段数码管

Q&#xff1a;DE2-115开发板上有8个七段数码管&#xff0c;如何用PIO IP并设计Nios II工程控制呢&#xff1f; A&#xff1a;基本思路&#xff1a;DE2-115上有8个7位七段数码管&#xff0c;而一个PIO最多可配置为32位&#xff0c;如此就可以添加2个PIO都配置为28位output。 Ni…

《500 Lines or Less》(13)—— A 3D Modeller

原文 作者 原code 我用py3重写的code 3D 建模器 介绍 计算机辅助设计&#xff08;Computer-aided design, CAD&#xff09;工具允许我们在2D屏幕上查看和编辑3D对象。为此&#xff0c;CAD工具必须具有3个基本功能&#xff1a; 表示对象&#xff1a;使用一种数据结构保存和表示…

SpringBoot的@Async注解有什么坑?

前言 SpringBoot中&#xff0c;Async注解可以实现异步线程调用&#xff0c;用法简单&#xff0c;体验舒适。 但是你一定碰到过异步调用不生效的情况&#xff0c;今天这篇文章总结了Async注解的坑点&#xff0c;希望对你会有所帮助。 未启用异步支持 Spring Boot默认情况下不启…

2024年短剧小程序视频怎么下载

想在闲暇之余轻松追剧&#xff0c;但不想跳转复杂的网页或者安装多个APP吗&#xff1f;来试试2024年的短剧小程序视频下载器吧&#xff01;它是一款专门为短剧爱好者打造的视频下载工具&#xff0c;让你轻松下载任何短剧小程序视频&#xff0c;随时随地享受精彩的短剧内容&…

Python密码测试程序

下面是一个简单的 Python 密码测试程序&#xff0c;用于检查用户输入的密码是否符合一些基本的安全要求&#xff0c;如长度、包含字母和数字等。这个程序可以作为一个基本的密码验证器&#xff0c;你可以根据需要进行修改和扩展。 1、问题背景 我们正在编写一个程序&#xff0…

解决 git克隆拉取代码报SSL certificate problem错误

问题&#xff1a;拉取代码时报错&#xff0c;SSL证书问题:证书链中的自签名证书问题 解决&#xff1a;只需要关闭证书验证&#xff0c;执行下面代码即可&#xff1a; git config --global http.sslVerify "false" 再次拉取代码就可以了

ssh远程免密登录

ssh远程连接分为五个阶段 版本号协商阶段密钥和算法协商阶段认证阶段会话请求阶段交互会话阶段 而上图的SessionKey即是在阶段2&#xff1a;密钥和算法协商阶段&#xff0c;服务器端和客户端利用DH交换&#xff08;Diffie-Hellman Exchange&#xff09;算法、主机密钥对等参数…

零基础自学网络安全/Web安全(超详细入门到进阶)学完即可就业(含学习笔记)

一、为什么选择网络安全&#xff1f; 这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前踏入…