第十周课下作业-IPC
第十周课下作业-IPC
题目:研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接
- 共享内存
- 管道
- FIFO
- 信号
- 消息队列
共享内存
共享内存允许两个或多个进程进程共享同一块内存(这块内存会映射到各个进程自己独立的地址空间)从而使得这些进程可以相互通信。
在GNU/Linux中所有的进程都有唯一的虚拟地址空间,而共享内存应用编程接口API允许一个进程使用公共内存区段。但是对内存的共享访问其复杂度也相应增加。共享内存的优点是简易性。 使用消息队列时,一个进程要向队列中写入消息,这要引起从用户地址空间向内核地址空间的一次复制,同样一个进程进行消息读取时也要进行一次复制。共享内存的优点是完全省去了这些操作。
共享内存会映射到进程的虚拟地址空间,进程对其可以直接访问,避免了数据的复制过程。因此,共享内存是GNU/Linux现在可用的最快速的IPC机制。
1.shmget函数
该函数用来创建共享内存,它的原型为:
int shmget(key_t key, size_t size, int shmflg);
不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值),只有shmget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
2.shmat函数
第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
voidvoid *shmat(int shm_id, const voidvoid *shm_addr, int shmflg);
3.shmdt函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
voidvoid *shmat(int shm_id, const voidvoid *shm_addr, int shmflg);
4、shmctl函数
与信号量的semctl函数一样,用来控制共享内存,它的原型如下:
voidvoid *shmat(int shm_id, const voidvoid *shm_addr, int shmflg);
- 首先先使用shmget建立一块共享内存,然后向该内存中写入数据并返回该共享内存shmid
使用另一个程序通过上一程序返回的shmid读该共享内存内的数据
建立共享内存并写入数据的程序
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <errno.h>
void get_buf(char *buf)
{
int i=0;
while((buf[i]=getchar())!='\n'&&i<1024)
i++;
}
int main(void)
{
int shmid;
shmid=shmget(IPC_PRIVATE,sizeof(char)*1024,IPC_CREAT|0666);
if(shmid==-1)
{
perror("shmget");
}
char *buf;
if((int)(buf=shmat(shmid,NULL,0))==-1)
{
perror("shmat");
exit(1);
}
get_buf(buf);
printf("%d\n",shmid);
return 0;
}
- 读取数据的程序
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int shmid;
shmid=atoi(argv[1]);
char *buf;
if((int)(buf=shmat(shmid,NULL,0))==-1)
{
perror("shmat");
exit(1);
}
printf("%s\n",buf);
shmdt(buf);
return 0;
}
命令行的第一个参数设为第一个程序输出的数字
如:
使用完以后可以使用
ipcrm -m 1867788
来删除该共享内存。
有一个很详细的实例:
管道
管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。
- 管道的特点:
1、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
2、只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。比如fork或exec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程。当父进程与使用fork创建的子进程直接通信时,发送数据的进程关闭读端,接受数据的进程关闭写端。
3、单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
4、数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
管道的实现机制:
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
管道只能在本地计算机中使用,而不可用于网络间的通信。下面是一个父子进程利用管道通信的实例:
main函数创建两个管道,并用fork生成一个子进程,客户端作为父进程运行,服务器则作为子进程运行。第一个管道用于从客户向服务器发送路径名, 第二个管道用于从服务器向客户发送该文件的内容。
/*
* main函数创建两个管道,并用fork生成一个子进程
* 客户端作为父进程运行,服务器则作为子进程运行
* 第一个管道用于从客户向服务器发送路径名
* 第二个管道用于从服务器向客户发送该文件的内容
*
* cin --客户端写pipe1[1]-----pipe1[0]服务器读
* 服务器写pipe2[1]-----pipe2[0]客户端读
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
//服务器从rfd中读取文件的名字,向wfd中写入文件的内容
void server(int rfd, int wfd)
{
char fileName[1024];
char fileContent[2048];
memset(fileName, 0, 1024);
memset(fileContent, 0, 2048);
//从rfd中读取文件的名字,
int n = read(rfd, fileName, 1024);
fileName[n] = 0;
printf("server receive the file name is %s\n", fileName);
int filefd = open(fileName, O_RDONLY);//打开文件
if(filefd < 0){
printf("open error\n");
return;
}
else{//读取文件的内容,并写入到wfd中
//读取文件的内容到fileContent中
int num = 0;
while((num = read(filefd, fileContent, 2048)) > 0){
printf("server read the fileContent is: %s", fileContent);
//将fileContent中的内容写入到wfd中
write(wfd, fileContent, num);
}
}
close(filefd);
close(rfd);
close(wfd);
}
//客户端从rfd中读取文件的内容,向wfd中写入文件的名字
void client(int rfd, int wfd)
{
char fileName[1024];
char fileContent[2048];
memset(fileName, 0, 1024);
memset(fileContent, 0, 2048);
printf("输入文件名字:");
//从标准输入输入文件的名字
fgets(fileName, 1024, stdin);
int len = strlen(fileName);
if(fileName[len-1] == '\n')
len--;
//向wfd中写入文件的名字
write(wfd, fileName, len);
printf("fileName = %s\n", fileName);
//从rfd中读取文件的内容
int n;
while((n = read(rfd, fileContent, 2048)) > 0){
printf("client receive the content is: %s", fileContent);
}
close(rfd);
close(wfd);
}
//主函数
int main()
{
//创建两个管道.
int pipe1[2], pipe2[2];
int ret = pipe(pipe1);
if(ret < 0){
printf("pipe error\n");
return -1;
}
ret = pipe(pipe2);
if(ret < 0){
printf("pipe error\n");
return -1;
}
//创建一个子进程,作为服务器,用于读取管道1(pipe1[0])中的文件名,并将文件的内容输出到管道2的pipe2[1]中
pid_t child_pid = fork();
if(child_pid < 0){
printf("fork error\n");
return -1;
}
else if(child_pid > 0){//父进程
//关闭管道1的写,关闭管道2的读, pipe1[1], pipe2[0]
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
}
else if(child_pid == 0){//子进程
//关闭管道1的读,关闭管道2的写, pipe1[0], pipe2[1]
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]);
}
waitpid(child_pid, NULL, 0);//等待子进程退出
return 0;
}
FIFO
有名管道用mkfifo函数创建。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
命名管道和匿名管道区别:
1:管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
2:如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
3:命名管道是一种特殊类型的文件;
使用命名管道实例:
进程1创建命名管道myfifo,并以阻塞方式写打开,进程1从标准输入获得数据并写入到命名管道中;
进程2以阻塞方式读打开该命名管道文件myfifo,进程2从该命名管道中读取数据并输出到标准输出上;
- 进程1:write_fifo.cpp
#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
/*
1:创建命名管道
2:从标准输入输入内容到buffer中
3:打开命名管道,将buffer中的内容写入到命名管道中
*/
int main()
{
int ret = mkfifo("myfifo", 0666);
if(ret < 0){
cerr << "mkfifo error..." << endl;
exit(-1);
}
char buff[1024];
memset(buff, 0, 1024);
int wrfd;
cout << "wating for another process open the myfifo to reading..."<< endl;
wrfd = open("myfifo", O_WRONLY);
if(wrfd == -1){
cerr << "open error..." << endl;
exit(-1);
}
pid_t pid = getpid();
cout << "process " << pid << " write: ";
while(cin.getline(buff, 1024)){
write(wrfd, buff, strlen(buff));
memset(buff, 0, 1024);
cout << "process " << pid << " write: ";
}
close(wrfd);
exit(0);
}
- 进程2:read_fifo.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdlib>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
/*
1:创建命名管道
2:从标准输入输入内容到buffer中
3:打开命名管道,将buffer中的内容写入到命名管道中
*/
int main()
{
char buff[1024];
memset(buff, 0, 1024);
int rdfd;
int ret = 0;
rdfd = open("myfifo", O_RDONLY);
if(rdfd < 0){
cout << "open error..." << endl;
exit(-1);
}
cout << "waiting for reading...\n";
while(1){
ret = read(rdfd, buff, 1024);
if(ret == 0){
cerr << "end of read..." << endl;
break;
}
cout << "process "<< getpid() << " read: " << buff << endl;
memset(buff, 0, 1024);
}
close(rdfd);
exit(0);
}
运行时就是write_fifo写进程创建管道,并向管道中写入数据,read_fifo读进程从管道中读数据。
信号
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
- 信号的处理——signal函数
程序可用使用signal函数来处理指定的信号,主要通过忽略和恢复其默认行为来工作。signal函数的原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
- 给出一个例子来说明,源文件名为signal1.c,代码如下:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void ouch(int sig)
{
printf("\nOUCH! - I got signal %d\n", sig);
//恢复终端中断信号SIGINT的默认行为
(void) signal(SIGINT, SIG_DFL);
}
int main()
{
//改变终端中断信号SIGINT的默认行为,使之执行ouch函数
//而不是终止程序的执行
(void) signal(SIGINT, ouch);
while(1)
{
printf("Hello World!\n");
sleep(1);
}
return 0;
}
可以看到,第一次按下终止命令(ctrl+c)时,进程并没有被终止,面是输出OUCH! - I got signal 2,因为SIGINT的默认行为被signal函数改变了,当进程接受到信号SIGINT时,它就去调用函数ouch去处理,注意ouch函数把信号SIGINT的处理方式改变成默认的方式,所以当你再按一次ctrl+c时,进程就像之前那样被终止了。
消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
- 在Linux中使用消息队列
Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。 - msgget函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t, key, int msgflg);
- msgsnd函数
该函数用来把消息添加到消息队列中。它的原型为:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函数返回的消息队列标识符。
- msgrcv函数
该函数用来从一个消息队列获取消息,它的原型为
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msgid, msg_ptr, msg_st的作用也函数msgsnd函数的一样。
msgreceive和msgsned来表示接收和发送信息。根据正常的情况,允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。
- 接收信息的程序源文件为msgreceive.c的源代码为:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid = -1;
struct msg_st data;
long int msgtype = 0; //注意1
//建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//从队列中获取消息,直到遇到end消息为止
while(running)
{
if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
{
fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s\n",data.text);
//遇到end结束
if(strncmp(data.text, "end", 3) == 0)
running = 0;
}
//删除消息队列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
- 发送信息的程序的源文件msgsend.c的源代码为:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_TEXT 512
struct msg_st
{
long int msg_type;
char text[MAX_TEXT];
};
int main()
{
int running = 1;
struct msg_st data;
char buffer[BUFSIZ];
int msgid = -1;
//建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//向消息队列中写消息,直到写入end
while(running)
{
//输入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(data.text, buffer);
//向队列发送数据
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
//输入end结束输入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
exit(EXIT_SUCCESS);
}
msgreceive.c文件main函数中定义的变量msgtype(注释为注意1),它作为msgrcv函数的接收信息类型参数的值,其值为0,表示获取队列中第一个可用的消息。再来看看msgsend.c文件中while循环中的语句data.msg_type = 1(注释为注意2),它用来设置发送的信息的信息类型,即其发送的信息的类型为1。所以程序msgreceive能够接收到程序msgsend发送的信息。
消息队列与命名管道的比较
消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。
与命名管道相比,消息队列的优势在于
1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
- 参考
第十周课下作业-IPC的更多相关文章
- 20155326 第十周课下作业-IPC
20155326 第十周课下作业-IPC 学习题目: 研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接 共享内存 管道 FIFO 信号 消息队列 学习过程 -IPC ...
- 2017-2018-1 20155320第十周课下作业-IPC
2017-2018-1 20155320第十周课下作业-IPC 研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接 共享内存 管道 FIFO 信号 消息队列 共享内存 ...
- 20155339 《信息安全系统设计》第十周课下作业-IPC
20155339 <信息安全系统设计>第十周课下作业-IPC 共享内存 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的 ...
- 20155322 2017-2018-1《信息安全系统设计》第十周 课下作业-IPC
20155322 2017-2018-1<信息安全系统设计>课下作业-IPC 作业内容 研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接. 共享内存 管 ...
- 20155219 第十周课下作业-IPC
题目:研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接 共享内存 管道 FIFO 信号 消息队列 1.共享内存 共享内存就是允许两个不相关的进程访问同一个逻辑内存. ...
- 20165234 《Java程序设计》第十周课下作业
相关知识点的总结 泛型 Java 泛型的主要目的是可以建立具有类型安全的集合框架,如链表.散列映射等数据结构. 可以使用“class 名称<泛型列表>”声明一个类,为了和普通的类有所区别, ...
- #学号 20175201张驰 《Java程序设计》第10周课下作业MyList
参见附件,补充MyList.java的内容,提交运行结果截图(全屏) 课下推送代码到码云 public class MyList { public static void main(String [] ...
- 20165234 《Java程序设计》第二周课下作业
1. 教材代码完成情况测试P14 把100改为自己的后四位学号,编译运行Kernighan.java 代码的功能是从给定一个数字,实现从1依次加到此数的和. 如下是我用命令行实现代码的编译与运行. 2 ...
- 2017-2018-1 20155330 《信息安全系统设计基础》第10周课堂测试&课下作业
2017-2018-1 20155330 <信息安全系统设计基础>第10周课堂测试&课下作业 stat命令的实现-mysate 学习使用stat(1),并用C语言实现 提交学习st ...
随机推荐
- 使用Babel和ES7创建JavaScript模块
[编者按]本文主要介绍通过 ES7 与 Babel 建立 JavaScript 模块.文章系国内 ITOM 管理平台 OneAPM 工程师编译呈现,以下为正文. 去年,新版的JavaScript发布了 ...
- mysql常用语句备忘
1.连接本地数据库 mysql -h localhost -u root -p123 2.连接远程数据库 mysql -h 192.168.0.201 -P 3306 -u root -p123 3. ...
- 如何监视和更新 Azure 中的 Linux 虚拟机
为确保 Azure 中的虚拟机 (VM) 正常运行,可以查看启动诊断.性能指标,并管理程序包更新. 本教程介绍如何执行下列操作: 在 VM 上启用启动诊断 查看启动诊断 在 VM 上启用诊断扩展 基于 ...
- Azure 镜像市场发布商指南
Azure 镜像市场发布商指南 本指南提供独立软件供应商产品上架到 Azure 镜像市场(以下简称 Azure 镜像市场)需要遵循的全流程. 文档适用范围 本指南适用于希望通过由世纪互联运营的Micr ...
- 解决eclipse 文件更新不自动刷新的问题
打开eclipse 1. Window ===> Preferences ===> General ===> Workspace 2. 勾选 1> Refresh using ...
- 转:APPlication,Session和Cookie的区别
方法 信息量大小 保存时间 应用范围 保存位置 Application 任意大小 整个应用程序的生命期 所有用户 服务器端 Session 小量,简单的数据 用户活动时间+一段延迟时间(一般为20分钟 ...
- 前端 网络三剑客之html 01
一.引语 1.html是什么? 1.超文本标记语言(Hypertext Markup Language):简称HTML或html.是通过标签语言来标记要显示的网页中的各个部分. 2.它遵循一套浏览器的 ...
- Linux fdisk命令详解[主分区/逻辑分区创建]
fdisk常见命令参数 -b<分区大小>:指定每个分区的大小: -l:列出指定的外围设备的分区表状况: -s<分区编号>:将指定的分区大小输出到标准输出上,单位为区块: -u: ...
- 铁乐学python_day18-19_面向对象编程1
以下笔记绝大部分(百分之80或以上)摘自我的授课老师之一:老男孩教育中的景老师. 她上课讲的知识点由浅入深,引人入胜,听她的课完全不会感觉到困阿,而且不知不觉中就感觉掌握了. 她的博客是: http: ...
- python字典去重脚本
#!/usr/bin/env python # encoding: utf-8 #字典去重小代码 import sys import os import platform try: pass exce ...