进程间通信----管道、消息队列、共享内存、信号量

一、进程间通信(Inter Process Communication)

1.目的
1)数据传输
2)资源共享
3)通知事件
4)进程控制
注:每个进程都有各自不同的用户地址空间,进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间中拷贝到缓冲区,进程B再从缓冲区将数据读走,内核提供的这种机制就是进程间通信。
2.说明
1)本质:两个毫不相干的进程,看到同一份资源(临界资源)。
2)进程用户空间是相互独立的,唯一的例外是共享内存区。
3.分类
1)管道
2)消息队列
3)共享内存
4)信号量

二、管道(pipe)

1.概念:从一个进程到另一个进程的数据流称为管道,管道也是一个文件。
2.匿名管道

#include <unistd.h>
功能:创建无名管道
int pipe(int fd[2]);
参数:fd,文件描述符数组,0代表读端,1代表写端//读写数据的本质就是往内核缓冲区读写数据。
返回值:成功返回0,失败返回错误代码

3.管道的特点
1)具有血缘关系,才可以进行通信。
2) 只能够单向传输(半双工)。
3)管道通信,基于字节流。
4)依赖于文件系统,生命周期随进程。
5) 管道是自带同步机制的.(互斥:一段时间内,只能有一个使用,原子性:只有两种状态,要么做,要么不做)。
4.管道在文件描述符角度看待
1)父进程创建管道。
2)父进程fork出子进程。
3)父进程关闭fd[0],子进程关闭fd[1]。
文件描述符

#include <unistd.h>
#include <stdlib.h>
#include <error.h>
#include <stdio.h>
#include <string.h>

#define ERR_EXIT(m)\
    do\
{\
    perror(m);\
    exit(EXIT_FAILURE);\
}while(0);

int main()
{
    int pipefd[2];
    if(pipe(pipefd)<0){
        ERR_EXIT("pipe");
    }
    pid_t pid;
    pid=fork(); 
    //调用成功将子进程的pid返回给父进程,失败返回-1给父进程。
    //调用成功会有两个返回值,对于父进程返回子进程的pid,对于子进程返回的是0
    if(pid==-1){   
        ERR_EXIT("fork");
    }
    if(pid==0){
        close(pipefd[0]);
        write(pipefd[1],"hello",5);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    }
    close(pipefd[1]);
    char buf[10]={0};
    read(pipefd[0],buf,10);
    close(pipefd[0]);
    printf("buf=%s\n",buf);
    return 0;
}

5.管道的一些说明
1)文件描述符是一个资源。
2)双方通信,需要建立起两个管道。
3)打开管道就是打开了一个文件,通过read()或者write()向文件内读写数据,读写的本质是往内核缓冲区内读写数据。
6.管道读写规则
1)当所有管道写端被关闭时,read返回0,相当于读到文件末尾。
2)如果写端未关闭,且不写,读端读完管道剩余的数据后,进入阻塞等待,直到有数据来到为止。
3)如果读端关闭,写端未关闭,write操作会触发SIGPIPE信号,使之退出。
4)如果读端未关闭,且不读,此时有写端写入,管道被写满后被阻塞,直到管道内有空位置才写入数据并返回。

三、命名管道(fifo)

1.概念:在不相关的进程之间进行通信,是一种特殊类型 的文件。
2.命名管道的创建
1)命令行:mkfifo filename
2)函数创建:int mkfifo(const char *filename,mode_t mode); //打开使用open
3.匿名管道与命名管道的区别
1)创建与打开方式不同,一旦创建并打开后,其意义是一样的。
2)匿名管道在内存中是不可见的,局限于父子之间。
3)命名管道文件在硬盘上,是可见的。
4.使用命名管道实现文件拷贝、使用命名管道实现server&client通信
1)https://github.com/CCTVYI/first_test/blob/master/fifo_filecopy
//文件拷贝效果
这里写图片描述
注:需要先创建一个文件abc,先将源文件传输到管道,在从管道输入到目的文件中。
进程间通信,需要在两个终端中运行,此例中由管道当媒介进行两个进程之间的通信。
2)https://github.com/CCTVYI/first_test/blob/master/fifo_server_client
//结果示例
这里写图片描述

四、消息队列(msg)

1.概念:可以发送有类型、数据块的方法。可以互相通信,生命周期随内核。但是消息队列也有它的不足,消息、消息队列总的字节数以及系统消息队列的总数都有一个上限。
注:消息队列是一个链表,链表的每一个结点都是一个消息,消息队列存放在内核中并由消息队列标识符标识。
2.消息队列的一些说明
1)内核为每个IPC对象维护一个数据结构

struct ipc_perm {
key_t __key; /* Key supplied to xxxget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

2)消息队列函数
a.msgget

功能:用来创建和访问一个消息队列
int msgget(key_t key, int msgflg);
//key: 某个消息队列的名字
//msgflg:由九个权限标志构成,类似mode模式标志是一样的
//返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1

b.msgctl

功能:消息队列的控制函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//msqid: 由msgget函数返回的消息队列标识码
//cmd:是将要采取的动作,(有三个可取值),常用IPC_RMID(删除消息队列)。
//返回值:成功返回0,失败返回-1

c.msgsnd

功能:把一条消息添加到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发⽣生的事情,一般为0
msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误
返回值:成功返回0;失败返回-1
消息结构参考形式如下:
struct msgbuf {
    long mtype;  //当mtype=1的消息都属于同一类消息。
    char mtext[1];
}

d.msgrcv

功能:是从一个消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息⻓长度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1

3.实现一个消息队列
https://github.com/CCTVYI/first_test/blob/master/msg
//显示效果
这里写图片描述
注:当下次运行时,应将消息队列资源手动删除(ipcrm -q msgid),查看IPC资源(ipcs -q)
4.ipcs和ipcrm命令(Q)
1)ipcs:显示ipc资源
2)ipcrm:手动删除ipc资源
3)显示消息队列:ipcs -q
4)删除消息队列:ipcrm -q 35412

五、共享内存(shm)

1.概念:允许两个不相关的进程访问同一个逻辑内存。
共享内存是最快的IPC资源,省了两次拷贝,不在涉及内核;
进程不在通过执行内核中的系统调用来传递彼此的数据;
生命周期随内核;
注:
1)优点是数据的共享使进程间的数据不用传送,而是直接访问内存,加快了效率
2)缺点是共享内存没有互斥与同步机制,所以我们需要使用其他方法来进行进程间的同步工作。
3)没有同步,也就是说当一个程序对共享内存操作的时候,并无自动机制去阻止第二个程序去操作它。
4)不同进程之间共享的内存通常为同一段物理内存,所以会导致进程之间互相产生影响。
2.共享内存的一些说明

1)shmget

功能:用来创建共享内存
int shmget(key_t key, size_t size, int shmflg);
//key:这个共享内存段名字
//size:共享内存⼤大⼩小
//shmflg:由九个权限标志构成,类似于mode模式标志
//返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

2)shmat

功能:将共享内存段连接到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);//shmaddr为NULL,核心自动选择一个地址
//shmid: 共享内存标识
//shmaddr:指定连接的地址
//shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
//返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

3)shmdt

功能:将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);
//shmaddr: 由shmat所返回的指针
//返回值:成功返回0;失败返回-1
//注意:将共享内存段与当前进程脱离不等于删除共享内存段

4)shmctl

功能:⽤用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid:由shmget返回的共享内存标识码
//cmd:将要采取的动作(有三个可取值),常用IPC_RMID
//buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
//返回值:成功返回0;失败返回-1

3.实现一个共享内存
https://github.com/CCTVYI/first_test/blob/master/shm
//效果预览
这里写图片描述
当你使用完共享内存后,可以进行手动删除,于是乎
这里写图片描述
你会发现131072总是删除不了,key值也是清一色的0,一般原因就两个,权限不够,进程占用。
解决办法:http://www.xuebuyuan.com/944653.html
原因就是:共享内存虽然被删除了,但进程仍然占用着共享内存,所以才出现key为0x00000000的现象。
4.ipcs与ipcrm(M)
1)ipcs -m:显示shm ipc资源。
2)ipcrm -m 35412:手动删除shm ipc资源。

六、信号量(sem-Semaphore)

1.作用:用于同步和互斥的,信号与信号量是两种不同的事物,通过生成令牌来授权,在任意时刻只能有一个执行线程访问代码的临界区域。信号量用来协调各个进程使用共享资源的访问的。
2.进程互斥
1)临界资源(互斥资源):同一时间只能有一个进程使用。
2)进程需要竞争某些互斥资源,进程之间这种关系称为进程的互斥
3.进程同步
多个进程需要互相配合完成某项任务。
4.P、V操作
1)互斥:PV在同一进程中
2)同步:PV在不同进程
3)P:减减操作,相当于可用资源数减少。
4)V:加加操作,相当于可用资源数增加。
5.信号量的本质
实际上就是一个计数器,程序对其访问都是原子操作,当一个进程执行了P操作,它将得到信号量,并可以进入临界区,信号量个数减1,第二个进程看见信号量此时为0,它会被挂起一直等待第一个进程结束并进行V操作,这时,第二个进程就可以执行。

struct semaphore
{
    int value;
    pointer_PCB queue;
}

6.信号量的相关函数
1)semget

功能:⽤用来创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg);
//key: 信号集的名字
//nsems:信号集中信号量的个数
//semflg: 由九个权限标志构成
//返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1

2)semctl

功能:⽤用于控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
//semid:由semget返回的信号集标识码
//semnum:信号集中信号量的序号
//cmd:将要采取的动作(有三个可取值)
//最后一个参数根据命令不同⽽而不同
//返回值:成功返回0;失败返回-1

3)semop

功能:⽤用来创建和访问⼀一个信号量集
int semop(int semid, struct sembuf *sops, unsigned nsops);
//semid:是该信号量的标识码,也就是semget函数的返回值
//sops:是个指向一个结构数值的指针
//nsops:信号量的个数
//返回值:成功返回0;失败返回-1

4)sembuf结构体

struct sembuf {
    short sem_num;
    short sem_op;
    short sem_flg;
};
sem_num是信号量的编号
sem_op一般只会⽤用到两个值
⼀个是“-1”,P操作,等待信号量变得可用
一个是“+1”,V操作,发出信号量已经变得可用
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO

7.两个进程同时打印,显示器为临界资源,使用二元信号量(互斥锁)进行保护。
https://github.com/CCTVYI/first_test/blob/master/sem
效果预览
这里写图片描述
先执行父进程,然后子进程。
8.ipcs与ipcrm (S)
1)ipcs -s:显示sem ipc 资源。
2)ipcrm -s 35412:手动删除sem ipc资源

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值