我们都知道Linux是从Unix发展来的,早期的Unix进程间通过无名管道(pipe)、有名管道(fifo)、信号(signal)的方式进行通信。在System V中进程间通信又有共享内存(share memory)、消息队列(message queue)、信号灯级(semaphore set)的方式。在BSD Unix中进程间通信是通过套接字(socket)。以上的这些进程间通信的方式Linux都继承了下来。
上一篇文章主要内容是Linux继承Unix进程间通信的方式,主要包括管道、信号量的方式实现进程间通信。这篇文章主要讲的是Linux继承System V的进程间通信的方式。
往期推荐
Linux下并发程序设计(1)——进程
Linux下并发程序设计(2)——线程
Linux下并发程序设计(3)——进程间通信
在使用IPC对象通信时,进程创建IPC对象前需要指定一个Key(key为0时,只能被当前进程访问。要被多个进程访问,就需要指定非零Key值)。ftok函数也可以创建Key值,通过key值创建一个共享内存、消息队列或者信号灯集。
#include
#include
key_t ftok(const char *path,int proj_id);
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
key_t key;//key_t等价于一个整形
if((key=ftok(".",'a'))==-1)//每一个进程必须生成相同的key,参数必须一致。参数一致才能得到相同的key值
{
perror("key");
exit(-1);
}
}
#include
#include
int shmget(key_t key,int size,int shmflg);
IPC_PRIVATE
(0 私有的进程)或ftok生成(多个进程)示例1:创建一个私有的共享内存,大小为512字节,权限为0666
int shmid;
if((shmid=shmget(IPC_PRIVATE,512,0666))<0){
perror("shmget");
exit(-1);
}
示例2:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666
key_t key;
int shmid;
if((key=ftok(".",'m'))==-1)//使用相同的参数才能得到相同值的key
{
perror("ftok");
exit(-1);
}
if((shmid=shmget(key,1024,IPC_CREAT|0666))<0){//不存在就创建,存在就打开
perror("shmget");
exit(-1);
}
#include
#include
void *shmat(int shmid,const void *shmaddr,int shmflg);
例如:在共享内存中存放键盘输入的字符串
char *addr
int shmid;
......
if((addr=(char *)shmat(shmid,NULL,0))==(char *)-1)//0表示对共享地址可读可写,并且shmid强转为字符指针
{
perror("shmat");
exit(-1);
}
fgets(addr,N,stdin);
...
#include
#include
int shmdt(void *shmaddr);
#include
#include
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
ipcs -l
可以查看共享内存的详细信息cat /proc/sys/kernel/shmmax
存放的为当前系统中共享内存最大大小当我们创建好一个消息队列以后,系统会创建一个结构体(包括消息队列的大小、个数、权限、key值等等)。在消息队列中不同的消息是分别存储的,对于每种不同的消息,消息列表中会有对应的链表,链式的队列来存放。当收到某种消息的时候会存放在对应的消息队列中,当收到一个新的消息类型时,系统会创建一个新的消息列表。
#include
#include
int msgget(key_t key,int msgflg);
#include
#include
int msgsnd(int msgid,const void *msgp,size_t size,int msgflg);
消息格式:
消息发送示例:
typedef struct{
long mtype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG)-sizeof(long))
int main()
{
MSG buf;
...
buf.mtype=100;
fgets(buf.mtext,64,stdin);//键盘输入字符串保存到buf.mtext
msgsnd(msgid,&buf,LEN,0);
...
return 0;
}
#include
#include
int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int msgflg);
消息接收示例:
typedef struct{
long mtype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG)-sizeof(long))
int main()
{
MSG buf;
...
if(msgrcv(msgid,&buf,200,0)<0{
perror("msgrcv");
exit(-1);
}
...
}
#include
#include
int msgctl(int msgid,int cmd,struct msqid_ds *buf);
两个进程通过消息队列轮流将键盘输入的字符串发送给对方,接收并打印对方发送的信息,直到有一方退出为止。
clientA.c内容如下:
#include
#include
#include
#include
#include
#include
#include
typedef struct
{
long mtype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG)-sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
key_t key;
int msgid;
MSG buf;
if((key=ftok(".",'q'))==-1){
perror("ftok");
exit(-1);
}
if((msgid=msgget(key,IPC_CREAT|0666))<0)
{
perror("msgget");
exit(-1);
}
while(1)
{
buf.mtype=TypeB;
printf("input >");
fgets(buf.mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);//发送消息
if(strcmp(buf.mtext,"quit\n")==0)
{
msgctl(msgid,IPC_RMID,0);
exit(0);
}
if(msgrcv(msgid,&buf,LEN,TypeA,0)<0)
{
perror("msgrcv");
}
printf("recv from clienB: %s",buf.mtext);
}
return 0;
}
clientB内容如下:
#include
#include
#include
#include
#include
#include
typedef struct
{
long mtype;
char mtext[64];
}MSG;
#define LEN (sizeof(MSG)-sizeof(long))
#define TypeA 100
#define TypeB 200
int main()
{
key_t key;
int msgid;
MSG buf;
if((key=ftok(".",'q'))==-1){
perror("ftok");
exit(-1);
}
if((msgid=msgget(key,IPC_CREAT|0666))<0)
{
perror("msgget");
exit(-1);
}
while(1)
{
if(msgrcv(msgid,&buf,LEN,TypeB,0)<0)
{
perror("msgrcv");
}
if(strcmp(buf.mtext,"quit\n")==0)//判断输入是不是quit
{
msgctl(msgid,IPC_RMID,0);
exit(0);
}
printf("recv from clienA: %s",buf.mtext);
buf.mtype=TypeA;
printf("input >");
fgets(buf.mtext,64,stdin);
msgsnd(msgid,&buf,LEN,0);
}
return 0;
}
在System V中进程间通信除了消息队列和共享内存的方式外还有信号灯机制。信号灯也叫信号量,用于进程/线程同步或者互斥机制。信号灯有三种类型,包括Posix无名信号灯(线程间同步或互斥)、Posix有名信号灯(进程间同步和互斥)、System V信号灯。Posix中的信号灯属于计数信号灯,而System V中的信号灯是一个或者多个计数信号灯的集合,由于是一个集合,因此可以对集合中的多个信号灯进行操作,还能避免申请多个资源时产生死锁的情况。
#include
#include
int semget(key_t key,int nsems,int semflg);
#include
#include
int semctl(int semid,int semnum,int cmd,...);
示例:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0
union semun myun;
myun.val=2;
if(semctl(semid,0,SETVAL,myun)<0)
{
perror("semctl");
exit(-1);
}
#include
#include
int semop(int semid,struct sembuf *sops,unsigned nsops)
sembuf
是系统定义好的结构体,内容如下:
struct sembuf
{
short semnum;//指定信号灯编号
short sem_op;//指定操作 -1:P操作 1:V操作
short sem_flg;//操作方式 0/IPC_NOWAIT
}
父进程通过System V信号灯同步对共享内存的读写,父进程从键盘输入字符串到共享内存,子进程删除字符串中的空格并打印,父进程输入quit后删除共享内存和信号灯集程序结束。
#include
#include
#include
#include
#include
#include
#include
#include
#define N 64
#define READ 0
#define WRITE 1
union semun
{
int val;
struct semd_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void init_sem(int semid,int s[],int n)
{
int i;
union semun myun;
for(i=0;i<n;i++)
{
myun.val=s[i];
semctl(semid,i,SETVAL,myun);
}
}
void pv(int semid,int num,int op)
{
struct sembuf buf;
buf.sem_num=num;
buf.sem_op=op;
buf.sem_flg=0;
semop(semid,&buf,1);
}
int main()
{
int shmid,semid,s[]={0,1};
pid_t pid;
key_t key;
char *shmaddr;
if((key=ftok(".",'s'))==-1)
{
perror("ftok");
exit(-1);
}
if((shmid=shmget(key,N,IPC_CREAT|0666))<0)
{
exit(-1);
}
if((semid=semget(key,2,IPC_CREAT|0666))<0)
{
perror("semget");
goto _error1;
}
init_sem(semid,s,2);
if((shmaddr=(char *)shmat(shmid,NULL,0))==(char*)-1)
{
perror("shmat");
goto _error2;
}
if((pid=fork())<0)
{
perror("fork");
exit(-1);
}
else if(pid == 0)
{
char *p,*q;
while(1)
{
pv(semid,READ,-1);//可读的缓冲区进行P操作
p=q=shmaddr;
while(*q)
{
if(*q!=' ')
{
*p++ = *q;
}
q++;
}
*p='\0';
printf("%s",shmaddr);
pv(semid,WRITE,1);//可写的进行V操作
}
}
else
{
while(1)
{
pv(semid,WRITE,-1);//可写执行P操作
printf("input >");
fgets(shmaddr,N,stdin);
if(strcmp(shmaddr,"quit\n")==0) break;
pv(semid,READ,1);//如果不是quit,唤醒子进程可读
}
kill(pid,SIGUSR1);//结束子进程
}
_error2:
shmctl(semid,0,IPC_RMID);
_error1:
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。