目录
30System V信号量(一)
信号量
●二值信号量:其值或为0或为1的信号量。这与互斥锁类似。若资源被锁住则信号量为0,若资源可用则信号量值为1。
●计数信号量:其值在0和某个限制值之间的信号量。
// 这两种类型的信号量中,等待操作都等待信号量的值变为大于0,然后将它减1。 //挂出(post)操作则只是将信号量的值加1,从唤醒在等待该信号量值变为大于0的任何线程。
●计数信号量集:一个或者多个信号量(构成一个集合),其中每个都是计数信号量。
信号量集结构
struct semid_ds { struct ipc_perm sem_perm; //例如0644,0600,也有一些宏特别指定,不过还是数字好记 time_t sem_otime; time_t sem_ctime; unsigned long sem_nsems; struct sem * sem_base;//这一项在man page中没有明确表示 //但man page提到信号量集中有这样的结构 };
struct sem
表示封装的信号量结构struct sem { unsigned short semval; /* semaphore value */ unsigned short semzcnt; /* # waiting for zero */ unsigned short semncnt; /* # waiting for increase */ pid_t sempid; /* ID of process that did last op */ }; /* 1、semval :当前某信号量的资源数目 2、semzcnt:当sem_op(见 struct sembuf)为0,且semop 函数没有设置IPC_NOWAIT 标志,且当前semval 不为0,此时semzcnt 会加1,表示等待这个信号量的资源变为0的进程数加1,且进程会阻塞等待直到4个事件其中一个发生,具体可man 2 semop 一下。 3、semncnt:当sem_op(见 struct sembuf)< 0,且semop 函数没有设置IPC_NOWAIT 标志,且当前semval < |sem_op| ,此时semncnt 会加1,表示等待这个信号量的资源增加的进程数加1,且进程会阻塞等待直到4个事件其中一个发生,具体可man 2 semop 一下。 4、当正确执行了semop 函数,则信号量集中的每个信号量的sempid 参数都被设置为改变此信号量的进程pid。 */
根据这两个结构体,在内核中某个特定信号量集可以图解为:
信号量集函数
函数原型
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
函数功能
//用来创建和访问一个信号量集
返回值
成功返回一个非负整数,即该信号量集的标识码;失败返回-1
参数说明
key: 信号量集的名字 nsems:信号量集中信号量的个数 semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
函数原型
int semctl(int semid, int semnum, int cmd, ...);
函数功能
用于控制信号量集
返回值
成功返回0;失败返回-1
参数说明
semid:由semget返回的信号量集标识码 semnum:信号量集中信号量的序号,从0开始编号 cmd:将要采取的动作(有三个可取值) 最后一个参数是 union semun,具体成员根据cmd 的不同而不同 union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; cmd 取值如下: SETVAL 设置信号量集中的信号量的计数值 GETVAL 获取信号量集中的信号量的计数值 IPC_STAT 把semid_ds结构中的数据设置为信号量集的当前关联值 IPC_SET 在进程有足够权限的前提下,把信号量集的当前关联值设置为semid_ds数据结构中给出的值 IPC_RMID 删除信号量集
函数原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
函数功能
用来创建和访问一个信号量集
返回值
成功返回0;失败返回-1
参数说明
semid:是该信号量集的标识码,也就是semget函数的返回值 sops:是个指向一个结构体的指针 nsops:信号量的个数 struct sembuf { unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ }; sem_num:是信号量的编号。 sem_op:是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。 当然+-n 和0 都是允许的。需要注意的是只有+n 才确保将semval +n 后马上返回,而-n 和 0 很可能是会阻塞的,见文章上面的分析,+-n 需要进程对信号量集有写的权限,而0 只需要读的权限。 sem_flag:的两个取值是IPC_NOWAIT或SEM_UNDO,设为前者如果当某个信号量的资源为0时进行P操作,此时不会阻塞等待,而是直接返回资源不可用的错误; 设为后者,当退出进程时对信号量资源的操作撤销;不关心时设置为0即可。 当要对一个信号量集中的多个信号量进行操作时,sops 是结构体数组的指针,此时nsops 不为1。 此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。
信号量示例
封装一个信号量集操作函数的工具
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* array for GETALL, SETALL */
/* Linux specific part: */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
int sem_create(key_t key)
{
int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1)
ERR_EXIT("semget");
return semid;
}
int sem_open(key_t key)
{
int semid = semget(key, 0, 0);
if (semid == -1)
ERR_EXIT("semget");
return semid;
}
int sem_p(int semid)
{
struct sembuf sb = {0, -1, /*IPC_NOWAIT*/SEM_UNDO};
int ret = semop(semid, &sb, 1);
if (ret == -1)
ERR_EXIT("semop");
return ret;
}
int sem_v(int semid)
{
struct sembuf sb = {0, 1, /*0*/SEM_UNDO};
int ret = semop(semid, &sb, 1);
if (ret == -1)
ERR_EXIT("semop");
return ret;
}
int sem_d(int semid)
{
int ret = semctl(semid, 0, IPC_RMID, 0);
if (ret == -1)
ERR_EXIT("semctl");
return ret;
}
int sem_setval(int semid, int val)
{
union semun su;
su.val = val;
int ret = semctl(semid, 0, SETVAL, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("value updated...\n");
return ret;
}
int sem_getval(int semid)
{
int ret = semctl(semid, 0, GETVAL, 0);
if (ret == -1)
ERR_EXIT("semctl");
printf("current val is %d\n", ret);
return ret;
}
int sem_getmode(int semid)
{
union semun su;
struct semid_ds sem;
su.buf = &sem;
int ret = semctl(semid, 0, IPC_STAT, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("current permissions is %o\n", su.buf->sem_perm.mode);
return ret;
}
int sem_setmode(int semid, char *mode)
{
union semun su;
struct semid_ds sem;
su.buf = &sem;
int ret = semctl(semid, 0, IPC_STAT, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("current permissions is %o\n", su.buf->sem_perm.mode);
sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);
ret = semctl(semid, 0, IPC_SET, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("permissions updated...\n");
return ret;
}
void usage(void)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "semtool -c\n");
fprintf(stderr, "semtool -d\n");
fprintf(stderr, "semtool -p\n");
fprintf(stderr, "semtool -v\n");
fprintf(stderr, "semtool -s <val>\n");
fprintf(stderr, "semtool -g\n");
fprintf(stderr, "semtool -f\n");
fprintf(stderr, "semtool -m <mode>\n");
}
int main(int argc, char *argv[])
{
int opt;
opt = getopt(argc, argv, "cdpvs:gfm:");
if (opt == '?')
exit(EXIT_FAILURE);
if (opt == -1)
{
usage();
exit(EXIT_FAILURE);
}
key_t key = ftok(".", 's');
int semid;
switch (opt)
{
case 'c':
sem_create(key);
break;
case 'p':
semid = sem_open(key);
sem_p(semid);
sem_getval(semid);
break;
case 'v':
semid = sem_open(key);
sem_v(semid);
sem_getval(semid);
break;
case 'd':
semid = sem_open(key);
sem_d(semid);
break;
case 's':
semid = sem_open(key);
sem_setval(semid, atoi(optarg));
break;
case 'g':
semid = sem_open(key);
sem_getval(semid);
break;
case 'f':
semid = sem_open(key);
sem_getmode(semid);
break;
case 'm':
semid = sem_open(key);
sem_setmode(semid, argv[2]);
break;
}
return 0;
}
31System V信号量(二)
用信号集解决哲学家就餐问题
我们尝试解决这个问题的方法是:仅当一个哲学家两边筷子都可用时才允许他拿筷子。
#include<stdio.h> #include<stdlib.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> #include<unistd.h> #include<errno.h> #include<sys/ipc.h> #include<sys/sem.h> #include<sys/wait.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; int semid; #define DELAY (rand() % 5 + 1) void wait_for_2fork(int no) { int left = no; int right = (no + 1) % 5; struct sembuf buf[2] = { {left, -1, 0}, {right, -1, 0} }; semop(semid, buf, 2); } void free_2fork(int no) { int left = no; int right = (no + 1) % 5; struct sembuf buf[2] = { {left, 1, 0}, {right, 1, 0} }; semop(semid, buf, 2); } void philosopere(int no) { srand(getpid()); for (; ;) { printf("%d is thinking\n", no); sleep(DELAY); printf("%d is hungry\n", no); wait_for_2fork(no); printf("%d is eating\n", no); sleep(DELAY); free_2fork(no); } } int main(void) { semid = semget(IPC_PRIVATE, 5, IPC_CREAT | 0666); if (semid == -1) ERR_EXIT("semget"); union semun su; su.val = 1; int i; for (i = 0; i < 5; i++) { semctl(semid, i, SETVAL, su); } int no = 0; pid_t pid; for (i = 1; i < 5; i++) { pid = fork(); if (pid == -1) ERR_EXIT("fork"); if (pid == 0) { no = i; break; } } philosopere(no); return 0; }
33System V共享内存与信号量综合
基于生产者-消费者模型实现先进先出的共享内存段
可以用信号量解决生产者消费者问题,如下图:
定义3个信号量,sem_full 和 sem_empty 用于生产者进程和消费者进程之间同步,即缓冲区为空才能生产,缓冲区不为空才能消费。由于共享同一块缓冲区,在生产一个产品过程中不能生产/消费产品,在消费一个产品的过程中不能生产/消费产品,故再使用一个 sem_mutex 信号量来约束行为,即进程间互斥。
下面基于生产者消费者模型,来实现一个先进先出的共享内存段:
如上图所示,定义两个结构体,shmhead 是共享内存段的头部,保存了块大小,块数,读写索引。shmfifo 保存了共享内存头部的指针,有效负载的起始地址,创建的共享内存段的shmid,以及3个信号量集的semid。
封装几个函数:
#include "shmfifo.h"
#include <assert.h>
shmfifo_t *shmfifo_init(int key, int blksize, int blocks)
{
shmfifo_t *fifo = (shmfifo_t *)malloc(sizeof(shmfifo_t));
assert(fifo != NULL);
memset(fifo, 0, sizeof(shmfifo_t));
int shmid;
shmid = shmget(key, 0, 0);
int size = sizeof(shmhead_t) + blksize * blocks;
if (shmid == -1)
{
fifo->shmid = shmget(key, size, IPC_CREAT | 0666);
if (fifo->shmid == -1)
ERR_EXIT("shmget");
fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
if (fifo->p_shm == (shmhead_t *) - 1)
ERR_EXIT("shmat");
fifo->p_payload = (char *)(fifo->p_shm + 1);
fifo->p_shm->blksize = blksize;
fifo->p_shm->blocks = blocks;
fifo->p_shm->rd_index = 0;
fifo->p_shm->wr_index = 0;
fifo->sem_mutex = sem_create(key);
fifo->sem_full = sem_create(key + 1);
fifo->sem_empty = sem_create(key + 2);
sem_setval(fifo->sem_mutex, 1);
sem_setval(fifo->sem_full, blocks);
sem_setval(fifo->sem_empty, 0);
}
else
{
fifo->shmid = shmid;
fifo->p_shm = (shmhead_t *)shmat(fifo->shmid, NULL, 0);
if (fifo->p_shm == (shmhead_t *) - 1)
ERR_EXIT("shmat");
fifo->p_payload = (char *)(fifo->p_shm + 1);
fifo->sem_mutex = sem_open(key);
fifo->sem_full = sem_open(key + 1);
fifo->sem_empty = sem_open(key + 2);
}
return fifo;
}
void shmfifo_put(shmfifo_t *fifo, const void *buf)
{
sem_p(fifo->sem_full);
sem_p(fifo->sem_mutex);
memcpy(fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->wr_index,
buf, fifo->p_shm->blksize);
fifo->p_shm->wr_index = (fifo->p_shm->wr_index + 1) % fifo->p_shm->blocks;
sem_v(fifo->sem_mutex);
sem_v(fifo->sem_empty);
}
void shmfifo_get(shmfifo_t *fifo, void *buf)
{
sem_p(fifo->sem_empty);
sem_p(fifo->sem_mutex);
memcpy(buf, fifo->p_payload + fifo->p_shm->blksize * fifo->p_shm->rd_index,
fifo->p_shm->blksize);
fifo->p_shm->rd_index = (fifo->p_shm->rd_index + 1) % fifo->p_shm->blocks;
sem_v(fifo->sem_mutex);
sem_v(fifo->sem_full);
}
void shmfifo_destroy(shmfifo_t *fifo)
{
sem_d(fifo->sem_mutex);
sem_d(fifo->sem_full);
sem_d(fifo->sem_empty);
shmdt(fifo->p_shm);
shmctl(fifo->shmid, IPC_RMID, 0);
free(fifo);
}
/*
1、shmfifo_init:先分配shmfifo 结构体的内存,
如果尝试打开共享内存失败则创建,创建的共享内存段大小 = shmhead大小 + 块大小×块数目,
然后shmat将此共享内存段映射到进程地址空间,
然后使用sem_create 创建3个信号量集,每个信号集只有一个信号量,即上面提到的3个信号量,设置每个信号量的资源初始值。
如果共享内存已经存在,则直接shmat映射下即可,此时3个信号量集也已经存在,sem_open 打开即可。
2、shmfifo_put:参照第一个生产者消费者的图,除去sem_p,sem_v 操作之外,中间就将buf 的内容memcpy 到对应缓冲区块,然后移动wr_index。
3、shmfifo_get:与shmfifo_put 类似,执行的是相反的操作。
4、shmfifo_destroy:删除3个信号量集,将共享内存段从进程地址空间剥离,删除共享内存段,释放shmfifo 结构体的内存。
*/
下面是生产者程序和消费者程序:
shmfifo_send.c
#include "shmfifo.h"
typedef struct stu
{
char name[32];
int age;
} STU;
int main(void)
{
shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);
STU s;
memset(&s, 0, sizeof(STU));
s.name[0] = 'A';
int i;
for (i = 0; i < 5; i++)
{
s.age = 20 + i;
shmfifo_put(fifo, &s);
s.name[0] = s.name[0] + 1;
printf("send ok\n");
}
free(fifo);
return 0;
}
shmfifo_recv.c
#include "shmfifo.h"
typedef struct stu
{
char name[32];
int age;
} STU;
int main(void)
{
shmfifo_t *fifo = shmfifo_init(1234, sizeof(STU), 3);
STU s;
memset(&s, 0, sizeof(STU));
int i;
for (i = 0; i < 5; i++)
{
shmfifo_get(fifo, &s);
printf("name = %s age = %d\n", s.name, s.age);
}
shmfifo_destroy(fifo);
return 0;
}