上一章,讲述了 system v 信号量,主要运行于进程之间,本章主要介绍 posix 信号量:有名信号量、无名信号量。
posix 信号量
posix 信号量进程是 3 种 ipc(inter-process communication) 机制之一,3 种 ipc 机制源于 posix.1 的实时扩展。single unix specification 将 3 种机制(消息队列,信号量和共享存储)置于可选部分中。在 susv4 之前,posix 信号量接口已经被包含在信号量选项中。在 susv4 中,这些接口被移至了基本规范,而消息队列和共享存储接口依然是可选的。
posix 信号量接口意在解决 xsi 信号量接口的几个缺陷。
相比于 xsi 接口,posix 信号量接口考虑了更高性能的实现。
posix 信号量使用更简单:没有信号量集,在熟悉的文件系统操作后一些接口被模式化了。尽管没有要求一定要在文件系统中实现,但是一些系统的确是这么实现的。
posix 信号量在删除时表现更完美。回忆一下,当一个 xsi 信号量被删除时,使用这个信号量标识符的操作会失败,并将 errno 设置成 eidrm。使用 posix 信号量时,操作能继续正常工作直到该信号量的最后一次引用被释放。
分类
posix 信号量是一个 sem_t 类型的变量,但 posix 有两种信号量的实现机制:无名信号量和命名信号量。无名信号量只可以在共享内存的情况下,比如实现进程中各个线程之间的互斥和同步,因此无名信号量也被称作基于内存的信号量;命名信号量通常用于不共享内存的情况下,比如进程间通信。
同时,在创建信号量时,根据信号量取值的不同,posix 信号量还可以分为:
二值信号量:信号量的值只有 0 和 1,这和互斥量很类似,若资源被锁住,信号量的值为 0,若资源可用,则信号量的值为 1;
计数信号量:信号量的值在 0 到一个大于 1 的限制值之间,该计数表示可用的资源的个数。
区别
有名信号量和无名信号量的差异在于创建和销毁的形式上,但是其他工作一样。
无名信号量只能存在于内存中,要求使用信号量的进程必须能访问信号量所在的这一块内存,所以无名信号量只能应用在同一进程内的线程之间(共享进程的内存),或者不同进程中已经映射相同内存内容到它们的地址空间中的线程(即信号量所在内存被通信的进程共享)。意思是说无名信号量只能通过共享内存访问。
相反,有名信号量可以通过名字访问,因此可以被任何知道它们名字的进程中的线程使用。
单个进程中使用 posix 信号量时,无名信号量更简单。多个进程间使用 posix 信号量时,有名信号量更简单。
联系
无论是有名信号量还是无名信号量,都可以通过以下函数进行信号量值操作。
wait(p)
wait 为信号量值减一操作,总共有三个函数,函数原型如下:
#include int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);link with -pthread. 这一句表示 gcc 编译时,要加 -pthread.返回值: 若成功,返回 0 ;若出错,返回 -1
sem_wait 的作用是,若 sem 小于 0 ,则线程阻塞于信号量 sem ,直到 sem 大于 0 ;否则信号量值减 1。
sem_trywait 作用与 sem_wait 相同,只是此函数不阻塞线程,如果 sem 小于 0,直接返回一个错误(错误设置为 eagain )。
sem_timedwait 作用也与 sem_wait 相同,第二个参数表示阻塞时间,如果 sem 小于 0 ,则会阻塞,参数指定阻塞时间长度。abs_timeout 指向一个结构体,这个结构体由从 1970-01-01 00:00:00 +0000 (utc) 开始的秒数和纳秒数构成。
结构体定义如下:
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds [0 .. 999999999] */};
如果指定的阻塞时间到了,但是 sem 仍然小于 0 ,则会返回一个错误 (错误设置为 etimedout )。
post(v)
post 为信号量值加一操作,函数原型如下:
#include int sem_post(sem_t *sem);link with -pthread.返回值: 若成功,返回 0 ;若出错,返回 -1
无名信号量
接口函数
信号量的函数都以 sem_ 开头,线程中使用的基本信号函数有 4 个,他们都声明在头文件 semaphore.h 中,该头文件定义了用于信号量操作的 sem_t 类型:
sem_init
该函数用于创建信号量,原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:该函数初始化由 sem 指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared 控制信号量的类型,如果其值为 0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享,value 为 sem 的初始值。返回值:该函数调用成功返回 0,失败返回 -1。
sem_destroy
该函数用于对用完的信号量进行清理,其原型如下:
int sem_destroy(sem_t *sem);
返回值:
成功返回 0,失败返回 -1。
sem_getvalue 函数
该函数返回当前信号量的值,通过 restrict 输出参数返回。如果当前信号量已经上锁(即同步对象不可用),那么返回值为 0,或为负数,其绝对值就是等待该信号量解锁的线程数。
int sem_getvalue(sem_t *restrict, int *restrict);
使用实例
【实例 1】:
#include #include #include #include #include #include #include #include sem_t sem;#define handle_error(msg) do { / perror(msg); / exit(exit_failure); / }while (0)static void handler(int sig){write(stdout_fileno, sem_post() from handler/n, 24);if(sem_post(&sem) == -1){ write(stderr_fileno, sem_post() failed/n, 18); _exit(exit_failure);}}int main(int argc, char *argv[]){ int s; struct timespec ts; struct sigaction sa; if (argc != 3) { fprintf(stderr, usage: %s /n, argv[0]); exit(exit_failure); } if (sem_init(&sem, 0, 0) == -1) handle_error(sem_init); /* establish sigalrm handler; set alarm timer using argv[1] */ sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(sigalrm, &sa, null) == -1) handle_error(sigaction); alarm(atoi(argv[1])); /* calculate relative interval as current time plus number of seconds given argv[2] */ if (clock_gettime(clock_realtime, &ts) == -1) handle_error(clock_gettime); ts.tv_sec += atoi(argv[2]); printf(main() about to call sem_timedwait()/n); while ((s = sem_timedwait(&sem, &ts)) == -1 && errno == eintr) continue; /* restart if interrupted by handler */ /* check what happened */ if (s == -1) { if (errno == etimedout) printf(sem_timedwait() timed out/n); else perror(sem_timedwait); } else { printf(sem_timedwait() succeeded/n); } exit((s == 0) ? exit_success : exit_failure);}
【实例 2】:
#include #include #include #include #include #include #include #include sem_t sem;void *func1(void *arg){ sem_wait(&sem); int *running = (int *)arg; printf(thread func1 running : %d/n, *running); pthread_exit(null);}void *func2(void *arg){ printf(thread func2 running./n); sem_post(&sem); pthread_exit(null);}int main(void){ int a = 3; sem_init(&sem, 0, 0); pthread_t thread_id[2]; pthread_create(&thread_id[0], null, func1, (void *)&a); printf(main thread running./n); sleep(10); pthread_create(&thread_id[1], null, func2, (void *)&a); printf(main thread still running./n); pthread_join(thread_id[0], null); pthread_join(thread_id[1], null); sem_destroy(&sem); return 0;}
有名信号量
有时候也叫命名信号量,之所以称为命名信号量,是因为它有一个名字、一个用户 id、一个组 id 和权限。这些是提供给不共享内存的那些进程使用命名信号量的接口。命名信号量的名字是一个遵守路径名构造规则的字符串。
接口函数
sem_open 函数
该函数用于创建或打开一个命名信号量,其原型如下:
sem_t *sem_open(const char *name, int oflag);sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数
name 是一个标识信号量的字符串。
oflag 用来确定是创建信号量还是连接已有的信号量。oflag 的参数可以为 0,o_creat 或 o_excl:如果为 0,表示打开一个已存在的信号量;如果为 o_creat,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回,此时 mode 和 value 都需要指定;如果为 o_creat|o_excl,表示如果信号量存在则返回错误。
mode 用于创建信号量时指定信号量的权限位,和 open 函数一样,包括:s_irusr、s_iwusr、s_irgrp、s_iwgrp、s_iroth、s_iwoth。
value 表示创建信号量时,信号量的初始值。
sem_close 函数
该函数用于关闭命名信号量:
int sem_close(sem_t *);
功能:单个程序可以用 sem_close 函数关闭命名信号量,但是这样做并不能将信号量从系统中删除,因为命名信号量在单个程序执行之外是具有持久性的。当进程调用 _exit、exit、exec 或从 main 返回时,进程打开的命名信号量同样会被关闭。
sem_unlink 函数功能:sem_unlink 函数用于在所有进程关闭了命名信号量之后,将信号量从系统中删除:
int sem_unlink(const char *name);
信号量操作函数与无名信号量一样。
使用实例
#include #include #include #include #include #include #include #include #include #define sem_name /sem_namesem_t *p_sem;void *testthread(void *ptr){ sem_wait(p_sem); sleep(2); pthread_exit(null);} int main(void){ int i = 0; pthread_t pid; int sem_val = 0; p_sem = sem_open(sem_name, o_creat, 0555, 5); if(p_sem == null) { printf(sem_open %s failed!/n, sem_name); sem_unlink(sem_name); return -1; } for(i = 0; i < 7; i++) { pthread_create(&pid, null, testthread, null); sleep(1); // pthread_join(pid, null); // not needed, or loop sem_getvalue(p_sem, &sem_val); printf(semaphore value : %d/n, sem_val); } sem_close(p_sem); sem_unlink(sem_name); return 0;}
命名和无名信号量的持续性
命名信号量是随内核持续的。当命名信号量创建后,即使当前没有进程打开某个信号量,它的值依然保持,直到内核重新自举或调用 sem_unlink()删除该信号量。
无名信号量的持续性要根据信号量在内存中的位置确定:
如果无名信号量是在单个进程内部的数据空间中,即信号量只能在进程内部的各个线程间共享,那么信号量是随进程的持续性,当进程终止时他也就消失了;
如果无名信号量位于不同进程的共享内存区,因此只要该共享内存区仍然存在,该信号量就会一直存在;所以此时无名信号量是随内核的持续性。
信号量 - 互斥量 - 条件变量
很多时候信号量、互斥量和条件变量都可以在某种应用中使用,那这三者的差异有哪些呢?下面列出了这三者之间的差异:
互斥量必须由给它上锁的线程解锁;而信号量不需要由等待它的线程进行挂出,可以在其他进程进行挂出操作;
互斥量要么被锁住,要么被解开,只有这两种状态;而信号量的值可以支持多个进程 / 线程成功的进行 wait 操作;
信号量的挂出操作总是被记住,因为信号量有一个计数值,挂出操作总会将该计数值加 1,然而当条件变量发送一个信号时,如果没有线程等待在条件变量,那么该信号就会丢失。
叉车防撞系统精确感知,应用场景详细解析
DRAMless并非永远代表低预算 还有这些设计须知
如果MacBook Pro设计成这个样子,会激起你的购买欲望嘛?
北醒联合创始人冯钰志:科技创业!是内心坚定又温柔的一场守护
麒麟9000s属于什么水平 麒麟9000s相当于台积电7nm
Linux信号量(2):POSIX 信号量
详解半导体分立器件脉冲测试的必要性及相关要求
北京大学发明新型三光子显微镜,实现深层活体三维脑成像的研究
苹果iPhoneXS和华为Mate20以及三星Note9哪个最好
巡线小车制作图解
视爵光旭勇立“智慧民航”新风口
嵌入式Linux:内核模块引用计数的实现(附源代码)
特斯拉发布2021年Q1财报:总营收103.89亿美元
新材料在线梳理了超具发展潜力的100种新材料
2020年台北电脑展暂不延期 举办时间依旧为6月2-6日
NASA高分辨率日冕成像仪任务数据发现细小热磁场线
一种基于FAHP和攻击树的信息系统安全评估方法
后备式ups主要优缺点
溶解氧监测水质智能型传感器的工作原理
对于电网储能中几种储能电池化学成分的评估