linux信号量简介(用户态)
一、说明 用户态进程使用的信号量又分为POSIX信号量和SYSTEM V信号量。POSIX信号量又分为有名信号量和无名信号量。有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中,只能用于线程或者有亲缘关系的进程间同步。 二、POSIX 信号量 对POSIX来说,信号量是个非负整数,常用于线程间同步。 1
·
一、说明
用户态进程使用的信号量分为POSIX信号量和SYSTEM V信号量。POSIX信号量又分为有名信号量和无名信号量。有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中,只能用于线程或者有亲缘关系的进程间同步。
二、POSIX 信号量
对POSIX来说,信号量是个非负整数,常用于线程间同步。
1、无名信号量
A、介绍
无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程线程)的共享变量,这两个条件是缺一不可的。
B、函数说明
int sem_init(sem_t *sem, int pshared, unsigned int value);
1) 若pshared==0 用于多线程的同步
2) 若pshared>0 用于多个相关进程间的同步(即由fork产生的)
int sem_getvalue(sem_t *sem, int *sval);
取回信号量sem的当前值,把该值保存到sval中。若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
1) 返回0
2) 返回阻塞在该信号量上的进程或线程数目
int sem_wait(sem_t *sem);
这是一个阻塞的函数,测试所指定信号量的值,它的操作是原子的。若sem>0,那么它减1并立即返回。若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。
int sem_trywait(sem_t *sem);
非阻塞的函数,其他的行为和sem_wait一样,除了:若sem==0,不是睡眠,而是返回一个错误EAGAIN。
int sem_post(sem_t *sem);
把指定的信号量sem的值加1,呼醒正在等待该信号量的任意线程。
C、例子(无名信号量在相关进程间的同步)
本来对于fork来说,子进程只继承了父进程的代码副本,mutex理应在父子进程中是相互独立的两个变量,但由于在初始化mutex的时候,由pshared = 1指定了mutex处于共享内存区域,所以此时mutex变成了父子进程共享的一个变量。此时,mutex就可以用来同步相关进程了。
2、有名信号量
A、介绍
有名信号量的特点是把信号量的值保存在文件中。这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。
B、函数说明
有名信号量在使用的时候,和无名信号量共享sem_wait和sem_post函数。区别是有名信号量使用sem_open代替sem_init,另外在结束的时候要像关闭文件一样去关闭这个有名信号量。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name是文件的路径名;
oflag 有O_CREAT或O_CREAT|EXCL两个取值;
mode控制新的信号量的访问权限;
value指定信号量的初始化值。
注意:这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。当oflag = O_CREAT时,若name指定的信号量不存在时,则会创建一个,而且后面的mode和value参数必须有效。若name指定的信号量已存在,则直接打开该信号量,同时忽略mode和value参数。当oflag = O_CREAT|O_EXCL时,若name指定的信号量已存在,该函数会直接返回error。
sem_unlink(const char *name);
从系统中删除信号灯。
对于SYSTEM V来说,信号量是信号量值的集合,而不是单个信号量。内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:
A、介绍
SYSTEM V信号量是SYSTEM V IPC(即SYSTEM V进程间通信)的组成部分,其他的有SYSTEM V消息队列,SYSTEM V共享内存。而关键字和IPC描述符无疑是它们的共同点,使用它们,就不得不先对它们进行熟悉。这里只对SYSTEM V信号量进行讨论。IPC描述符相当于引用ID号,要想使用SYSTEM V信号量(或MSG、SHM),就必须用IPC描述符来调用信号量。而IPC描述符是内核动态提供的(通过semget来获取),用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字KEY来定位描述符。某个KEY只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和客户事先认可共同使用某个KEY,那么大家就都能定位到同一个描述符,也就能定位到同一个信号量,这样就达到了SYSTEM V信号量在进程间共享的目的。
关键字的获取有多种方法:(1) 服务器可以指定关键字IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户机取用。关键字 IPC_PRIVATE保证服务器创建一个新IPC结构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件取得此标识符。IPC_PRIVATE关键字也可用于父、子关系进程。父进程指定 IPC_PRIVATE创建一个新IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为exec函数的一个参数传给一个新程序。(2) 在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个 IPC结构相结合,在此情况下,get函数(msgget、semget或shmget)出错返回。服务器必须处理这一错误,删除已存在的IPC结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。(3) 客户机和服务器认同一个路径名和课题I D(课题I D是0 ~ 2 5 5之间的字符值),然后调用函数ftok将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键字的问题。使用ftok并非高枕无忧。有这样一种例外:服务器使用ftok获取得一个关键字后,该文件就被删除了,然后重建。此时客户端以此重建后的文件来ftok所获取的关键字就和服务器的关键字不一样了。所以一般商用的软件都不怎么用ftok。一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使用ftok,而只是在该头文件中存放一个大家都知道的关键字。
B、函数说明
int semget(key_t key, int nsems, int oflag); //创建和打开信号量
(1) nsems>0 : 创建一个新的信号量集,指定集合中信号量的数量,一旦创建就不能更改。
(2) nsems==0 : 访问一个已存在的集合
(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。
(4) 创建成功后信号量结构被设置:
.sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
.oflag 参数中的读写权限位存入sem_perm.mode
.sem_otime 被置为0,sem_ctime被设置为当前时间
.sem_nsems 被置为nsems参数的值
该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。
semget函数执行成功后,就产生了一个由内核维持的类型为semid_ds结构体的信号量集,返回semid就是指向该信号量集的引索。
int semop(int semid, struct sembuf *opsptr, size_t nops); //设置信号量的值(PV操作)
(1) semid: 是semget返回的semid
(2)opsptr: 指向信号量操作结构数组
(3) nops : opsptr所指向的数组中的sembuf结构体的个数
struct sembuf {
short sem_num; // 要操作的信号量在信号量集里的编号,
short sem_op; // 信号量操作
short sem_flg; // 操作表示符
};
(4) 若sem_op 是正数,其值就加到semval上,即释放信号量控制的资源;若sem_op 是0,那么调用者希望等到semval变为0,如果semval等于0就返回;若sem_op 是负数,其值加到semval上,若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用(就是信号量的值大于0),若没有设置 IPC_NOWAIT,则进程直接返回EAGAIN。注意:semval是指semid_ds中的信号量集中的某个信号量的值。
(5) sem_flg
SEM_UNDO 由进程自动释放信号量
IPC_NOWAIT 不阻塞
到这里,读者肯定有个疑惑:semop希望改变的semval到底在哪里?我们怎么没看到有它的痕迹?其实,前面已经说明了,当使用semget时,就产生了一个由内核维护的信号量集(当然每个信号量值即semval也是只由内核才能看得到了),用户能看到的就是返回的semid。内核通过semop 函数的参数,知道应该去改变semid 所指向的信号量的哪个semval。
int semctl(int semid, int semum, int cmd, ../* union semun arg */); //对信号集实行控制操作(semval的赋值等)
semid是信号量集合;
semnum是信号在集合中的序号;
semum是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成:
union semun
{
int val; // cmd == SETVAL
struct semid_ds *buf // cmd == IPC_SET或者 cmd == IPC_STAT
ushort *array; // cmd == SETALL,或 cmd = GETALL
};
val只有cmd ==SETVAL时才有用,此时指定的semval = arg.val。
注意:当cmd == GETVAL时,semctl函数返回的值就是我们想要的semval。千万不要以为指定的semval被返回到arg.val中。array指向一个数组,当cmd==SETALL时,就根据arg.array来将信号量集的所有值都赋值;当cmd ==GETALL时,就将信号量集的所有值返回到arg.array指定的数组中。buf 指针只在cmd==IPC_STAT 或IPC_SET 时有用,作用是semid 所指向的信号量集semid_ds机构体)。一般情况下不常用,这里不做谈论。另外,cmd == IPC_RMID还是比较有用的。
C、例子
用户态进程使用的信号量分为POSIX信号量和SYSTEM V信号量。POSIX信号量又分为有名信号量和无名信号量。有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中,只能用于线程或者有亲缘关系的进程间同步。
二、POSIX 信号量
对POSIX来说,信号量是个非负整数,常用于线程间同步。
1、无名信号量
A、介绍
无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程线程)的共享变量,这两个条件是缺一不可的。
B、函数说明
int sem_init(sem_t *sem, int pshared, unsigned int value);
1) 若pshared==0 用于多线程的同步
2) 若pshared>0 用于多个相关进程间的同步(即由fork产生的)
int sem_getvalue(sem_t *sem, int *sval);
取回信号量sem的当前值,把该值保存到sval中。若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
1) 返回0
2) 返回阻塞在该信号量上的进程或线程数目
int sem_wait(sem_t *sem);
这是一个阻塞的函数,测试所指定信号量的值,它的操作是原子的。若sem>0,那么它减1并立即返回。若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。
int sem_trywait(sem_t *sem);
非阻塞的函数,其他的行为和sem_wait一样,除了:若sem==0,不是睡眠,而是返回一个错误EAGAIN。
int sem_post(sem_t *sem);
把指定的信号量sem的值加1,呼醒正在等待该信号量的任意线程。
C、例子(无名信号量在相关进程间的同步)
本来对于fork来说,子进程只继承了父进程的代码副本,mutex理应在父子进程中是相互独立的两个变量,但由于在初始化mutex的时候,由pshared = 1指定了mutex处于共享内存区域,所以此时mutex变成了父子进程共享的一个变量。此时,mutex就可以用来同步相关进程了。
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc,char **argv)
{
int fd, i, count=0, nloop=10, zero=0, *ptr;
sem_t mutex;
//open a file and map it into memory
fd = open("log.txt", O_RDWR|O_CREAT, S_IRWXU);
write(fd, &zero, sizeof(int));
ptr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
/* create, initialize semaphore */
if(sem_init(&mutex, 1, 1) < 0) {
printf("semaphore initilization\n");
return -1;
}
if (fork() == 0) { /* child process*/
for (i = 0; i < nloop; i++) {
sem_wait(&mutex);
printf("child: %d\n", (*ptr)++);
sem_post(&mutex);
}
return -1;
}
/* back to parent process */
for (i = 0; i < nloop; i++) {
sem_wait(&mutex);
printf("parent: %d\n", (*ptr)++);
sem_post(&mutex);
}
return 0;
}
2、有名信号量
A、介绍
有名信号量的特点是把信号量的值保存在文件中。这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。
B、函数说明
有名信号量在使用的时候,和无名信号量共享sem_wait和sem_post函数。区别是有名信号量使用sem_open代替sem_init,另外在结束的时候要像关闭文件一样去关闭这个有名信号量。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name是文件的路径名;
oflag 有O_CREAT或O_CREAT|EXCL两个取值;
mode控制新的信号量的访问权限;
value指定信号量的初始化值。
注意:这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。当oflag = O_CREAT时,若name指定的信号量不存在时,则会创建一个,而且后面的mode和value参数必须有效。若name指定的信号量已存在,则直接打开该信号量,同时忽略mode和value参数。当oflag = O_CREAT|O_EXCL时,若name指定的信号量已存在,该函数会直接返回error。
sem_unlink(const char *name);
从系统中删除信号灯。
注意:在做这个之前,要确定所有对这个有名信号量的引用都已经通过sem_close()函数关闭了,然后只需在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量,注意如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作用。也就是说,必须是最后一个使用该信号量的进程来执行sem_unlick才有效。因为每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。
C、例子
server.c文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "jimmy";
int main(int argc,char **argv)
{
char ch;
int shmid;
key_t key;
char *shm, *s;
sem_t *mutex;
key = 1000;
mutex = sem_open(SEM_NAME, O_CREAT, 0644, 1);
if(mutex == SEM_FAILED) {
printf("unable to create semaphore\n");
sem_unlink(SEM_NAME);
return -1;
}
//create the shared memory segment with this key
shmid = shmget(key, SHMSZ, IPC_CREAT|0666);
if(shmid < 0) {
printf("failure in shmget\n");
return -1;
}
//attach this segment to virtual memory
shm = shmat(shmid, NULL, 0);
//start writing into memory
s = shm;
for(ch='A'; ch<='Z'; ch++) {
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while(*shm != '*') {
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
return 0;
}
client.c文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "jimmy";
int main(int argc,char **argv)
{
char ch;
int shmid;
key_t key;
char *shm, *s;
sem_t *mutex;
key = 1000;
mutex = sem_open(SEM_NAME, 0, 0644, 0);
if(mutex == SEM_FAILED) {
printf("reader:unable to execute semaphore\n");
sem_close(mutex);
return -1;
}
//create the shared memory segment with this key
shmid = shmget(key, SHMSZ, 0666);
if(shmid < 0) {
printf("reader:failure in shmget\n");
return -1;
}
//attach this segment to virtual memory
shm = shmat(shmid, NULL, 0);
//start reading
s = shm;
for(s=shm; *s!=NULL; s++) {
sem_wait(mutex);
putchar(*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by another semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
return 0;
}
三、SYSTEM V信号量
对于SYSTEM V来说,信号量是信号量值的集合,而不是单个信号量。内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:
struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限*/
struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集
中的每个信号量对应其中一个数组元素*/
ushort sem_nsems; /* sem_base 数组的个数*/
time_t sem_otime; /* 最后一次成功修改信号量数组的时间*/
time_t sem_ctime; /* 成功创建时间*/
};
struct sem {
ushort semval; /* 信号量的当前值 */
short sempid; /* 最后一次返回该信号量的进程ID号 */
ushort semncnt; /* 等待semval大于当前值的进程个数 */
ushort semzcnt; /* 等待semval变成0的进程个数 */
};
A、介绍
SYSTEM V信号量是SYSTEM V IPC(即SYSTEM V进程间通信)的组成部分,其他的有SYSTEM V消息队列,SYSTEM V共享内存。而关键字和IPC描述符无疑是它们的共同点,使用它们,就不得不先对它们进行熟悉。这里只对SYSTEM V信号量进行讨论。IPC描述符相当于引用ID号,要想使用SYSTEM V信号量(或MSG、SHM),就必须用IPC描述符来调用信号量。而IPC描述符是内核动态提供的(通过semget来获取),用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字KEY来定位描述符。某个KEY只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和客户事先认可共同使用某个KEY,那么大家就都能定位到同一个描述符,也就能定位到同一个信号量,这样就达到了SYSTEM V信号量在进程间共享的目的。
关键字的获取有多种方法:(1) 服务器可以指定关键字IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户机取用。关键字 IPC_PRIVATE保证服务器创建一个新IPC结构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件取得此标识符。IPC_PRIVATE关键字也可用于父、子关系进程。父进程指定 IPC_PRIVATE创建一个新IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为exec函数的一个参数传给一个新程序。(2) 在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个 IPC结构相结合,在此情况下,get函数(msgget、semget或shmget)出错返回。服务器必须处理这一错误,删除已存在的IPC结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。(3) 客户机和服务器认同一个路径名和课题I D(课题I D是0 ~ 2 5 5之间的字符值),然后调用函数ftok将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键字的问题。使用ftok并非高枕无忧。有这样一种例外:服务器使用ftok获取得一个关键字后,该文件就被删除了,然后重建。此时客户端以此重建后的文件来ftok所获取的关键字就和服务器的关键字不一样了。所以一般商用的软件都不怎么用ftok。一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使用ftok,而只是在该头文件中存放一个大家都知道的关键字。
B、函数说明
int semget(key_t key, int nsems, int oflag); //创建和打开信号量
(1) nsems>0 : 创建一个新的信号量集,指定集合中信号量的数量,一旦创建就不能更改。
(2) nsems==0 : 访问一个已存在的集合
(3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。
(4) 创建成功后信号量结构被设置:
.sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
.oflag 参数中的读写权限位存入sem_perm.mode
.sem_otime 被置为0,sem_ctime被设置为当前时间
.sem_nsems 被置为nsems参数的值
该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。
semget函数执行成功后,就产生了一个由内核维持的类型为semid_ds结构体的信号量集,返回semid就是指向该信号量集的引索。
int semop(int semid, struct sembuf *opsptr, size_t nops); //设置信号量的值(PV操作)
(1) semid: 是semget返回的semid
(2)opsptr: 指向信号量操作结构数组
(3) nops : opsptr所指向的数组中的sembuf结构体的个数
struct sembuf {
short sem_num; // 要操作的信号量在信号量集里的编号,
short sem_op; // 信号量操作
short sem_flg; // 操作表示符
};
(4) 若sem_op 是正数,其值就加到semval上,即释放信号量控制的资源;若sem_op 是0,那么调用者希望等到semval变为0,如果semval等于0就返回;若sem_op 是负数,其值加到semval上,若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用(就是信号量的值大于0),若没有设置 IPC_NOWAIT,则进程直接返回EAGAIN。注意:semval是指semid_ds中的信号量集中的某个信号量的值。
(5) sem_flg
SEM_UNDO 由进程自动释放信号量
IPC_NOWAIT 不阻塞
到这里,读者肯定有个疑惑:semop希望改变的semval到底在哪里?我们怎么没看到有它的痕迹?其实,前面已经说明了,当使用semget时,就产生了一个由内核维护的信号量集(当然每个信号量值即semval也是只由内核才能看得到了),用户能看到的就是返回的semid。内核通过semop 函数的参数,知道应该去改变semid 所指向的信号量的哪个semval。
int semctl(int semid, int semum, int cmd, ../* union semun arg */); //对信号集实行控制操作(semval的赋值等)
semid是信号量集合;
semnum是信号在集合中的序号;
semum是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成:
union semun
{
int val; // cmd == SETVAL
struct semid_ds *buf // cmd == IPC_SET或者 cmd == IPC_STAT
ushort *array; // cmd == SETALL,或 cmd = GETALL
};
val只有cmd ==SETVAL时才有用,此时指定的semval = arg.val。
注意:当cmd == GETVAL时,semctl函数返回的值就是我们想要的semval。千万不要以为指定的semval被返回到arg.val中。array指向一个数组,当cmd==SETALL时,就根据arg.array来将信号量集的所有值都赋值;当cmd ==GETALL时,就将信号量集的所有值返回到arg.array指定的数组中。buf 指针只在cmd==IPC_STAT 或IPC_SET 时有用,作用是semid 所指向的信号量集semid_ds机构体)。一般情况下不常用,这里不做谈论。另外,cmd == IPC_RMID还是比较有用的。
C、例子
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
//#define USE_SEM
union semun
{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
static int sem_id = 0;
static int set_semvalue()
{
//用于初始化信号量,在使用信号量前必须这样做
union semun sem_union;
sem_union.val = 1;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
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");
}
static int semaphore_p()
{
#ifdef USE_SEM
//对信号量做减1操作,即等待P(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;//P()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_p failed\n");
return 0;
}
#endif
return 1;
}
static int semaphore_v()
{
#ifdef USE_SEM
//这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;//V()
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_v failed\n");
return 0;
}
#endif
return 1;
}
int main(int argc,char **argv)
{
char message = 'X';
int i = 0;
//创建信号量
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
if(argc > 1) {
//程序第一次被调用,初始化信号量
if(!set_semvalue()) {
fprintf(stderr, "Failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
//设置要输出到屏幕中的信息,即其参数的第一个字符
message = argv[1][0];
sleep(2);
}
for(i = 0; i < 10; ++i) {
//进入临界区
if(!semaphore_p())
exit(EXIT_FAILURE);
//向屏幕中输出数据
printf("%c", message);
//清理缓冲区,然后休眠随机时间
fflush(stdout);
sleep(rand() % 3);
//离开临界区前再一次向屏幕输出数据
printf("%c", message);
fflush(stdout);
//离开临界区,休眠随机时间后继续循环
if(!semaphore_v())
exit(EXIT_FAILURE);
sleep(rand() % 2);
}
sleep(10);
printf("\n%d - finished\n", getpid());
if(argc > 1) {
//如果程序是第一次被调用,则在退出前删除信号量
sleep(3);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
更多推荐
已为社区贡献1条内容
所有评论(0)