c 进程间的通信

您所在的位置:网站首页 python进程间通信 c 进程间的通信

c 进程间的通信

#c 进程间的通信| 来源: 网络整理| 查看: 265

在上篇讲解了如何创建和调用进程

c 进程和系统调用

这篇文章就专门讲讲进程通信的问题

先来看一段下边的代码,这段代码的作用是根据关键字调用一个Python程序来检索RSS源,然后打开那个URL

#include #include #include #include #include void error(char *msg) { fprintf(stderr, "Error: %s %s", msg, strerror(errno)); exit(); } void open_url(char *url) { char launch[]; // windows sprintf(launch, "cmd /c start %s",url); system(launch); // linux sprintf(launch, "x-www-browser ‘%s’ &",url); system(launch); // mac sprintf(launch, "open '%s'",url); system(launch); } int main(int argc, char * argv[]) { // 取出要搜索的参数 char *phrase = argv[]; // 设置RSS源 char *vars[] = {"RSS_FEED=http://news.baidu.com/n?cmd=1&class=civilnews&tn=rss&sub=0",NULL}; // 开启一条管道,用于连接下边的父进程和子进程 int fd[]; if (pipe(fd) == -) { error("Can not create a pipe"); } // 开启一个进程 pid_t pid = fork(); if (pid == -) { error("Can not fork process"); } // 接下来让子进程去源内查找结果 pid==0 为子进程 if (pid == ) { // 因为子进程不需要使用管道的输出端,关闭它 close(fd[]); // 把标准输出设置为管道的输入端,之所以这么设置,是因为子集成当查询到数据的时候就会调用标准输出函数,然后把数据流入管道中 dup2(fd[], ); if (execle("/usr/bin/python", "/usr/bin/python","./rssgossip.py","-u",phrase,NULL,vars) == -) { error("Can't run script"); } } // 上边的if中的代码是子进程的代码,下边的代码是父进程的 // 父进程中不使用管道的写入端,关闭 close(fd[]); // 把标准输入 定向为管道的读取端 dup2(fd[], ); char line[]; // 以\t 开头的就是url while (fgets(line, , stdin)) { if (line[] == '\t') { open_url(line+); FILE *file = fopen("te.txt", "w"); fclose(file); } } return ; }

我们先看看进程内部是什么样子的

进程含有它内部运行的程序,还有栈和堆的数据空间。除此之外,它还要记录数据流的连向,比如标准输出连接到哪里。进程用文件描述符来表示数据流,所谓的描述符其实就是一个数字,进程会把文件描述符和对应的数据流保存在描述符表中,就像下边的这张图一样

文件描述表的一列是文件描述符号,另外一列是他们对应的数据流。虽然名字叫文件描述符,但他们不一定连接硬盘上的某个文件,也有可能连接键盘,屏幕,文件指针,网络等等。

描述符表的前三项万年不变:0代表标准输入 ,1代表标准输出 ,2代表标准错误,其他项要么为空,要么连接进程打开的数据流。比如程序在打开文件进行读写的时候,就会打开一项。

每当创建进程后,默认的0(标准输入)指向键盘,1(标准输出)和2(标准错误)指向屏幕。

那么问题来了,前三项不是万年不变的吗?那么我应该怎么控制数据流呢?

其实,0/1/2在描述符表中的位置虽然是不可变的,但是他们指向的数据流确实可以改变的。

举个简单的例子,我想打开一个文件,进程是怎么做呢?

首先我们先打开一个文件

FILE *file = fopen("quitar.mp3", "r");

当系统执行完上边这行代码的时候,系统会打开quitar.mp3这个文件,并且会返回一个指向这个文件的指针,系统还会表里描述符表的空项,并把新文件注册在其中

但是当我们做了很多操作,描述符表中有很多项的时候,我们如何才能找到我们想要的那一项呢 ?

答案是fileno() 函数,成功会返回文件指针的文件描述符,错误了不会返回-1,只要你把打开文件的指针传给了它,就一定会返回描述符。

int desc = fileno(file);

我们现在已经能够通过fileno()函数拿到描述符了,那我们应该如何修改该项的数据流呢?

答案是dup2() 函数。他会复制当前描述符的数据流到制定的描述符的数据流。

dup2(,);

好了,我们已经知道如何改变数据流了,完全可以把标准输出指向一个文件,然后把数据写入这个文件中。

只知道这些还不够,有点开发经验的人都知道进程和进程之间执行任务所需要的时间是不一样的,往往需要等待一个进程完成后再进行下边的任务,比如父进程和子进程,父进程需要等待子进程完成后再继续,这个该怎么办呢?

答案就是waitpid()函数。它会等待子进程结束后才返回。

现在终于说到重点内容了,进程之间传递数据靠的就是管道,在进程之间创建一条管道,

比如我们要把子进程的数据传给父进程。

使用pipe()函数打开两条数据流。

因为子进程需要把数据发送到父进程,所以要用管道连接子进程的标准输出和父进程的标准输入。你将用pipe()函数建立管道,还记得吗?我们说过,每当打开数据流的时候,它都会加入描述符表中,pipe()函数也是如此,它创建两条相连的数据流,并把他们加入到表中,然后你只需要往其中一条数据流中写数据,就能从另一条数据流中读取。

pipe()在描述符表中创建这两项时,会把他们的文件描述符保存在一个包含两个元素的数组中:

好了,本片文章中开头给出的代码所使用到的知识点都已经讲解清楚了,原理大概就是这样的,

我们已经知道了进程是怎么一回事?知道了如何在进程中利用管道做一些事情了,然而,这些还不够,虽然我们知道了如何创建进程,配置环境,进程间通信,但是进程是怎么结束的呢?

我们带着这个小小的疑问继续往下看

加入我们写了一个从键盘数据的小程序,像这样

#include int main () { char name[]; printf("请输入一个名字:"); fgets(name, , stdin); printf("你输入的名字是: %s",name); return ; }

当我们按下Ctrl+C 的时候程序就结束了,也就是进程就结束了。但这其中到底发生了什么呢?

printf("你输入的名字是: %s",name);

printf并没有调用,是fgets调用了exit()吗?

其实这涉及到了操作系统是如何控制程序的问题

当调用fgets函数时,操作系统会从键盘读取数据,当它发现用户按了Ctrl-C 后就会向程序发送中断信号,就像这样:

信号是一个短消息,也就是一个整型。当信号到来时,进程必须停止手中一切工作来处理信号,进程会查看信号映射表,表中每一个信号都对应着一个信号处理函数,中断信号的默认处理函数就是exit()函数。

那么系统为什么不直接结束程序呢?而是在信号表中查找处理函数?就是为了让我们可以自定义信号处理函数。

使用sigaction函数

sigaction函数是一个函数包装器,本质上是一个结构体

/* * Signal vector "template" used in sigaction call. */ struct sigaction { union __sigaction_u __sigaction_u; /* signal handler */ sigset_t sa_mask; /* signal mask to apply */ int sa_flags; /* see signal options below */ };

它有一个函数指针 __sigaction_u

/* union for signal handlers */ union __sigaction_u { void (*__sa_handler)(int); void (*__sa_sigaction)(int, struct __siginfo *, void *); };

但是在平时开发中一般这么使用

/* if SA_SIGINFO is set, sa_sigaction is to be used instead of sa_handler. */ #define sa_handler __sigaction_u.__sa_handler #define sa_sigaction __sigaction_u.__sa_sigaction

sigaction告诉操作系统收到信号时应该调用哪个函数,加入我们想在收到中断信号的时候调用我们自定义的my_custom_fun(),就要把我们自定义的这个函数包装成sigaction。

// 创建一个新动作 struct sigaction action; // 想让计算机调用哪个函数,这个被包装的my_custom_fun函数就叫做处理器 action.sa_handler = my_custom_fun; // 使用掩码过滤信号,通常会用一个空的掩码 sigemptyset(&action.sa_mask); // 一些附加的标志位,置为0就行了 action.sa_flags = ;

当然这个被包装的函数也需要以特定的方式创建,这个函数我们下边就称之为处理器了,处理器必须接受信号参数,信号是一个整形值,如果你自定义一个信号处理函数,就需要接受一个整型参数,像这样:

void my_custom_fun(int sig) { exit(); }

由于我们以参数的形式传递信号,所以多个信号可以共用一个处理器,也可以每个信号写一个处理器。

要点: 处理器的代码应该短而快,刚好能处理接受到的信号就好。

那么系统怎么知道我们偷偷的更换了处理器呢?

因此要使用sigaction() 函数来注册sigaction,让系统知道它的存在

sigaction(signal_no, &new_action, &old_action);

我们来看看这个函数的样子:

int sigaction(int, const struct sigaction * __restrict, struct sigaction * __restrict);

它几首3个参数:

1. 信号编号,这个整型值代表了你希望处理的信号,通常会传递类似SIGINT/SIGQUIT这样的标准信号。

2. 新动作,你想注册的新sigaction的地址

3. 旧动作, 如果你想保存被替换的信号处理器,可以再传一个sigaction指针,如果不想保存,可以传一个NULL。

注意:如果sigaction() 的函数失败,会返回-1,并设置errno变量。

当然,为了能够快速的使用这些功能,我们可以把创建这个sigaction的过程封装起来,我们只需要告诉函数需要捕捉的信号是什么,设置的处理器是什么就够了。

我们写了下边的函数:

int catch_signal(int sig, void (*handler)(int)) { // 创建一个新动作 struct sigaction action; // 想让计算机调用哪个函数,这个被包装的my_custom_fun函数就叫做处理器 action.sa_handler = handler; // 使用掩码过滤信号,通常会用一个空的掩码 sigemptyset(&action.sa_mask); // 一些附加的标志位,置为0就行了 action.sa_flags = ; return sigaction(sig, &action, NULL); }

我们先用一个简单的例子来演示下上边讲到的sigaction,代码如下

#include #include #include int catch_signal(int sig, void (*handler)(int)) { // 创建一个新动作 struct sigaction action; // 想让计算机调用哪个函数,这个被包装的my_custom_fun函数就叫做处理器 action.sa_handler = handler; // 使用掩码过滤信号,通常会用一个空的掩码 sigemptyset(&action.sa_mask); // 一些附加的标志位,置为0就行了 action.sa_flags = ; return sigaction(sig, &action, NULL); } void my_custom_fun(int sig) { printf("一切都结束了"); exit(); } int main () { if (catch_signal(SIGINT, my_custom_fun) == -) { fprintf(stderr, "替换不成功"); exit(); } char name[]; printf("请输入一个名字:"); fgets(name, , stdin); printf("你输入的名字是: %s",name); return ; }

运行程序,当我Ctrl-C 的时候,结果如下

请输入一个名字:^C一切都结束了bogon:- machao$ SIGINT 进程被中断 SIGQUIT 有人要求停止进程,并把存储器中的内容保存到核心转存文件中 SIGFPE 浮点错误 SIGTRAP 调试人员询问进程执行到了哪里 SIGSEGV 进程试图访问非法存储器地址 SIGWINCH 终端窗口的大小发生变化 SIGTERM 有人要求系统的内核终止进程 SIGPIPE 进程在向一个没有人读的管道写数据

那么问题又来了,我们应该如何发送系统信号呢?

最后我们使用一个稍微复杂点的例子来掩饰上边讲的内容

说明:

SIGALRM 是一个定时器信号,使用alarm()函数可以设置一个定时器,设置一个时间,当时间结束的时候,会发出SIGALRM信号,如果在定时器时间还未结束的情况下,再次调用了alarm()函数,定时器将重新计时

这个程序测试用户的数学水平,要求用户做乘法,程序的条件如下:

1.用户Ctrl-C

2.回答时间超过5秒

程序在结束时会显示总得分,并把退出状态设为0

代码如下

#include #include #include #include #include #include #include int score = ; int catch_signal(int sig, void (*handler)(int)) { // 创建一个新动作 struct sigaction action; // 想让计算机调用哪个函数,这个被包装的my_custom_fun函数就叫做处理器 action.sa_handler = handler; // 使用掩码过滤信号,通常会用一个空的掩码 sigemptyset(&action.sa_mask); // 一些附加的标志位,置为0就行了 action.sa_flags = ; return sigaction(sig, &action, NULL); } // 结束游戏的处理器 void end_game(int sig) { printf("\n 总得分: %i \n", score); exit(); } // 时间到了的处理器 void times_up(int sig) { printf("\n 时间到了"); // 当倒计时结束的时候,引发SIGINT信号,调用end_game函数 raise(SIGINT); } void error(char *msg) { fprintf(stderr, "Error: %s %s", msg, strerror(errno)); exit(); } int main () { // 中断信号 if (catch_signal(SIGINT, end_game) == -) { fprintf(stderr, "结束不成功"); exit(); } // 定时信号 if (catch_signal(SIGALRM, times_up) == -) { fprintf(stderr, "闹钟不成功"); exit(); } // srandom函数利用一个时间因子产生一个不同的队列给random函数调用,这样random函数每次运行时就不会产生一样的伪随机输出了 srandom (time (NULL)); while () { // 生成两个 0 ~ 10 的随机数 int a = random() % ; int b = random() % ; char txt[]; // 定时5秒 alarm(); printf("%i * %i = ?",a,b); fgets(txt, , stdin); int answer = atoi(txt); if (answer == a * b) { score++; }else { printf("错误!得分: %i \n",score); } } return ; }

首先我们回答两个问题,然后等待5秒倒计时结束 , 再次运行程序,我们Ctrl-C

运行结果为

好了,关于进程间的通信的内容,和进程的操作就写到这里了

c 进程间的通信的更多相关文章 Android进程间的通信之AIDL

Android服务被设计用来执行很多操作,比如说,可以执行运行时间长的耗时操作,比较耗时的网络操作,甚至是在一个单独进程中的永不会结束的操作.实现这些操作之一是通过Android接口定义语言(AIDL ...

Android进程间的通信之Messenger

Android进程间的通信方式可以通过以下两种方式完成: Android接口定义语言(AIDL) 使用Messenger绑定服务 本文我们将学习使用Messenger绑定服务的方式进行进程间的通信. ...

Unix系统中,两个进程间的通信

进程之间通常需要进行数据的传输或者共享资源等,因此进程间需要通讯. 可以通过管道,信号,消息队列,共享内存,信号量和套接字等方式 FIFO表示命名管道,这种管道的操作是基于先进先出原理. PIPE 表 ...

探讨一个新的两个进程间的通信和编程模型 (Windows)

本文探讨一个新的Windows上的两个UI进程间的通信和编程模型. 开门见山,下面是这个通信模型的梗概图: 这个模型的设计目标描述如下: (1)发送数据接口:RpcSend, RpcPost RpcS ...

采用虚拟命名管道的字符设备和阻塞型I/O实现进程间的通信实现KWIC程序

采用虚拟命名管道的字符设备和阻塞型I/O实现进程间的通信实现KWIC程序专业程序代写c++程序代写

python3,进程间的通信

本文来源于python 3.5版本的官方文档 multiprocessing模块为进程间通信提供了两种方法: 1.进程队列queue The Queue class is a near clone o ...

Python 多进程编程之 进程间的通信(在Pool中Queue)

Python 多进程编程之 进程间的通信(在Pool中Queue) 1,在进程池中进程间的通信,原理与普通进程之间一样,只是引用的方法不同,python对进程池通信有专用的方法 在Manager()中 ...

Python 多进程编程之 进程间的通信(Queue)

Python 多进程编程之 进程间的通信(Queue) 1,进程间通信Process有时是需要通信的,操作系统提供了很多机制来实现进程之间的通信,而Queue就是其中的一个方法----这是操作系统开辟 ...

python全栈开发day32-进程创建,进程同步,进程间的通信,进程池

一.内容总结 1.进程创建 1) Process:两种创建一个新进程的方法: 1.实例化Process,通过args=(,)元组形式传参,2创建类继承Process,类初始化的时候传参数 2) p.j ...

随机推荐 Qt之重写QLabel类

在mylabel.h 文件中#ifndef MYLABEL_H#define MYLABEL_H #include /*重新实现QLabel类,使其支持点击事件*/clas ...

【百度地图API】如何进行地址解析与反地址解析?——模糊地址能搜索到精确地理信息!

原文:[百度地图API]如何进行地址解析与反地址解析?--模糊地址能搜索到精确地理信息! 摘要: 什么是地址解析? 什么是反地址解析? 如何运用地址解析,和反地址解析? 可以同时运用地址解析,和反地址 ...

Webpack 2 视频教程 016 - Webpack 2 中生成 SourceMaps

原文发表于我的技术博客 这是我免费发布的高质量超清「Webpack 2 视频教程」. Webpack 作为目前前端开发必备的框架,Webpack 发布了 2.0 版本,此视频就是基于 2.0 的版本讲 ...

JavaScript之图片懒加载的实现

图片懒加载指的是在浏览过程中随着需要才被加载出来,例如某宝上面浏览商品时,会伴随很多的图片,如果一次全部加载出来的话,显然资源有些浪费,并且加载速度也会相对降低,那么懒加载的实现很重要.即随着浏览翻阅 ...

优化之Joiner组件

Joiner组件在运行时需要额外的内存空间处理中间结果,因此会影响性能 可通过查看Joiner performance计数器来决定Joiner组件是否需要优化 通过如下方式优化Joiner组件 将Ma ...

201671010142 2017-2 《java第十一章学习感悟》

事件处理基础 事件源,事件监听器,事件监听器  监听器接口的实现,监听器对象所属类必须实现与事件源相对应的接口,即必须提供接口中方法的实现. 适配器类 当程序用户试图关闭一个框架窗口时,Jframe对 ...

解决mysql:Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (111)

(一)出现问题的的报错信息 Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (111) ( ...

Python3-操作系统发展史

操作系统发展史 手工操作 —— 穿孔卡片 批处理 —— 磁带存储 多道程序系统 操作系统的作用 手工操作 —— 穿孔卡片 1946年第一台计算机诞生--20世纪50年代中期,计算机工作还在采用手工操作 ...

Convert 实现 pdf 和图片格式互转

pdf 转换为图片 (注意:pdf 默认转换的是透明背景,如果转为jpg格式必须添加背景色.-background white -flatten) convert -background white ...

Oracle学习笔记之四,SQL语言入门

1. SQL语言概述 1.1 SQL语言特点 集合性,SQL可以的高层的数据结构上进行工作,工作时不是单条地处理记录,而对数据进行成组的处理. 统一性,操作任务主要包括:查询数据:插入.修改和删除数据 ...



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3