信号量
信号量描述
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源 时,需要对信号量的值进行原子减一,该操作被称为 P 操作。
当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。
释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。
信号量主要用来同步进程。
信号量的值如果只取 0,1,将其称为二值信号量。
如果信 号量的值大于 1,则称之为计数信号量。
**临界资源:同一时刻,只允许被一个进程或线程访问的资源 **
**临界区:访问临界资源的代码段 **
信号量使用
semget(); 创建或者获取已存在的信号量
int semget(key_t key, int nsems, int semflg);
semget()成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号 量的个数
semflg 可选: IPC_CREAT IPC_EXCL
semop()对信号量进行改变,做 P 操作或者 V 操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
semop()成功返回 0,失败返回-1
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
semctl()控制信号量
int semctl( int semid, int semnum, int cmd, …);
semctl()成功返回 0,失败返回-1semid:信号量的ID
cmd 选项: SETVAL IPC_RMID
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
封装一个c文件实现创建一个信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sem.h>
union semun
{
int val;
};
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建信号量,如果存在就失败
if ( semid == -1 )//失败,表示该(key_t)1234)信号已存在
{
semid = semget((key_t)1234,1,0600);//获取已存在的信号量id
if ( semid == -1)
{
printf("semget err\n");
}
}
else//全新创建成功,那么要进行初始化
{
union semun a;
a.val = 1;//信号量的初始值
if ( semctl(semid,0,SETVAL,a) == -1)//设置初始值
{
printf("semctl err\n");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1; //p操作
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1)
{
printf("semop p err\n");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1; //v操作
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1)
{
printf("semop v err\n");
}
}
void sem_destroy()
{
if ( semctl(semid,0,IPC_RMID) == -1)
{
printf("semctl destroy err\n");
}
}
假设资源只有一份,每轮a进程使用2次 ,b进程使用3次,如何解决。
我们可以使用信号量解决临界资源问题
a进程代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "sem.c"
int main()
{
sem_init();//
for(int i = 0; i < 5; i++)
{
//p
sem_p();
printf("a");
fflush(stdout);
int n = rand() % 3;
sleep(n);
printf("a");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
sleep(10);
sem_destroy();
return 0;
}
b进程代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "sem.c"
int main()
{
sem_init();
for(int i = 0; i < 5; i++)
{
sem_p();
printf("b");
int n = rand() % 3;
sleep(n);
printf("bb");
fflush(stdout);
sem_v();
n = rand() % 3;
sleep(n);
}
return 0;
}
效果如下:a,b不会同时访问该资源
共享内存
共享内存原理
共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理 内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访 问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供 同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。
shemget()创建共享内存
int shmget(key_t key, size_t size, int shmflg);
shmget()用于创建或者获取共享内存
shmget()成功返回共享内存的 ID, 失败返回-1
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_EXCL
shmat() 用来创建映射
void * shmat( int shmid, const void *shmaddr, int shmflg);
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
shmdt()用来断开映射
int shmdt( const void *shmaddr);
shmdt()断开当前进程的 shmaddr 指向的共享内存映射
shmdt()成功返回 0, 失败返回-1
shmctl()用来控制共享内存
int shmctl( int shmid, int cmd, struct shmid_ds *buf);
shmctl()成功返回 0,失败返回-1
cmd: IPC_RMID 32. *
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int shmid = shmget((key_t)1234,sizeof(char)*128,IPC_CREAT|0600);
if(shmid == -1)
{
printf("shmget error\n");
exit(1);
}
char* p = shmat(shmid,NULL,SHM_W);
if(p == NULL)
{
printf("shmat error\n");
exit(2);
}
while(1)
{
char buff[128]={0};
printf("parent input: ");
fflush(stdout);
fgets(buff,127,stdin);
if(strncmp(buff,"end",3) == 0)
{
break;
}
int pid = fork();
if(pid == -1) break;
if(pid != 0){
strcpy(p,buff);
}
if(pid == 0){
char* ptr=shmat(shmid,NULL,0);
printf("child read: %s\n",ptr);
shmdt(ptr);
exit(0);
}
wait(NULL);
}
shmdt(p);
exit(0);
}
结果如图:
下面我们用信号量来实现对共享内存的访问。
代码如下:
sem.c
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/sem.h>
#define SEM1 0
#define SEM2 1
union semun
{
int val;
};
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234, 2, IPC_CREAT | IPC_EXCL | 0600); // 全新创建信号量,如果存在就失败
if (semid == -1) // 失败,表示已存在
{
semid = semget((key_t)1234, 2, 0600); // 获取已存在的信号量id
if (semid == -1)
{
printf("semget err\n");
}
}
else // 全新创建成功,那么要进行初始化
{
union semun a;
const int ar[2] = {1, 0};
for (int i = 0; i < 2; ++i)
{
a.val = i; // 信号量的初始值
if (semctl(semid, i, SETVAL, a) == -1) // 设置初始值
{
printf("semctl err\n");
}
}
}
}
void sem_p(int sem)
{
struct sembuf buf;
buf.sem_num = sem;
buf.sem_op = -1; // p
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1)
{
printf("semop p err\n");
}
}
void sem_v(int sem)
{
struct sembuf buf;
buf.sem_num = sem;
buf.sem_op = 1; // v
buf.sem_flg = SEM_UNDO;
if (semop(semid, &buf, 1) == -1)
{
printf("semop v err\n");
}
}
void sem_destroy()
{
if (semctl(semid, 0, IPC_RMID) == -1)
{
printf("semctl destroy err\n");
}
}
read.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.c"
int main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if ( shmid == -1 )
{
printf("shmget err\n");
exit(1);
}
char * s = (char*)shmat(shmid,NULL,0);
if ( s == (char*)-1)
{
printf("shmat err\n");
exit(1);
}
sem_init();
while( 1 )
{
sem_p(SEM2);
if ( strncmp(s,"end",3) == 0 )
{
break;
}
printf("read:%s\n",s);
sem_v(SEM1);
}
shmdt(s);
shmctl(shmid,IPC_RMID,NULL);
sem_destroy();
}
write.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.c"
int main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if ( shmid == -1 )
{
printf("shmget err\n");
exit(1);
}
char* s = (char*)shmat(shmid,NULL,0);
if ( s == (char*)-1)
{
printf("shmat err\n");
exit(1);
}
sem_init();
while( 1 )
{
printf("input: ");
char buff[128] = {0};
fflush(stdout);
fgets(buff,128,stdin);
sem_p(SEM1);
strcpy(s,buff);
sem_v(SEM2);
if ( strncmp(buff,"end",3) == 0)
{
break;
}
}
shmdt(s);
}
消息队列
接口介绍
1.msgget() 获取消息队列
int msgget(key_t key, int msqflg);
msgget()创建或者获取一个消息队列
msgget()成功返回消息队列 ID,失败返回-1
msqflg: IPC_CREAT
2.msgsnd()发送信息
int msgsnd( int msqid, const void *msqp, size_t msqsz, int msqflg);
msgsnd()发送一条消息,消息结构为:
struct msgbuf
{
long mtype; // 消息类型, 必须大于 0 必须有
char mtext[1]; // 消息数据
};
msgsnd()成功返回 0, 失败返回-1
msqsz: 指定 mtext 中有效数据的长度
msqflg:一般设置为 0 可以设置 IPC_NOWAIT
3.msgrcv()接收消息
ssize_t msgrcv( int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
msgrcv()接收一条消息
msgrcv()成功返回 mtext 中接收到的数据长度, 失败返回-1
msqtyp: 指定接收的消息类型,类型可以为 0(忽略类型)
msqflg: 一般设置为 0 可以设置 IPC_NOWAIT
4.msgctl()控制消息队列
int msgctl( int msqid, int cmd, struct msqid_ds *buf);
msgctl()控制消息队列
msgctl()成功返回 0,失败返回-1
cmd: IPC_RMID
测试代码:
msgread.c //从消息队列中读取
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
struct message
{
long type;//固定
char msg[16];
};
int main()
{
int msgid=msgget((key_t)1234,IPC_CREAT|0600);
if(msgid==-1)
{
printf("msgget err\n");
exit(1);
}
struct message dt;
msgrcv(msgid,&dt,16,1,0);//0代表不区分类型
printf("read message:%s\n",dt.msg);
exit(0);
}
msgcreat.c //写入数据
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
struct message //自定义结构体
{
long type;//固定的
char msg[16];
};
int main()
{
int msgid=msgget((key_t)1234,IPC_CREAT|0600);
if(msgid==-1)
{
printf("msgget err\n");
exit(1);
}
struct message dt;
dt.type=1;
strcpy(dt.msg,"China");
msgsnd(msgid,&dt,16,0);
exit(0);
}