信号量

什么是信号量
信号量主要保护共享资源的,确保该资源在同一时刻只有一个线程占用。
换句话说它就是控制多进程(多线程)共同访问共享资源的一种手段。
信号量的定义
最初的定义:信号量是一个特殊的变量,它只能取正整数值,并且程序对其访问都是原子操作。

正式的定义:它是一个特殊的变量,只允许对它进行等待和发送信号。

信号量就是一种特殊的计数器
当其值>0时,其表示可用临界资源的个数
当其值<0,表示资源忙,至少有一个线程在等待。
信号量工作原理

先看计数器计数的方式:

自增型:开始计数为0,来一个自增1,走一个自减1,到计数最大值不允许再来
自减型:开始计数 就是最大值,来一个自减1,走一个自增1,计数到0不允许再来
信号量采用的是自减型,这里说下我个人的理解:(MAX为最大值)
1)我们一般关心资源占用完了没,而不是这个资源一共可以被几个进程占用。所以我们关心的是什么时候不允许再来。
显然自减型判断是否为0更为方便。

我们可以用公共停车场来解释

公共停车场 :信号量控制访问的临界资源的个数
车位:临界资源
想要停车的车辆:想要访问临界资源的线程
计数器:信号量

此刻停车场共有4个停车位,3个停车位分别被车D ,车F,车E占用了,还剩下车位4此刻没被占用。

理解为:信号量管理了4个临界资源, 三个临界资源此刻被占用了,一个没被占用。

现在车A 车B 车C都想进去停车,但是车位只剩下一个。出现了一种“竞态”,谁先抢到计数器发的停车卡谁就可以进去。
进程A 进程B 进程C 同时想对信号量进行P操作,这一步是原子操作,谁先操作成功就可以进入临界区访问临界资源,其中一个进程完成了p操作后,其他两个进程会被挂起,存放入等待队列中,当信号量又>0时,排在前面的那个进程又会被唤醒,又进行p操作。


信号量分类
1)内核信号量,由内核控制路径使用
2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量。
POSIX信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。
无名 信号量,其值保存在内存中。

内核信号量
内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而, 当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源 被释放时,进程才再次变为可运行。只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。

自旋锁是相当于数据被锁住,就反复执行一条指令(个人理解类比于代码中while(1)死循环),不是睡眠,所以它能处理中断处理的程序。
自旋锁只能一个进程持有。
信号量可以多个进程持有。
自旋锁数据被锁住,等待的进程(线程)就会反复的执行一条指令,对CPU资源的占用,所以适合短时间持有锁。
信号量<0进程会被挂起,和放入等待队列,>0时候进程会被唤醒。所以是函数准备和收尾开销比较大,适合长时间持有锁。

这个是内核信号量的结构体

struct semaphore
{
     atomic_t count;   
    int sleepers;
    wait_queue_head_t wait;  
}


count是信号量的值,>0代表代表有闲资源,=0代表忙资源, <0代表有至少一个等待进程。
sleepers是一个标志,代表是否有一些进程在信号量上睡眠
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。


内核信号量的函数

初始化信号量
void sema_init (struct semaphore *sem, int val);
把val值初始化信号量sem;

获取信号量
void down(struct semaphore * sem);
这个函数是获得信号量sem,可能会引起睡眠,所以不可以中断上下文使用该函数,它将sem的值减少1,如果该值为非负,直接返回,否则该进程被挂起,直到有进程释放了该信号量,唤醒该进程。
int down_interruptible(struct semaphore * sem);
会被信号打断,正常返回0,打断返回-EINTR

int down_trylock(struct semaphore * sem); 
它会去获取信号量,如果立即获得则返回0,没有则返回非0,不会被睡眠,所以可以被中断上下文使用。


释放信号量
void up(struct semaphore * sem);

用户信号量

POSIX信号量
(1)无名信号量

无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

这里可以理解为就是我们创建一个信号变量sem_t  sem;那么这个变量必须是多线程所的共享的变量。这也是为什么常用于多线程,因为一个进程里的多线程中全局变量,栈区堆区,文件描述符是共享的。很容易创建这个共享的信号量。

无名信号量的函数
int sem_init(sem_t *sem, int shared, unsigned int value);
sem是信号量的指针;
share = 0 代表是在同一进程里的多线程,
value初始化值;

int sem_wait(sem_t wait);
申请访问 相当于 p操作;

int sem_post(sem_t *sem);
释放资源 相当于 v操作;

我们看个例子主线程把输入字符传入给线程id,线程把字符串大写变小写,写入a.txt

无名就是这么简单的操作,但是也有明显的缺点,就是要共享这个sem信号量。

如果在多进程中使用无名信号量
很简单创建一个信号量,然后fork()一下,当然只要在血缘的进程都可以使用,也暴露了其局限性

(2)有名信号量
有名信号量就是相当于用sem_open函数 代替 无名的sem_init函数
sem_wait 和 sem_post 是和无名共享的
还有个关闭信号量的函数sem_close()


sem_t *sem_open(const char *name,int oflag,mode_t mode,int value);
sem_t  sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);
各位看下就大致知道参数的意思了
name:是文件路径名,linux信号量是直接存在/dev/shm 你写个name相当于/dev/shm/sem.name  默认帮你保存在这个路径
你只要写好.后面的name,这个你自己取个后缀名。
mode :权限
value:信号量初始值

SYSTEMV信号量
这里操作的是信号量集,不是单个信号量了,要用到的头文件<sys/ipc.h>

信号量集
信号量集和信号集没有一点关系,首先信号集是信号的集合,它用的是sigset_t类型的变量的位来保存每一个信号,而信号量集是信号量的集合,它是用数组每个元素来保存每个信号量。信号是通知进程间事件的发生,信号量是控制进程间同步,或者线程间同步的一种手段。

SYSTEMV信号量函数

fsemget(key_t key, int nsems, int oflag)

key 用来大家都共同使用某个key,大家都能定位到一个信号量上,这样就能达到信号量在进程间共享。

nsems 代表着信号量的个数,信号量集创建一旦完成就不得更改其个数,除非删除重新创建,或者重新创建一个新的信号量集。

oflag 指定该信号量的权限

int semctl(int semid, int semnum, int cmd,...) 


semid 是信号量
semnum  是信号量在集合中的序号
semun   是一个结构体
union semun
{

          int val;
          struct semid_ds *buf;
          ushort *array;   

这个函数有两个作用一个是初始化信号量,一个是删除信号量
我们看一下这个函数分别是怎么使用这两个作用的
cmd取SETVAL,可以给每个信号量赋值
第四个参数是初始值
semop函数
struct sembuf
{

     unsigned short sem num;       //操作信号量的下标
     short  sem_op;                //对信号量操作方式 , -1,0, 1
     short sem_flg;              // 计数为0是否阻塞,为0就阻塞,为IPC_NOWIT就阻塞
}

 
如何创建信号量集
1)使用fotk或者头文件,生成一个key
2)使用semget创建/获取信号量集
3)使用semtcl给该信号量集中每个信号赋值
4)使用信号量,semop函数
5)使用semtcl删除信号量

 
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 鲸 设计师:meimeiellie 返回首页