- UID
- 1029342
- 性别
- 男
|
信号量
一.什么是信号量
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)
所拥有。
信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明
它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
二.信号量的分类
在学习信号量之前,我们必须先知道——Linux提供两种信号量:
(1) 内核信号量,由内核控制路径使用
(2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM
V信号量。
POSIX信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名
信号量,其值保存在内存中。
倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷
失了方向。
三.内核信号量
1.内核信号量的构成
内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,
当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源
被释放时,进程才再次变为可运行。
只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内
核信号量。
内核信号量是struct semaphore类型的对象,它在<asm/semaphore.h>中定义:
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
}
count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这
个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。
2.内核信号量中的等待队列(删除,没有联系)
上面已经提到了内核信号量使用了等待队列wait_queue来实现阻塞操作。
当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。当条件得到满足
时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工
作队列中等待CPU资源,在适当的时机被调度。
内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用
户干涉。由用户真正使用的等待队列我们将在另外的篇章进行详解。
3.内核信号量的相关函数
(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.内核信号量的使用例程
在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的
共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux内核中
解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。
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);
}
四.POSIX 信号量与SYSTEM V信号量的比较
1. 对POSIX来说,信号量是个非负整数。常用于线程间同步。
而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,
这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。
2.POSIX信号量的引用头文件是“<semaphore.h>”,而SYSTEM V信号量的引用头文件是
“<sys/sem.h>”。
3.从使用的角度,System V信号量是复杂的,而Posix信号量是简单。比如,POSIX信
号量的创建和初始化或PV操作就很非常方便。
五.POSIX信号量详解
1.无名信号量
无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初
始化该无名信号量,之后就可以放心使用了。
无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信
号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程
(线程)的共享变量,这两个条件是缺一不可的。
常见的无名信号量相关函数:sem_destroy
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) 返回阻塞在该信号量上的进程或线程数目
linux采用返回的第一种策略。
sem_wait(或sem_trywait)相当于P操作,即申请资源。
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。
sem_post相当于V操作,释放资源。
int sem_post(sem_t *sem);
把指定的信号量sem的值加1;
呼醒正在等待该信号量的任意线程。
注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数
(a)无名信号量在多线程间的同步
无名信号量的常见用法是将要保护的变量放在sem_wait和sem_post中间所形成的
临界区内,这样该变量就会被保护起来,例如:
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id, 0, 1);
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;
} |
|