Linux进程间的通信:
进程间通信的方式:
- 无名管道(pipe)和有名管道(fifo)
- 消息队列
- 共享内存
- 信号量
- 信号
- 套接字
匿名管道:
pipe函数创建一个匿名管道,其函数原型:
# include<unistd.h>
int pipe(int fd[2])
参数fd[2]是一个长度为二的文件描述符;其中fd[1]是写入端,fd[0]是读出端;
返回值:
0表示创建成功,-1表示创建失败;
读写操作:
read(fd[0],buf,size_t size )
返回值:函数实际读取的字节,返回零表示读到文件末尾或者读到不可读的数据
write(fd[1],buf,size_t size)
buf:要写的内容
size:要写的字节大小
防止拥塞
对于匿名管道,读进程和写进程初始都会拥有fd[0],fd[1],两个进程各自一套,写程序时一定要注意在写进程里面 close(fd[0]),在读进程里面 close(fd[1]),在写进程写任务执行完之后务必 close(fd[1]),读进程读完之后务必 close(fd[0])。
只有在每个进程中的fd[1]关闭了之后,read()函数才会认为管道不会再写进数据了,在这种情况下,不足size的内容才会被正确的·读取出来。
文件描述符:
文件描述符:
操作系统通过一个整数打开的文件,将这个整数称为文件描述符,进程能够打开文件描述符的个数 [0,ulimit-n]
一个进程在此存在期间,会有一些文件被打开,从而会返回一些文件描述符,从shell中运行一个进程,
默认会有3个文件描述符存在(0、1、2),0与进程的标准输入相关联,1与进程的标准输出相关联,2与进程的标准错误输出相关联,
open (const char *path,int flag)
参数:path:要打开的文件
flag:打开方式
O_RDONLY:只读方式打开
O_WRONLY:只写方式打开
O_RDWR:读写方式打开
O_TRUNC:清空文件
O_APPEND:追加
返回值:
成功返回:文件描述符
失败返回-1
stdin 0
stdout 1
stderr 2
创建文件
int open(const char *path,int flag,mode_t mode)
path:要创建的文件名
flags:O_CREAT(文件不存在就创建)
mode:权限0644
读文件
功能:从fd文件读取数据到buf所指向的空间,该空间的大小为len
返回值:实际读取的字节数
int read(int fd,char buf,size_t len)
函数功能:会把参数所指定的文件传送len个字节到buf指针所指向的内存
返回值:函数实际读取的字节,返回零表示读到文件末尾或者读到不可读的数据
写文件:
int write(int fd,const char *buf,size_len)
函数功能:往fd所指向的文件写入数据,数据的起始地址为buf,大小为len。
定位;
lseek(int fid,off_t offset,int whence)
offset:偏移量
whence:参考位置
SEEK_SET:文件开始
SEEK_CUR:当前所在位置
SEEK_END:文件结尾
命名管道:
创建管道:int mkfifo(const char *name,mode_t mode)
int fid=open(name,O_RDONLY) //读
int fid =open(name,O_WRONLY) //写
//read和write的语义和匿名管道一样
消息队列:
什么是消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
Linux中如何使用消息队列
Linux提供了一系列消息队列的函数接口来让我们方便地使用它来实现进程间的通信。它的用法与其他两个System V PIC机制,即信号量和共享内存相似。
查看IPC对象
ipcs -q
删除IPC对象
ipcrm -Q key
系统中最多能够创建
cat /proc/sys/kernel/msgmni个消息队列
每个消息队列能够装多少字节
cat /proc/sys/kernel/msgmax
一个消息队列中所有消息的总字节数
cat /proc/sys/kernel/msgmnb
头文件:
#include <sys/msg.h>
#include <sys/ipc.h>
- 创建或者使用消息队列:msgget()函数
该函数用来创建和访问一个消息队列。它的原型为:
int msgget(key_t key, int msgflg);
返回值:
它返回一个以key命名的消息队列的标识符(非零整数),失败时返回-1.
参数:
msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息 队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
- 将消息添加到消息队列中:msgsnd()函数。它的原型为:
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
返回值:
如果调用成功,消息数据的一份副本将被放到消息队列中,并返回0,失败时返回-1.
参数:
msgid是由msgget函数返回的消息队列标识符。
msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员 变量开始的结构体,接收函数将用这个成员来确定消息的类型。消息的结构体定义如下:
struct msgbuf
{
long channel;消息类型(通道号),要>=1
char mtext[100];写上自己的消息
}
msg_sz
是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成 员变量的长度。
- 从一个消息队列获取消息:msgrcv()函数,它的原型为:
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
返回值:
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消 息失败时返回-1。
参数:
msfgid :是msgget函数返回的得消息队列标识符;
msg_ptr:一个指向接收消息的指针
msg_st:装接收消息的大小
msgtype:可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型 的第 一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。
msgflg:用于控制当队列中没有相应类型的消息可以接收时将发生的事情。//一般为0
共享内存:
共享内存的实现分为两个步骤:
一、 创建共享内存,使用shmget函数。
二、 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
创建共享内存
int shmget(key_t key ,int size,int shmflg)
key标识共享内存的键值:0/IPC_PRIVATE。当key的取值为IPC_PRIVATE,则函数shmget将创建一块新的共享内存;如果key的取值为0,而参数中又设置了IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
size:共享内存段的大小
shmflg://创建 IPC_CREAT|0644,打开 0
返回值:如果成功,返回共享内存表示符,如果失败,返回-1。
映射共享内存
int shmat(int shmid,char *shmaddr,int flag)
参数:
shmid:shmget函数返回的共享存储标识符
flag:决定以什么样的方式来确定映射的地址(通常为0)
返回值:
如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1。
共享内存解除映射
当一个进程不再需要共享内存时,需要把它从进程地址空间中多里。
int shmdt(char *shmaddr)
进程间的同步和互斥以及临界区:
同步;
这种制约主要源于进程间的合作。比如输入、计算、输出。在运行过程中,某进程可能要在某些同步点上等待另一伙伴(协作进程)为它提供消息,在未获得消息之前,该进程处于等待状态,获得信息后被唤醒进入就绪态,才能被执行。
互斥:
同处于一个系统中的进程,通常都共享着某种资源,如共享CPU、共享I/O设备等,所谓间接相互制约即源于这种资源共享,使系统中本来没有关系的进程因竞争资源产生了制约关系。
临界资源:同一时刻,只允许一个或有限个进程或线程访问的资源。
例如:
(1)多个人同时用一个笔签字,此时只能有一个人用笔写字,其他人只有等他写完才可以使用这支笔。
(2)若商场试衣间可以有3个试衣间,可以同时供3个人使用,其他人必须等到其中的试衣间没人才能使用。
(3)但是像走廊,不是临界资源,可以同时由多人同时通行。
临界区:访问临界资源的代码段。
原子操作:不可被分割或中断的操作,操作一旦开始执行,就比执行结束,中途不能被任何原因打断。
生产者与消费者
有两个进程分别为消费者进程和生产者进程,对同一个临界资源进行访问,生产者不断的将生产的产品加入缓冲区,而消费者不断的消费缓冲区中的资源,利用信号量实现两个进程的同步与互斥。
在生产者往缓冲区加入产品时,需要两个信号量,一个是互斥信号量,使得在生产者往缓存里放产品的时候其他进程不能对临界资源进行操作,另一个信号量是指示缓存区是否已满的信号量,从而判断生产者能否往缓冲区加入产品;而消费者从缓冲区中消费资源的时候,也需要两个信号量,一个信号量是互斥信号量,使得消费者在从缓冲区中取产品的时候其他进程不能对临界资源进行操作,另外一个信号量是指示缓冲区是否为空,从而判断消费者能否对缓冲区进行操作。
由以上分析,可知在该问题中共需要三个信号量,一个是用于互斥的信号量mutux=1; 一个是用于指示缓冲区空位置数目的empty=N;另外一个是指示缓冲区填满位置的信号量full = 0;
用于同步的信号量一定是位于不同进程中,用于互斥的信号量一定是位于同一个进程中。
信号量:
创建或打开信号量
int semget(key_t key,int nsem //信号量集中信号量的个数,int flags//打开0 ,创建IPC_CREAT|0644)
参数:
flags:最后一个参数又是和消息队列的flg参数一模一样:IPC_CREAT:存在则打开,否则创建;
设置信号量初值:
semctl(semid,int semnum//信号量集中的第几个信号量,int cmd,su//信号量初值)
查看信号量的值
semctl(semid,int semnum//信号量集中的第几个信号量,int cmd,0)
返回值:当前信号量的值
pv操作:
semop(int semid,struct sembuf sb[],int flags)
struct sembuf{
short sem_num;//信号量的下标,表示对信号量集中的哪一个信号量操作
short sem_op;/1 表示v操作 加一,-1 表示p操作 减一
short sem_flag;//0
};
二元信号量P/V操作
put.c
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# include <sys/sem.h>
# include <string.h>
void v(int id)
{
struct sembuf sb[2]={0,1,0,0,-1,0};
semop(id,sb,1);
}
void p(int id)
{
struct sembuf sb[2]={0,1,0,0,-1,0};
semop(id,sb,1);
}
union semun
{
int val;
};
int main(void)
{
int shmid=shmget(1235,sizeof(int),IPC_CREAT|0600);
if(shmid==-1)perror("shmget"),exit(1);
int shmid1=shmget(1234,0,0);
int semid=semget(1235,1,IPC_CREAT|0600);
int semid1=semget(1234,0,0);
if(semid==-1)perror("semget"),exit(-1);
union semun su={1};
int *pv=shmat(shmid,NULL,0);
int *pv1=shmat(shmid1,NULL,0);
int num=0;
while(1){
sleep(1);
*pv=num++;
printf("put ok:%d\n",num-1);
v(semid);
p(semid1);
printf("p1=:%d\n",*pv1);
}
}
get.c
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/ipc.h>
# include <sys/shm.h>
# include <sys/sem.h>
# include <string.h>
void p(int id)
{
struct sembuf sb[2]={0,-1,0,0,1,0};
semop(id,sb,1);
}
void v(int id)
{
struct sembuf sb[2]={0,-1,0,0,1,0};
semop(id,sb,1);
}
union semun
{
int val;
};
int main(void)
{
int shmid1=shmget(1234,sizeof(int),IPC_CREAT|0600);
if(shmid1==-1)perror("shmget"),exit(1);
int shmid=shmget(1235,0,0);
if(shmid==-1)perror("shmget"),exit(1);
int semid1=semget(1234,2,IPC_CREAT|0600);
if(semid1==-1)perror("semget"),exit(-1);
int semid=semget(1235,0,0);
if(semid==-1)perror("semget"),exit(-1);
union semun su ={2};
int *pv=shmat(shmid,NULL,0);
int *pv1=shmat(shmid1,NULL,0);
int num1=0;
while(1){
sleep(1);
*pv1=num1++;
printf("put1 ok:%d\n",num1-1);
v(semid1);
p(semid);
printf("p=:%d\n",*pv);
}
}