Linux进程通信(信号量、共享内存)
文章目录
一、信号量(Semaphore)
1.1 简介
几个概念:
原文链接:https://blog.csdn.net/dove1202ly/article/details/79999540
进程互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程竞争使用这些资源,进程的这种关 系为进程的互斥。也就是说一个资源每次只能被一个进程访问。
进程同步:在访问资源的时候,以某种特定顺序的方式区访问资源,也就是多个进程需要相互配合共同完成一项资 源。
临界资源(互斥资源):系统中某些资源一次只允许一个进程使用。
临界区:在进程中涉及到互斥资源的程序段。
-
Linux下的信号量是什么?
信号量是一种特殊的变量,用来保护共享资源(临界资源)。当一个进程访问这个共享资源时,不能给其他进程使用这个资源,否则会出现奇怪的问题。
-
如何使用信号量保护共享资源?
进程在使用共享资源之前先请求信号量,请求得到信号量就可以使用共享资源,否则不能使用,在使用完之后释放掉信号量。例如,对于二值信号量,不可能有两个进程同时访问某个共享资源,因为二值信号量只有0和1两个值,一个进程申请了信号量,另一个进程只能等待前一个进程使用完毕再去申请。
1.2 相关函数
semget创建
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数说明:
key与之前的消息队列中的key类似,同样可以使用ftok
函数获取。
nsems:即为num of sems,信号量的数量,如果是二值信号量设置为1
semflg:标志,如果semflg为key指定了IPC_CREAT和IPC_EXCL,并且已经存在一个信号量集,则semget()失败, errno设置为EEXIST。(这类似于O_CREAT | O_EXCL for open的效果(2))。
返回值:如果成功,返回值将是信号量集标识符(非负整数),否则返回-1,errno表示错误。
semop操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
semid:信号量的标识id。
sops:这个参数是一个sembuf结构体,包含以下成员:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
nsops:由sops指向的数组中的每个nsops元素都是一个结构,该结构指定要在单个信号量上执行的操作。
返回值:成功返回0,失败返回-1,置相应的errno
semctl控制
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
参数:
semnum:信号量编号,说明是对哪个信号量进行的操作
cmd:操作命令,选择SETVAL设置初始化信号量,IPC_RMID为删除信号量。
第四个参数一般是一个共用体:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
一般只使用val,传递信号量的初始值。
返回值:失败返回-1,当cmd设置不同的值时会返回不同的内容,但都是非负值。
1.3 同步实验
示例代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>
//semctl函数需要使用的共用体类型
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int sem_id;
static int set_semvalue()
{
union semun sem_union;
sem_union.val = 1;//使用成员val给信号量传值
if(semctl(sem_id,0,SETVAL,sem_union)==-1)//设置失败
return 0;
return 1;
}
static int semaphore_p()
{
struct sembuf sem_b;
sem_b.sem_num = 0;//除非使用一组信号量,否则它为0
sem_b.sem_op = -1;//-1为减,即请求信号
sem_b.sem_flg = SEM_UNDO;//使操作系统跟踪信号
if(semop(sem_id,&sem_b,1)==-1)
{
fprintf(stderr,"semaphore_p failed\n");
return 0;
}
return 1;
}
static int semaphore_v()
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;//+1发送信号
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id,&sem_b,1)==-1)
{
fprintf(stderr,"semaphore_v failed\n");
return 0;
}
return 1;
}
static void del_semvalue()
{
//删除信号量
union semun sem_union;
if(semctl(sem_id,0,IPC_RMID,sem_union)==-1)//删除失败
fprintf(stderr,"Failed to delete semaphore\n");
}
/*主函数*/
int main(int argc,char *argv[])
{
char message = 'x';
//创建信号量
sem_id = semget((key_t)1234,1,0666|IPC_CREAT);
if(argc>1)
{//如果参数大于1
//初始化信号量
if(!set_semvalue())
{
fprintf(stderr,"init failed\n");
exit(EXIT_FAILURE);
}
//参数1的第一个字符赋给message
message = argv[1][0];
}
int i=0;
for(i=0;i<5;i++)
{
//等待信号量
if(!semaphore_p())
exit(EXIT_FAILURE);
printf("%c",message);
fflush(stdout);
sleep(1);
//发送信号量
if(!semaphore_v())
exit(EXIT_FAILURE);
sleep(1);
}
printf("\n%d-finished\n",getpid());
if(argc>1)
{
//退出前删除信号量
del_semvalue();
}
exit(EXIT_SUCCESS);
}
执行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200314200802279.png)
分析
使用./sem2 0 & ./sem2
命令运行了两个程序,第一个程序因为传入了两个参数(argc>2)会进行信号量的初始化和删除,message字符也被设置为传入的字符0
,第二个程序只有一个参数,因此message值为x
。可以利用信号量来进行进程同步,使进程按照一定的先后顺序去执行。
二、共享内存
2.1 简介
共享内存进行进程通信的思想非常简单。多个进程共同享有一段内存,某个进程想内存中写数据,其他共享这段内存的进程就可以通过读的方式去获取数据,从而实现通信。
共享内存基本操作步骤:创建、映射、脱离。
2.2 步骤
下面介绍一下这三个方法使用的API函数。
shmget创建
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数说明:
key:与之前的很多API使用方式相同,可以用ftok获取。
size:顾名思义就是创建的内存的大小
shmflg:标志,仍然与前面的方法类似,可以使用IPC_CREAT创建,加上IPC_EXCL创建的话如果存在就会出错返 回,详见man手册。
返回值:成功返回共享内存的标识shmid,失败返回-1并置相应errno。
shmat附加
at即为attch附加的含义,shmat()将shmid标识的System V共享内存段连接到调用进程的地址空间。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数说明:
shmid:共享内存标识id。
shmaddr:看名字就知道是地址,是附加到进程地址空间的共享内存地址,满足以下情况:
如果shmaddr为空(NULL),系统将选择一个合适的(未使用的)地址来附加这个段。
如果shmaddr不为空,SHM_RND在shmflg中指定,则附加发生在等于shmaddr的地址处,该地址四 舍五入为SHMLBA的最近倍数。
其他情况,shmaddr必须是与页对齐的地址,在该地址处发生附加。
shmflg:参考上一个函数。
返回值:成功返回一个指针,指向附加的共享内存段的地址;错误返回-1,同样会置errno相应的错误。
shmdt脱离
dt意思是detach分离、脱离。shmdt()将位于shmaddr指定地址的共享内存段从调用进程的地址空间中分离出来。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数说明:
shmaddr:需要脱离的共享内存地址。
返回值:成功返回0,失败返回-1并置errno。
shmctl操作
这个函数根据传入的参数对共享内存进行操作,可以对共享内存进行初始化或者删除创建的共享内存,当我们不再使用共享内存时就可以对其进行删除操作。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数说明:
shmid:共享内存标识id。
cmd:操作命令。
buf:这是shmid_ds结构体指针,结构体类型为:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
返回值:失败返回-1并置相应的errno,成功根据传入的命令不同有不同的返回值。
2.3 共享内存读写实验
代码
shmWrite.c:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h"
int main(void)
{
int running = 1;
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];//用于保存输入的文本
int shmid;
//创建共享内存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//将共享内存连接到当前进程的地址空间
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %p\n", shm);
//设置共享内存
shared = (struct shared_use_st*)shm;
while(running)//向共享内存中写数据
{
//数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
while(shared->written == 1)
{
sleep(1);
printf("Waiting...\n");
}
//向共享内存中写入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
//写完数据,设置written使共享内存段可读
shared->written = 1;
//输入了end,退出循环(程序)
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享内存从当前进程中分离
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
shmRead.c:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include "shmdata.h"
int main(void)
{
int running = 1;//程序是否继续运行的标志
void *shm = NULL;//分配的共享内存的原始首地址
struct shared_use_st *shared;//指向shm
int shmid;//共享内存标识符
//创建共享内存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//将共享内存连接到当前进程的地址空间
shm = shmat(shmid, 0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %p\n", shm);
//设置共享内存
shared = (struct shared_use_st*)shm;//强制转换为shared_use_st指针
shared->written = 0;
while(running)//读取共享内存中的数据
{
//没有进程向共享内存定数据有数据可读取
if(shared->written != 0)
{
printf("You wrote: %s", shared->text);
sleep(rand() % 3);
//读取完数据,设置written使共享内存段可写
shared->written = 0;
//输入了end,退出循环(程序)
if(strncmp(shared->text, "end", 3) == 0)
running = 0;
}
else//有其他进程在写数据,不能读取数据
sleep(1);
}
//把共享内存从当前进程中分离
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
//删除共享内存
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shmdata.h:
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
//作为一个标志,非0:表示可读,0表示可写
int written;
//记录写入和读取的文本
char text[TEXT_SZ];
};
#endif
运行测试
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200315134230911.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNzkwMDc4,size_16,color_FFFFFF,t_70)
分析
首先在后台运行R读程序,然后在运行W写程序,在通过写端写入键盘输入的数据到共享内存中,然后读端读出内存中的text数据,输入end可以在读写两端跳出循环,执行脱离、删除操作,最后退出。
2.4 多进程操作共享内存
源码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
int main(int argc,char **argv)
{
int shmid;
char *p_addr,*c_addr;
if(argc!=2)
{
fprintf(stderr,"Usage:%s + str\n\a",argv[0]);
exit(1);
}
if((shmid=shmget((key_t)1234, 1024, 0666|IPC_CREAT))==-1)
{
fprintf(stderr,"Create Share Memory Error: %s\n\a",strerror(errno));
exit(1);
}
//创建子进程
if(fork())//父进程写
{
p_addr=shmat(shmid,0,0);
memset(p_addr,'\0',1024);
strncpy(p_addr,argv[1],1024);
printf("father process send: %s\n", p_addr);
wait(NULL); //释放资源,不关心终止情况
printf("wait sonProc finish!\n");
if(shmdt(p_addr) < 0)
{
perror("father proc shmdt");
exit(1);
}
if(shmctl(shmid, IPC_RMID, 0) < 0)//删除共享内存
perror("shmctl");
exit(0);
}
else //子进程读
{
sleep(1);
c_addr=shmat(shmid,0,0);
printf("son process get: %s\n",c_addr);
if(shmdt(c_addr) < 0)
{
perror("son proc shmdt");
exit(1);
}
exit(0);
}
}
运行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200315135706755.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNzkwMDc4,size_16,color_FFFFFF,t_70)
分析
使用fork创建一个子进程,在父进程中接入共享内存并写数据到共享内存,接着使用wait(NULL);等待子进程运行结束。运行结束后在父进程中删除共享内存。最后可以使用ipcs -m
命令查看内存是否删除。