文章目录
1. 基本概念
- 信号量是一种用于提供不同进程或者一个给定进程的线程间同步的手段。
- 分为两种信号量:
1)命名信号量:有个名字,不相关的进程可以访问,随内核持续。
2)未命名信号量(基于内存的信号量):没有名字,在内存中预先商定的位置,可以在进程之间或一组线程之间共享,进程间共享时信号量必须位于共享内存区域中,线程共享时,信号量置于线程共享的内存区域中。随进程或者内核持续性。
两种信号量的API:
其中命名使用sem_open
创建信号量,使用sem_close
关闭信号量的引用,sem_unlink
删除信号量
基于内存的信号量需要程序自身定义一个sem_t,
在通过sem_init
来初始化,最后通过sem_destory
来销毁 - Posix信号量是一个整数
linux把有名信号量当成小型的Posix共享内存对象,挂载在目录/dev/shm/
下。 - linux下编译include<semaphore.h>后任然出现
undefined reference to ‘sem_open‘
,需要加上编译选项-pthread
2. 命名信号量
2.1 打开或者创建 sem_open
可以创建和打开一个信号量,打开的时候只需要前面两个参数
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
// oflags:O_CREAT,O_EXCL,O_RDONLY, O_RDWR,O_WRONLY mode为权限位 value为信号量的值
sem_t *sem_open(const char *name, int oflags, ... /*mode, value*/);
// 成功返回一个信号量描述符, 失败返回SEM_FAILED
// sem_open会自己分配内存对象,返回指针。
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
sem_t *sem = sem_open("test.sem", O_CREAT | O_RDWR, (S_IRUSR | S_IWUSR), 0);
if (sem == SEM_FAILED) {
printf("create sem fail\n");
return -1;
}
return 0;
}
2.2 关闭一个信号量 sem_close
int sem_close(sem_t *sem); // 成功返回0,失败返回-1
2.3 删除一个信号量 sem_unlink
int sem_unlink(const char *name); // 成功返回0,失败返回-1
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
sem_t *sem = sem_open("test.sem", O_RDWR);
if (sem == SEM_FAILED) {
printf("open sem fail\n");
return -1;
}
if (sem_close(sem) == -1) {
printf("close sem fail\n");
return -1;
}
if (sem_unlink("test.sem") == -1) {
printf("unlink sem fail\n");
return -1;
}
printf("close and unlink sem end\n");
return 0;
}
2.4 信号量操作: sem_wait,sem_post, sem_getvalue
信号量操作:PV操作
P – 减少信号量 – sem_wait
V – 增加信号量 – sem_post
#include <semaphore.h>
// P操作,信号量-1,如果当前信号量大于0,sem_wait直接返回,否则阻塞知道信号量大于0
int sem_wait(sem_t *sem); // 成功返回0,失败返回-1
// 在阻塞过程中被其他信号中断,则sem_wait返回EINTER
// 非阻塞版本,如果信号量不大于0,则会失败并且返回EAGAIN错误
int sem_trywait(sem_t *sem); // 成功返回0,失败返回-1
// 指定尝试时间, 超过时间后返回失败并置错误为ETIMEDOUT
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // 成功返回0,失败返回-1
// 指定的时间是一个绝对值,需要相对时间需要调用clock_gettime获取CLOCK_REALTIME,再在其基础上增加时间生成一个适合的timespec结构
// 发布一个信号量,V操作,信号量+1, 唤醒阻塞的sem_wait函数
int sem_post(sem_t *sem); // 成功返回0,失败返回-1
int sem_getvalue(sem_t *sem, int *value); // // 成功返回0,失败返回-1
打开并sem_wait等待信号量
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
sem_t *sem = sem_open("test.sem", O_CREAT | O_RDWR, (S_IRUSR | S_IWUSR), 1);
if (sem == SEM_FAILED) {
printf("open sem fail\n");
return -1;
}
if (sem_wait(sem) == -1) {
printf("open sem fail\n");
return -1;
}
printf("%d, sem_wait success return \n", (long)getpid());
return 0;
}
打开、关闭、删除信号量
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
sem_t *sem = sem_open("test.sem", O_RDWR);
if (sem == SEM_FAILED) {
printf("open sem fail\n");
return -1;
}
if (sem_close(sem) == -1) {
printf("close sem fail\n");
return -1;
}
if (sem_unlink("test.sem") == -1) {
printf("unlink sem fail\n");
return -1;
}
printf("close and unlink sem end\n");
return 0;
}
打开、获取信号量数量、发送信号量
#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
sem_t *sem = sem_open("test.sem", O_RDWR);
if (sem == SEM_FAILED) {
printf("open sem fail\n");
return -1;
}
int value = 0;
if (sem_getvalue(sem, &value) != -1) {
printf("cur sem value is %d \n", value);
}
if (sem_post(sem) == -1) {
printf("post sem fail\n");
return -1;
}
if (sem_getvalue(sem, &value) != -1) {
printf("cur sem value is %d \n", value);
}
printf("post sem end: %d \n", (long)getpid());
return 0;
}
3. 未命名信号量(基于内存的信号量、匿名信号量)
- 基于内存的信号量,信号量是程序自己分配空间(命名信号量为
sem_open
分配空间),信号量存放的位置决定了多线程和多进程之间的共享性, 也决定了信号量是随内核还是随程序的持续性 - 相比命名信号量增加了两个api,
sem_init
和sem_destory(sem)
,sem_init
是自己定义一个sem_t
,然后初始化, 命名信号量的sem_open
是自己定义一个sem_t*
指针,通过sem_open
来初始化。这两个函数分别代替命名信号量的sem_open
和sem_close
,sem_unlink
- 相比命名信号量不存在权限位控制(因为不存在sem_open)。
3.1 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value); // 成功返回0,失败返回-1
1)pshared
:0表示线程共享,1表示进程间共享,进程间共享时需要为信号量分配个共享内存区域(Posix共享内存对象 – 随内核;使用mmap()
创建的共享映射 – fork
创建的子进程会继承父进程的内存映射;System V的共享内存段 – 随内核)
2)对一个已经初始化过的未命名信号量再次进行初始化会导致未定义的行为。
3)value用来对未命名信号量赋值。
3.2 销毁信号量
销毁的信号量必须先经过sem_init
int sem_destory(sem_t *sem); // 成功返回0,失败返回-1
4. 其他
4.1 信号量相关宏限制
SEM_NSEMS_MAX
进程能够拥有的POSIX信号量的最大数目, >256
SEM_VALUE_MAX POSIX
信号量能够取得最大值 32767
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
int main()
{
printf("SEM_NSEMS_MAX = %ld, SEM_VALUE_MAX = %ld\n",
sysconf(_SC_SEM_NSEMS_MAX), sysconf(_SC_SEM_VALUE_MAX));
return 0;
}
参考:
1) 网络编程 卷2 进程间通信
2) linux/unix系统编程手册
3)linux api查询 :https://www.die.net/