Linux信号量
一、信号量常用函数
1. semget函数
- 功能:创建或者获取信号量
- 函数声明:
int semget(key_t key, int nsems, int semflg);
- 头文件
<sys/sem.h>
key
: 信号量的键值,是一个整数typedef unsigned int key_t
,一般采用十六进制,例如0x5005
,不同信号量的key
不能相同。nsems
: 信号量的个数,如1,2semflg
: 信号量的访问权限,与文件的权限一样,例如0666|IPC_CREAT
,0666
表示全部用户对它可读写,IPC_CREAT
表示如果信号量不存在,就创建它,在头文件<sys/ipc.h>
中声明。- 返回值: 成功返回信号量的
id
;失败返回-1
,并设置errno
- 使用
ipcs -s
命令可以查看系统的信号量,包括:键值key
,信号量id
(semid
),拥有者(owner
),权限(perms
),大小(bytes
) - 使用
ipcrm -s 信号量id
命令可以手工删除信号量
2. semop函数
- 功能:给信号量做
pv
操作,p
则信号量减1
,v
是信号量加1
- 头文件
<sys/sem.h>
- 函数声明:
int semop(int semid, struct sembuf *sops, size_t nsops);
semid
:semget()
返回的信号量唯一标识sops
: 头文件<sys/sem.h>
中定义的结构体
struct sembuf {
unsigned short sem_num; /* 信号的第几个。信号量的编号 */
short sem_op; /* 信号量的pv操作,如果是-1则是p操作,如果是v则1*/
short sem_flg; /* 如果信号量用于互斥锁,设置为SEM_UNDO;如果信号量用于生产消费者模型,设置为0。*/
};
nsops
: 需要操作的信号量的个数- 返回值:成功返回信号量的标识符;失败返回
-1
- 注意:
p
操作把当前进程的运行状态设置为堵塞状态,直到另外1
个进程唤醒。申请1
个空的资源(信号量减1
)如果申请成功直接退出,如果失败,进程堵塞,一直等待,直到有进程v
操作为止。v
操作负责把1
个堵塞的进程唤醒(释放资源)(信号量加1
)。信号量如果只有0
和1
两种状态,可用作互斥锁,1
表示解锁,0
表示加锁。因此,p
操作就是加锁的过程,v
操作就是解锁的过程。
3. semctl函数
- 功能:删除、修改、获取、初始化信号量,可以理解为对信号量的一些操作,通过不同的入口参数来设置
- 头文件
<sys/sem.h>
- 函数声明:
常用两个重载版本:
int semctl(int semid, int semnum, int cmd);
用于信号量的删除以及信号量值的获取
int semctl(int semid, int semnum, int cmd, union semun sem_union);
用于信号量的初始化以及信号量的删除 semid
:semget()
返回的信号量唯一标识semnum
: 信号量的编号,使用单个信号量的时候默认是0cmd
: 操作信号的命令
常用宏:
IPC_RMID
删除信号量
SETVAL
设置信号量的值
GETVAL
获取信号量的值sem_union
: 自行定义的共同体
union semun { //当前联合需要自行定义
int val; /* 设置信号量的值 */
struct semid_ds *buf; /*IPC_STAT, IPC_SET的时候需要的buf结构体指针 */
unsigned short *array; /* 设置多个信号量的时候 数组指针 */
struct seminfo *__buf; /* IPC_INFO */
}
- 返回值:如果是
GETVAL
,成功返回信号量的值,失败返回-1
;如果是SETVAL
或者IPC_RMID
,成功返回0
,失败返回-1
二、信号量当作互斥锁使用
1.csemp类
_public.h
#ifndef _PUBLIC_HH
#define _PUBLIC_HH
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
class csemp
{
private:
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int m_semid;
short m_sem_flg;
csemp(const csemp&) = delete;
csemp& operator=(const csemp&) = delete;
public:
csemp(): m_semid(-1) {}
// 初始化信号量
bool init(key_t key, unsigned short value=1, short sem_flg=SEM_UNDO);
// P操作,加锁
bool wait(short value=-1);
// V操作,释放锁
bool post(short value=1);
int getValue();
bool destroy();
~csemp() {}
};
#endif
2.成员函数实现
_public.cpp
#include <_public.h>
// 初始化
bool csemp::init(key_t key, unsigned short value, short sem_flg)
{
// 如果已经初始化,无需再次初始化
// 这里的初始化指的是将内存中的信号量绑定到进程中实例化的对象
if (m_semid != -1) return false;
m_sem_flg = sem_flg;
// 获取信号量,如果获取不成功就创建
if ((m_semid = semget(key, 1, 0666)) == -1)
{
// 如果失败的原因是内存中的信号量不存在,那么就创建信号量
if (errno == ENOENT)
{
// IPC_EXCL保证只有一个进程可以创建信号量,其他进程只能获取
if ((m_semid = semget(key, 1, 0666|IPC_CREAT|IPC_EXCL)) == -1)
{
// 如果失败的原因是信号量已经存在,那么就获取信号量,返回true
if (errno == EEXIST)
{
// 如果获取失败,就返回false
if ((m_semid = semget(key, 1, 0666) == -1))
{
perror("init 1 semget()");
return false;
}
return true;
}
// 如果是其他原因导致的失败,那么返回false
else
{
perror("init 2 segment()");
return false;
}
}
// 创建信号量成功之后,设置信号量的值
// 实例化共同体成员对象
union semun sem_union;
sem_union.val = value;
if (semctl(m_semid, 0, SETVAL, sem_union) < 0)
{
perror("init semctl()");
return false;
}
}
// 如果是其他原因导致的失败,则说明已存在的信号量无法被获取
else
{
perror("init 3 semget()");
return false;
}
}
// 获取成功,直接返回true
return true;
}
3. 信号量实现互斥锁
demo.cpp
#include <_public.h>
struct Turtle
{
int age;
char name[51];
};
int main(int argc, char* argv[])
{
if (argc != 3)
{
cout << "入口参数过少" << endl;
return -1;
}
int shmid = shmget(0x5001, sizeof(Turtle), 0640|IPC_CREAT);
if (shmid == -1)
{
cout << "获取共享内存失败" << endl;
return -1;
}
// 链接共享内存至当前进程
Turtle* tur = (Turtle*)shmat(shmid, 0, 0);
cout << "age: " << tur->age << ' ' << "name: " << tur->name << endl;
// 初始化信号量
csemp mutex;
if (mutex.init(0x6003) == -1)
{
cout << "初始化信号量失败" << endl;
return -1;
}
// 加锁
if (mutex.wait())
{
cout << "申请加锁成功!" << endl;
}
// 修改共享内存数据
tur->age = atoi(argv[1]);
strcpy(tur->name, argv[2]);
sleep(10);
cout << "age: " << tur->age << ' ' << "name: " << tur->name << endl;
// 解锁
if (mutex.post())
{
cout << "解锁成功!" << endl;
}
// 断开进程与共享内存的链接
shmdt(tur);
//删除共享内存(当真正需要删除共享内存时将注释解开即可)
/*
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
cout << "共享内存删除失败" << endl;
return -1;
}*/
return 0;
}