为了防止出现因多个进程同时访问一个共享资源引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。信号量就可以提供这样的一种访问机制,确保多个线程在对共享内存这样的共享资源同时读写时,使之实现同步与互斥,也就是说信号量用来调协进程对公共资源的访问的
信号量本质上是一个计数器,也是临界资源,进程对其访问都是原子操作,信号量记录着临界资源的个数
信号量可以使用ipcs -s查看
使用ipcrm -s [semid]删除
原理
信号量只能进行等待和发送信号两种操作,即P(sv)和V(sv)
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程并将其PCB放入队列中
V(sv):如果等待队列中有其他进程因等待sv而被挂起,就让一个恢复运行,如果没有进程因等待sv而挂起,就给它加1
所以一个信号量不仅包含临界资源的数目,还必须维护一个队列
实际应用中信号量是不能单独定义的,而是定义一个信号量集,其中包含一组信号量,同一信号集中的信号量使用同一个引用ID,每个信号量集有一个与之对应的结构体,其中记录着各种信息
1 | struct semid_ds { |
其中struct sem才是信号量的描述结构体:
1 | struct sem { |
系统中的限制:
SEMVMX最大的信号值
SEMMNI系统允许的最大信号量集个数
SEMMNS系统允许的最大信号量个数
SEMMSL每个信号集中最大的信号量个数
创建/打开信号集
int semget(key_t key, int nsems, int semflg);
参数key和semflg用法和共享内存相同
nsems: 信号量集中创建的信号量数量,如果创建信号集,sem_nsems将被设置为nsems;如果打开一个已存在的信号集,此参数就被忽略
如果调用成功,则返回信号量集合标识符,否则返回-1,并设置error
操作信号量集(原子操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid: semget返回的信号量集标识符
sops: 为sembuf结构数组指针,每一个元素表示一个操作,这个函数是一个原子操作,一旦执行就将执行数组中所有的操作
nsops: 为数组中元素的个数
1 | struct sembuf { |
控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
semid: semget返回的信号集标识符
semnum: 信号集中信号量的序号
cmd: 将要采取的动作
根据cmd参数具体内容,IPC_RMID删除信号量,可能有第四个参数,是一个联合体,当cmd为GET时返回对应值
成功返回0,失败返回-1,设置errno
1 | union semun { |
操作演示
子进程循环输出A和A到显示器上这2个A中间不可被打断
父进程循环输出B和B到显示器上这2个B中间不可被打断
https://github.com/Ranjiahao/Linux/tree/master/ipc/Semaphore