您的位置:首页 > 其它

信号量知识汇总

2014-08-27 14:27 183 查看


1、什么是信号量
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问,保护忙资源。
2、信号量的用途
保护共享资源,使得资源在一个时刻只有一个进程(线程 ),信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒
3、信号量的分类
(1)内核信号量,由内核控制路径使用
(2)用户态进程使用的信号量 ,分为POSIX信号量和 SYSTEMV信号量。
其中:POSIX信号量,分为有名信号量和无名信号量;有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名 信号量,其值保存在内存中。
3.1、信号量机制简介
当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。
4、不同信号量使用
4.1、内核信号量
4.1.1、内核信号量构成
内核信号量类似于自旋锁 ,锁关闭,内核控制路径不执行,当内核控制路径要获取忙资源是,进程挂起;当资源释放是,进程重新运行。只有睡眠函数可以获得内核信号量。
内核信号量是 struct semaphore 类型的对象,它在<asm/semaphore.h>中定义:
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
}
其中:
count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这 个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。
4.1.2、内核信号量的相关函数
1)初始化:
void sema_init (struct semaphore *sem, int val);
void init_MUTEX (struct semaphore *sem); //将 sem 的值置为 1,表示资源空闲
void init_MUTEX_LOCKED (struct semaphore *sem); //将 sem的值置为0,表示资 源忙
2)申请内核信号量所保护的资源:
void down(struct semaphore * sem); // 可引起睡眠
int down_interruptible(struct semaphore * sem); //down_interruptible能被信 号打断
int down_trylock(struct semaphore * sem); // 非阻塞函数,不会睡眠,无法锁定 资源则
3)释放内核信号量所保护的资源:
void up(struct semaphore * sem);
4.1.3、内核信号量的常用 函数
当多个线程同时访问相同的资源时,可能会引发“竞态“,因此我们必须对共享资源进行并发控制。 解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)
ssize_t globalvar_write(struct file *filp,const char *buf,size_t len,loff_t*off)
{
//获得信号量
if(down_interruptible(&sem))
{
return - ERESTARTSYS;
}
//将用户空间的数据复制到内核空间的 global_var
if(copy_from_user(&global_var, buf, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
//释放信号量
up(&sem);
return sizeof(int);
}
4.2、用户进程信号量(POSIX 信号量与SYSTEM V 信号量 )
1、对于POSIX,信号量是个非负整数,常用于线程间同步 ;而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,常用于进程间同步 。
2、POSIX 信号量的引用头文件是“<semaphore.h>”,而 SYSTEM V 信号量的引用头
文件是“<sys/sem.h>”。
3、System V 信号量是复杂的,而 Posix 信号量是简单。
4.2.1POSIX 信号量 
4.2.1.1、无名信号量
无名信号量常用于多线程间的同步,同时也用于相关进程间的同步 ,即它是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程 (线程)的共享变量。
1)无名信号量相关函数
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化函数
若pshared==0 用于同一多线程的同步;
若 pshared>0用于多个相关进程间的同步(即由fork产生的)
int sem_getvalue(sem_t *sem, int *sval); //取回信号量sem 的当前值,把该值保存到sval中。
若有1个或更多的线程或进程调用 sem_wait 阻塞在该信号量上,该函数返回两种值: 返回 0 或返回阻塞在该信号量上的进程或线程数目
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; 呼醒正在等待该信号量的任意线程。
2)无名信号量的应用
1、无名信号量多线程同步
注明:线程 1 先执行完,然后线程 2 才继 续执行,直至结束 
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id1, sem_id2;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id1);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id2);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id2);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id1);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id1, 0, 1); // 空闲的
sem_init(&sem_id2, 0, 0); // 忙的
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main,,,\n")
return 0;
}
2、无名信号量在相关进程间的同步
注明:相关进程,是因为本程序中共有2个进程,其中一个是另外一个的子进程.
对于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) //
{
perror("semaphore initilization");
exit(0);
}
if (fork() == 0)
{ /* child process*/
for (i = 0; i < nloop; i++)
{
sem_wait(&mutex);
printf("child: %d\n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}
/* back to parent process */
for (i = 0; i < nloop; i++)
{
sem_wait(&mutex);
printf("parent: %d\n",(*ptr)++);
sem_post(&mutex);
}
exit(0);
}
4.2.1.2 有名信号量
把信号量的值保存在文件中 ,既可以用于线程,也可以用于相关进程间,甚至是不相关 进程。
由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父 进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当 然文件里面保存的有名信号量值就共享了。
1)有名信号量相关函数
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value); //初始化
说明:有名信号量使用 sem_open 代替 sem_init,另外在结束的时候要像关闭文件 一样去关闭这个有名信号量。
功能:打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完 成了信号量的创建、初始化和权限的设置
参数:name 是文件的路径名;
Oflag 有 O_CREAT 或 O_CREAT|EXCL 两个取值;
mode_t 控制新的信号量的访问权限;
Value 指定信号量的初始化值。
其次有名信号量和无名信号量共享sem_wait sem_post函数
在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量
2)有名信号量应用
1、服务进程和客户进程都使用 shmget shmat 来获取得一块共 享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。
<u>File1: server.c </u>
#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[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize semaphore
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED)
{
perror("unable to create semaphore");
sem_unlink(SEM_NAME);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid<0)
{
perror("failure in shmget");
exit(-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);
exit(0);
}

<u>File 2: client.c</u>
#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[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
perror("reader:unable to execute semaphore");
sem_close(mutex);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,0666);
if(shmid<0)
{
perror("reader:failure in shmget");
exit(-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);
exit(0);
}
4.2.2SYSTEM V 信号量
信号量值的集合,而不是单个信号量。相关的信号量操作函数由<sys/ipc.h>引 用。 
4.2.2.1、信号量结构体
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 的进程个数 */
};
4.2.2.2、相关函数
SYSTEM V 信号量是 SYSTEM V IPC(即 SYSTEM V 进程间通信)的组成部分,其 他的有 SYSTEM V 消息队列,SYSTEM V 共享内存。而关键字和 IPC描述符无疑是它们的 共同点。
IPC描述符相当于引用 ID 号,要想使用 SYSTEM V 信号量(或 MSG、SHM),就必 须用 IPC 描述符来调用信号量。而 IPC描述符是内核动态提供的(通过 semget 来获取), 用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字KEY 来定位描述符。
KEY只会固定对应一个描述符(由内核完成),假如服务器和 客户事先认可共同使用某个 KEY,那么大家就都能定位到同一个描述符,也就能定位到同 一个信号量,这样就达到了 SYSTEM V 信号量在进程间共享的目的。
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 初始化的。
int semop(int semid, struct sembuf *opsptr, size_t nops); //设置信号量的值
(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 变为大于或等于 sem_op 的绝对 值
(5) sem_flg
SEM_UNDO 由进程自动释放信号量
IPC_NOWAIT 不阻塞
int semctl(int semid, int semum, int cmd, ../* union semun arg */);//信号集实行控制操作 
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
array 指向一个数组,当 cmd==SETALL 时,就根据 arg.array 来将信号量集的所 有值都赋值;当 cmd ==GETALL 时,就将信号量集的所有值返回到 arg.array 指定的数 组中
buf 指针只在 cmd==IPC_STAT 或 IPC_SET 时有用,作用是 semid 所指向的信号量 集
4.2.2.3、相关应用
#include  <sys/types.h>
#include  <sys/ipc.h>
#include  <sys/sem.h>
#include  <stdio.h>
static int nsems;
static int semflg;
static int semid;
int  errno=0;

union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
int main()
{
struct sembuf sops[2]; //要用到两个信号量,所以要定义两个操作数组
int rslt;
unsigned short argarray[80];
arg.array = argarray;
semid = semget(IPC_PRIVATE, 2, 0666);
if(semid < 0 )
{
printf("semget failed. errno: %d\n", errno);
exit(0);
}
//获取 0th 信号量的原始值
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
//初始化 0th 信号量,然后再读取,检查初始化有没有成功
arg.val = 1; // 同一时间只允许一个占有者
semctl(semid, 0, SETVAL, arg);
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
sops[1].sem_flg = 0;
rslt=semop(semid, sops, 1); //申请 0th 信号量,尝试锁定
if(rslt < 0 )
{
printf("semop failed. errno: %d\n", errno);
exit(0);
}
//可以在这里对资源进行锁定
sops[0].sem_op = 1;
semop(semid, sops, 1); //释放 0th 信号量
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
rslt=semctl(semid, 0, GETALL, arg);
if (rslt < 0)
{
printf("semctl failed. errno: %d\n", errno);
exit(0);
}
printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]);
if(semctl(semid, 1, IPC_RMID) == -1)
{
Perror(“semctl failure while clearing reason”);
}
return(0);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  semaphore