课下作业-IPC
研究Linux下IPC机制:原理,优缺点,每种机制至少给一个示例,提交研究博客的链接
- 共享内存
- 管道
- FIFO
- 信号
- 消息队列
共享内存
共享内存的使用过程可分为 创建->连接->使用->分离->销毁 这几步。
共享内存(shared memory)是最简单的Linux进程间通信方式之一。使用共享内存,不同进程可以对同一块内存进行读写。由于所有进程对共享内存的访问就和访问自己的内存空间一样,而不需要进行额外系统调用或内核操作,同时还避免了多余的内存拷贝,所以,这种方式是效率最高、速度最快的进程间通信方式。
这种最大限度的自由也给共享内存带来了缺点:内核并不提供任何对共享内存访问的同步机制,比如同时对共享内存的相同地址进行写操作,则后写的数据会覆盖之前的数据。所以,使用共享内存一般还需要使用其他IPC机制(如信号量)进行读写同步与互斥。
管道
管道是一种最基本的 IPC机制,由pipe函数创建:头文件: #include
<unistd.h>
函数名:int pipe(int file
des[2]); 调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端,一个写端。然后通
过filedes
参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的
写端(很好记,就像0是标准输入
1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或write(filedes[1]);向这
个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。
★管道的分类:
①普通管道(PIPE):通常有两种限制,一是单工,即只能单向传输;二是血缘,即常用于父子进程间(或有血缘关
系的进程间)。
②流管道(s_pipe):去除了上述的第一种限制,实现了双向传输。
③命名管道(name_pipe):去除了上述的第二种限制,实现了无血缘关系的不同进程间通信。
fifo
fifo read端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100
int main()
{
int fd = open(_PATH_, O_RDONLY);
if(fd < 0){
printf("open file error!\n");
return 1;
}
char buf[_SIZE_];
memset(buf, '\0', sizeof(buf));
while(1){
int ret = read(fd, buf, sizeof(buf));
if (ret <= 0)//error or end of file
{
printf("read end or error!\n");
break;
}
printf("%s\n", buf);
if( strncmp(buf, "quit", 4) == 0 )
{
break;
}
}
close(fd);
return 0;
}
fifo write端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100
int main()
{
int ret = mkfifo(_PATH_,0666|S_IFIFO);
if(ret == -1){
printf("mkfifo error\n");
return 1;
}
int fd = open(_PATH_, O_WRONLY);
if(fd < 0){
printf("open error\n");
}
char buf[_SIZE_];
memset(buf, '\0', sizeof(buf));
while(1){
scanf("%s", buf);
int ret = write(fd, buf, strlen(buf)+1);
if(ret < 0){
printf("write error\n");
break;
}
if( strncmp(buf, "quit", 4) == 0 )
{
break;
}
}
close(fd);
return 0;
}
信号
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数;(3)执行缺省操作,Linux对每种信号都规定了默认操作,详细情况请参考[2]以及其它资料。注意,进程对实时信号的缺省反应是进程终止。
Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。
- 实例一:信号发送及处理
实现一个信号接收程序sigreceive(其中信号安装由sigaction()。
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void new_op(int,siginfo_t*,void*);
int main(int argc,char**argv)
{
struct sigaction act;
int sig;
sig=atoi(argv[1]);
sigemptyset(&act.sa_mask);
act.sa_flags=SA_SIGINFO;
act.sa_sigaction=new_op;
if(sigaction(sig,&act,NULL) < 0)
{
printf("install sigal error\n");
}
while(1)
{
sleep(2);
printf("wait for the signal\n");
}
}
void new_op(int signum,siginfo_t *info,void *myact)
{
printf("receive signal %d", signum);
sleep(5);
}
说明,命令行参数为信号值,后台运行sigreceive signo &,可获得该进程的ID,假设为pid,然后再另一终端上运行kill -s signo pid验证信号的发送接收及处理。同时,可验证信号的排队问题。
注:可以用sigqueue实现一个命令行信号发送程序sigqueuesend。
消息队列
消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写
权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息
。消息队列是随内核持续的。也就是说进程的退出,如果不自主去释放资源,消息队列是会悄无声息的存在的。所以
较管道来说,消息队列的生命周期更加持久。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息 来避免
命名管道的同步和阻塞问题。消息队列与管道不同的是,消息队列是基于消息的, 而管道是基于字节流的,且消息队
列的读取不一定是先入先出。消息队列与命名管道有一 样的不足,就是每个消息的最大长度是有上限的
(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限
用消息队列对客户端和服务端间通信的模拟:
1.comm.h
#pragma once
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define _PATH_NAME_ "/tmp"
#define _PROJ_ID_ 0x6666
#define _SIZE_ 1024
extern int server_type;
extern int client_type;
struct msgbuf
{
long mtype;
char mtext[_SIZE_];
};
int creat_msg_queue();
int get_msg_queue();
//int creat_msg_queue(int msg_id);
int send_msg(int msg_id,int send_type,const char* msg);
int recv_msg(int msg_id,int recv_type,char* msg_out);
int destroy_queue(int msg_id);
2.comm.c
#include"comm.h"
int server_type = 1;
int client_type = 2;
static int comm_msg_queue(int flags)
{
key_t _key = ftok(_PATH_NAME_,_PROJ_ID_);
if(_key < 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
int msg_id = msgget(_key,flags);
//int msg_id = msgget(_key,IPC_CREAT | IPC_EXCL | 0666);
return msg_id;
}
int creat_msg_queue()
{
int flags = IPC_CREAT | IPC_EXCL | 0666;
return comm_msg_queue(flags);
}
int get_msg_queue()
{
int flags = IPC_CREAT;
return comm_msg_queue(flags);
}
int destroy_queue(int msg_id)
{
if(msgctl(msg_id,IPC_RMID,NULL) != 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
return 0;
}
int send_msg(int msg_id,int send_type,const char* msg)
{
struct msgbuf _buf;
_buf.mtype = send_type;
strncpy(_buf.mtext,msg,strlen(msg)+1);
if(msgsnd(msg_id,&_buf,sizeof(_buf.mtext),0) < 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
return 0;
}
int recv_msg(int msg_id,int recv_type,char* msg_out)
{
struct msgbuf _buf;
_buf.mtype = 0;
memset(_buf.mtext,'\0',sizeof(_buf.mtext));
if(msgrcv(msg_id,&_buf,sizeof(_buf.mtext),recv_type,0) < 0)
{
printf("%d : %s",errno,strerror(errno));
return -1;
}
strcpy(msg_out,_buf.mtext);
return 0;
}
3.server.c
#include"comm.h"
int main()
{
int msg_id = creat_msg_queue();
if(msg_id <0)
{
printf("%d : %s\n",errno,strerror(errno));
return 1;
}
char buf[_SIZE_];
while(1)
{
memset(buf,'\0',sizeof(buf));
recv_msg(msg_id,client_type,buf);
printf("client:%s\n",buf);
if(strcasecmp(buf,"quit") == 0)
{
break;
}
printf("client say done,Please Enter# ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s > 0)
{
buf[_s - 1] = '\0';
}
send_msg(msg_id,server_type,buf);
}
destroy_queue(msg_id);
return 0;
}
4.client.c
#include"comm.h"
int main()
{
int msg_id = get_msg_queue();
char buf[_SIZE_];
while(1)
{
printf("Please Enter:");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s >0 )
{
buf[_s - 1] = '\0';
}
send_msg(msg_id,client_type,buf);
if(strcasecmp(buf,"quit") == 0)
{
break;
}
memset(buf,'\0',sizeof(buf));
recv_msg(msg_id,server_type,buf);
printf("server# %s\n",buf);
}
return 0;
}
课堂测试
要求
学习使用stat(1),并用C语言实现
提交学习stat(1)的截图
man -k ,grep -r的使用
伪代码
产品代码 mystate.c,提交码云链接
测试代码,mystat 与stat(1)对比,提交截图
补充