文章目录
信号量
基本概念
- 临界资源
任意时刻只能被一个进程(线程)访问的资源称为临界资源 - 临界区
当多个进程同时访问系统上的某个资源时,比如同时修改某个文件,就要考虑进程的同步问题,以确保任一时刻只有一个进程拥有对此资源的操作权限,对临界资源进行访问的这段代码就称之为临界区。 - 原子操作
指的是该操作不会在执行完成之前被其它操作或者任务所打断,也就是说不会执行的最小单元 - 信号量
信号量是一种特殊的变量,访问具有原子性,信号量的操作有如下两种方式:
假设有信号量SV
①p操作:如果SV大于0,就将SV减1,如果SV的值为0,则挂起进程的执行;等待其他进程进行V操作后,唤醒此进程
②V操作:如果有其它进程因等待SV被挂起,则唤醒此进程,如果没有,将SV加1
例:生活中的停车场,停车位类似于临界资源,一个车位只能停一辆车,停车这个过程就类似于执行临界区代码,车停入后,车位就少了一个,类似于信号量的P操作,车位数-1,当车开走后,车位数+1类似于信号量的V操作。如果车位满了的话,有车来就需等待,以上例子类似于用信号量实现进程同步。
Linux信号量机制
Linux信号量API声明在<sys/sem.h>中
semget系统调用
semget 系统调用创建一个新的信号量集,或者获取一个已经存在的信号量集,函数定义如下:
int semget(key_t key, int num_sems, int sem_flags);
key:可以理解为信号量的唯一标识
num_sems: 信号量集信号量的个数
sem_flags:有两个值IPC_CREAT和IPC_EXCL
IPC_CREATE:创建一个新的信号量集(即使信号量已经存在)
IPC_EXCL:与IPC_CREATE按位或使用,创建唯一的信号量,若存在,返回错误
**返回值:创建成功返回信号量的标识符,创建失败,返回-1
semop系统调用
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops)
sem_id:信号量的标识符
sembuf结构:
struct sembuf
{
unsigned short int sem_num;//对于二进制信号量为0
short int sem_op;//对信号的操作,-1即为P操作,1即为V操作
short int sem_flag;//一般使用SEM_UNDO
}
num_sem_ops:操作信号量的个数
semctl系统调用
int semctl(int sem_id, int sem_num, int command,...)
sem_id :信号量的唯一标识
sem_num:信号量在信号量集中的编号,对于二进制信号量来说取1
command:指定要执行的命令(一般需要第四个参数),两个值IPC_RMID(删除信号量)SETVAL(设置信号量的值)
返回值:失败时返回-1
sem_union:可选参数,是一个union semun结构,它至少包含以下几个成员:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
semun联合体由程序员自己定义,一般用来初始化信号量的值
信号量的使用示例程序
通过信号量控制父子进程交错打印1-9
sem.c(将信号量的操作封装成函数)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include "sem.h"
#define MAX_SEM 2
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1296, MAX_SEM, IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1)
{
semid = semget((key_t)1296, MAX_SEM, 0600);
if(semid == -1)
{
perror("semget error");
return;
}
}
else
{
union semun a;
int arr[MAX_SEM] = {1, 0};
int i = 0;
for(; i < MAX_SEM; i++)
{
a.val = arr[i];
if(semctl(semid, i, SETVAL, a) == -1)
{
perror("semctl error");
}
}
}
}
void sem_p(int index)
{
if(index < 0 || index >= MAX_SEM)
{
return;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop error");
return;
}
}
void sem_v(int index)
{
if(index < 0 || index >= MAX_SEM)
{
return;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop error");
return;
}
}
void sem_destroy()
{
if(semctl(semid, 0, IPC_RMID) == -1)
{
perror("semctl erorr");
}
}
fork.c
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include "sem.h"
int main()
{
sem_init();
pid_t n = fork();
if(n > 0)//父进程
{
int i = 0;
for(i = 0;i < 10;i++)
{
sem_p(0);
if(i % 2 == 0)
{
printf("%d ",i);
}
fflush(stdout);
sem_v(1);
}
int a;
wait(&a);
sem_destroy();
}
else//子进程
{
int i = 0;
for(i = 0;i < 10;i++)
{
sem_p(1);
if(i % 2 != 0)
{
printf("%d ",i);
}
fflush(stdout);
//sleep(1);
sem_v(0);
}
}
}
运行结果
共享内存
基本概念
- 所谓共享内存就是多个进程可以访问同一块内存,是最快的IPC机制,因为不涉及进程之间的任何数据传输,所以我们必须使用其他辅助手段来同步进程对共享内存的访问,否则会产生竟态条件。
Linux共享内存机制
shmget系统调用
int shmget(key_t key, size_t size, int shmflg)
key:标识一段全局唯一的共享内存
size:创建的共享内存的大小
shmflg:与上述信号量的flg相同
返回值:创建成功返回一个正整数
shmat系统调用
void *shmat(int shm_id, const void* shm_addr, int shmflg)
shm_id:共享内存的标识
shm_addr:关联到那块儿进程地址空间,一般都传NULL,由操作系统选择
shmflg:一般取0
返回值:返回共享内存被关联到的地址
shmdt系统调用
int shmdt(void *shm_addr)
将关联到shm_addr处的共享内存从进程中分离
shmctl系统调用
int shmctl(int shm_id, int command, struct shmid_ds * buf)
shm_id:标识
command:要执行的命令(IPC_RMID,销毁共享内存)
返回值:失败时返回-1,成功时取决于command
共享内存程序实例
sem.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include "sem.h"
#define MAX_SEM 2
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1296, MAX_SEM, IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1)
{
semid = semget((key_t)1296, MAX_SEM, 0600);
if(semid == -1)
{
perror("semget error");
return;
}
}
else
{
union semun a;
int arr[MAX_SEM] = {1, 0};
int i = 0;
for(; i < MAX_SEM; i++)
{
a.val = arr[i];
if(semctl(semid, i, SETVAL, a) == -1)
{
perror("semctl error");
}
}
}
}
void sem_p(int index)
{
if(index < 0 || index >= MAX_SEM)
{
return;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop error");
return;
}
}
void sem_v(int index)
{
if(index < 0 || index >= MAX_SEM)
{
return;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1)
{
perror("semop error");
return;
}
}
void sem_destroy()
{
if(semctl(semid, 0, IPC_RMID) == -1)
{
perror("semctl erorr");
}
}
shma.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600);
assert(-1 != shmid);
char *s = (char *)shmat(shmid, NULL, 0);
sem_init();
while(1)
{
char buff[128];
printf("please input:");
fgets(buff, 128, stdin);
sem_p(0);
strcpy(s, buff);
sem_v(1);
if(strncmp(buff, "end", 3) == 0)
{
break;
}
}
shmdt(s);
exit(0);
}
shmb.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600);
assert(-1 != shmid);
char *s = (char *)shmat(shmid, NULL, 0);
sem_init();
while(1)
{
char buff[128];
printf("please input:");
fgets(buff, 128, stdin);
sem_p(0);
strcpy(s, buff);
sem_v(1);
if(strncmp(buff, "end", 3) == 0)
{
break;
}
}
shmdt(s);
exit(0);
}
运行结果
![从共享内存中读取数据](https://img-blog.csdnimg.cn/20191126145144325.png
消息队列
基本概念
消息队列是在两个进程之间传递二进制块儿数据的一种简单而有效的方式。每个数据块都有特定的类型,接收方可以根据数据的类型来选择性的接收数据。Linux消息队列的API都定义在sys/msg.h头文件中,包含了四个系统调用:msgget、msgsnd、msgrcv、msgctl
Linux消息队列机制
msgget系统调用
int msgget(key_t key, int msgflg)
key:用来标识全局唯一的消息队列
msgflg:与semflg参数相同
返回值:成功返回一个正整数,失败返回-1,并设置errno
msgsnd系统调用
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg)
msgid:是msgget返回的消息队列的标识符
msg_ptr:指向要发送的消息,消息得定义如下类型
struct msgbuf
{
long mtype;//消息类型
char mtext[512];//消息数据
}
其中,mytype定义消息类型,必须是一个正整数,mtext储存消息数据
msg_sz:是msgbuf中mtext的长度
msgflg:通常支持IPC_NOWAIT,以非阻塞状态发送消息
msgrcv系统调用
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int mytype, int msgflg):
msqid:msgget返回的消息队列的标识符
msg_ptr:用于储存接受的消息
msq_sz:消息队列数据部分的长度
msgtype:接收消息的类型
msgflg:通常设置为IPC_NOWAIT:如果消息对列中没有消息,则立即返回,并设置err弄
msgctl系统调用
int msgctl(int msqid, int command,struct msqid_ds* buf)
msqid:msgget返回的消息队列的标识符
command:指定要执行的命令(IPC_RMID),移除消息队列
返回值:失败时返回-1,成功时取决于command参数
消息队列应用实例
msgsnd.c(发送消息)
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <assert.h>
typedef struct MsgData
{
long type;
char mText[128];
}MsgData;
int main()
{
int msgid = msgget((key_t)1234, 0664 | IPC_CREAT);
assert(-1 != msgid);
MsgData data;
data.type = 100;
strcpy(data.mText, "hello1");
msgsnd(msgid, &data, strlen(data.mText), 0);
data.type = 200;
strcpy(data.mText, "hello2");
msgsnd(msgid, &data, strlen(data.mText), 0);
data.type = 300;
strcpy(data.mText, "hello3");
msgsnd(msgid, &data, strlen(data.mText), 0);
exit(0);
}
msgrcv.c(接受消息)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/msg.h>
#include <assert.h>
typedef struct MsgData
{
long type;
char mText[128];
}MsgData;
int main()
{
int msgid = msgget((key_t)1234, 0664 | IPC_CREAT);
assert(-1 != msgid);
MsgData data;
memset(&data, 0 ,sizeof(data));
msgrcv(msgid, &data, 127, 200, 0);
printf("%d\n",data.type);
printf("%s\n",data.mText);
memset(&data, 0 ,sizeof(data));
msgrcv(msgid, &data, 127, 100, 0);
printf("%d\n",data.type);
printf("%s\n",data.mText);
memset(&data, 0 ,sizeof(data));
msgrcv(msgid, &data, 127, 300, 0);
printf("%d\n",data.type);
printf("%s\n",data.mText);
}
运行结果
以上就是进程间通讯的几种方式
IPC命令
上述三种IPC进程间通讯方式都采用了唯一键值来描述一个共享资源,当程序调用semget,shmget,msgget时就创建了这些共享资源的实例,在Linux平台,可以使用ipcs来查看创建了哪些共享资源,例如:
输出结果对应的是系统拥有的三种资源,可见该系统未使用消息队列和共享内存,分配了一组键值为0的信号量。
此外,我们还可以使用ipcrm来删除遗留在系统中的共享资源。