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

news2024/10/7 14:25:18

实验内容

通过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左右的时间

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

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

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

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

相关文章

知行之桥EDI系统跨平台版本安装报错及解决方案

本文将为大家介绍如何在Windows系统中安装知行之桥EDI系统跨平台版本的常见报错以及解决方案。如下图所示&#xff1a; 在知行软件官网的导航栏中点击 下载 按钮&#xff0c;即可看到知行之桥EDI系统不同版本的下载选项&#xff0c;点击右侧跨平台版本&#xff0c;选择 Windows…

uniapp-ios支付

uniapp安卓包中的微信,支付宝逻辑放在iOS测试包中也能使用. 但询问iOS开发者后得知,有支付相关功能的app要上架苹果,必须先有苹果支付,不然苹果审核不给过.甚至没有支付逻辑,但打包时有支付相关的SDK也不行,苹果会认为你偷偷做了支付逻辑,想要绕开他. 一. 去苹果开发者后台把…

蓝桥杯备赛(填空题)【Python B组】

一、弹珠堆放 问题描述 小蓝有 20230610 颗磁力弹珠&#xff0c;他对金字塔形状尤其感兴趣&#xff0c;如下图所示&#xff1a; &#xff08;图是盗来的啊&#xff0c;侵权请联系删除&#xff09; 问题分析 找规律&#xff0c;第一层1个&#xff0c;第二层3个&#xff0c;第…

全国在线选座电影票小程序app开发需要具备哪些条件api是必须的吗?

全国在线选座电影票小程序或APP的开发需要具备一系列的条件&#xff0c;而API&#xff08;应用程序编程接口&#xff09;通常是其中必不可少的一部分。以下是一些关键的条件和API的作用&#xff1a; 关键条件&#xff1a; 明确需求和目标&#xff1a;首先&#xff0c;你需要明…

【牛客】[HNOI2003]激光炸弹

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 注意从&#xff08;1,1&#xff09;开始存即可&#xff0c;所以每次输入x,y之后&#xff0c;要x,y。 因为m的范围最大为…

FFmpeg———encode_video(学习)

目录 前言源码函数最终效果 前言 encode_video:实现了对图片使用指定编码进行编码&#xff0c;生成可播放的视频流&#xff0c;编译时出现了一些错误&#xff0c;做了一些调整。 基本流程&#xff1a; 1、获取指定的编码器 2、编码器内存申请 3、编码器上下文内容参数设置 4、…

智慧公厕:数据驱动的新时代公共厕所管理

公共厕所是城市的重要基础设施&#xff0c;直接关系到人民群众的生活质量和城市形象。然而&#xff0c;长期以来&#xff0c;公共厕所的管理问题一直困扰着城市管理者。为了解决这个难题&#xff0c;智慧公厕应运而生。本文将以智慧公厕源头实力厂家广州中期科技有限公司&#…

商务分析方法与工具(四):Python的趣味快捷-简单函数你真的会用吗?

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

CDN内容分发网络

CDN&#xff1a;内容分发网络的起源与工作原理 随着互联网的迅猛发展&#xff0c;内容交付网络&#xff08;CDN&#xff09;作为一种重要的网络基础设施&#xff0c;扮演着越来越关键的角色。本文将从CDN的起源、简介、工作原理以及如何识别等方面进行阐述。 # 1. CDN的起源 …

想做视频号小店,为何不建议开通个体店?开店步骤+做店思路如下

我是王路飞。 如果你想在视频号开通店铺的话&#xff0c;那么一定不要使用个体执照开通个体店&#xff1f; 这是为什么呢&#xff1f; 原因很简单&#xff0c;视频号个体店是无法入驻优选联盟的&#xff0c;只能企业店可以入驻。 因为现阶段视频号小店的自然流量很少&#…

elementUI table表格相同元素合并行----支持多列

效果图如下: vue2代码如下&#xff1a; 只粘贴了js方法哦&#xff0c; methods: {// 设置合并行 setrowspans() { const columns [‘name’, ‘value’]; // 需要合并的列名 // 为每个需要合并的列设置默认 rowspan this.tableData.forEach(row > { columns.forEach(col …

flutter开发实战-GetX响应式状态管理使用

flutter开发实战-GetX响应式状态管理使用 GetX是一个简单的响应式状态管理解决方案。GetX是Flutter的一款超轻、功能强大的解决方案。它将高性能状态管理、智能依赖注入和路由管理快速而实用地结合在一起。这里简单使用一下GetX 一、引入GetX 在工程的pubspec.yaml中引入插件…

药物代谢动力学学习笔记

一、基本概念 二、经典房室模型 三、非线性药物代谢动力学 四、非房室模型 五、药代动力学与药效动力学 六、生物等效性评价 七、生物样品分析方法 基本概念 生物样品&#xff1a;生物机体的全血、血浆、血清、粪便、尿液或其他组织的样品 特异性&#xff0c;specificity&…

运维实施工程师之Linux服务器全套教程

一、Linux目录结构 1.1 基本介绍 Linux 的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&#xff0c;然后在此目录下再创建其他的目录。 在 Linux 世界里&#xff0c;一切皆文件&#xff08;即使是一个硬件设备&#xff0c;也是使用文本来标…

MQTT 5.0 报文解析 03:SUBSCRIBE 与 UNSUBSCRIBE

欢迎阅读 MQTT 5.0 报文系列 的第三篇文章。在上一篇中&#xff0c;我们介绍了 MQTT 5.0 的 PUBLISH 及其响应报文。现在&#xff0c;我们将介绍用于订阅和取消订阅的控制报文。 在 MQTT 中&#xff0c;SUBSCRIBE 报文用于发起订阅请求&#xff0c;SUBACK 报文用于返回订阅结果…

纯干货!Prompt链式方法总结,灵活驾驭各种大模型!

当面对复杂任务的时&#xff0c;单一的Prompt是不够的&#xff0c;我们需要将Prompt链接在一起才能完成。今天这篇文章主要集中在Prompt链式方法介绍用于实现复杂任务生成&#xff0c;其中主要包括&#xff1a;顺序Prompt链、并行Prompt链、抽样Prompt链、树状Prompt链、循环Pr…

SpringBoot实现Config下自动关联.xml、.properties配置信息的实例教程

本篇文章主要讲解在SpringBoot实现Config下自动关联.xml、.properties配置信息的实例教程。 日期&#xff1a;2024年5月4日 作者&#xff1a;任聪聪 .properties文件调用方法 步骤一、打开我们的 .properties 创建一个demo参数如下图&#xff1a; 步骤二、创建一个config的包&…

代码随想录——双指针与滑动窗口(四)

一.1423. 可获得的最大点数 题目详情 解题思路 这里我们每次只能取最左或最右边的卡牌,第一反应其实是使用双指针&#xff0c;通过局部贪心来解决&#xff0c;但是如果两边相等的话用局部贪心无法来判断到底取哪一边&#xff0c;那我们不妨换一个思路&#xff1a; 我们首先任…

uniapp的app端推送功能,不使用unipush

1&#xff1a;推送功能使用htmlPlus实现&#xff1a;地址HTML5 API Reference (html5plus.org) 效果图&#xff1a; 代码实现&#xff1a; <template><view class"content"><view class"text-area"><button click"createMsg&q…