CSCI3150 T06 进程间信号量
课程:CSCI3150 Introduction to Operating Systems · 2025T2 本讲:T06 进程间信号量 · 原件 T06-信号量-2025T2.pptx(本地课件) · PDF页码副本(本地课件) p.1–21 定位:区分有名与无名对象、句柄关闭与名称删除,理解跨进程共享。 前后:前接 T05;后接 T07。
逐页伴读
p.1–3:历史作业与本次主题
p.1 Semaphores in C
CSCI3150 Introduction to Operating Systems, Spring 2026
p.2 Assignment 2
Invitation URL: https://classroom.github.com/a/RKvPmvxf
Deadline: 23:59:59 p.m., Sun, Mar 1st, 2026
Submission: Github classroom (refer to the Assignment 1 guide: https://piazza.com/class/mjxsr09gf91dw/post/11)
p.3 Assignment 2
For students who have already accepted asg2, please run git pull to update
封面2026年2月11日,p.2–3仍提醒旧作业截止及版本更新,不是当期布置。
p.4–6:API按对象种类成组使用
p.4 Assignment 2
If you can't access asg2 via this URL, please try the solution also mentioned in asg1:
p.6 The semaphore.h is a powerful tool for creating and managing semaphores in C. It defines the sem_t type, used in performing semaphore operations. See also https://pubs.opengroup.org/onlinepubs/7908799/xsh/semaphore.h.html
课件程序与接口(p.6,原页片段)
To use it: #include <semaphore.h>
Interfaces:
int sem_close(sem_t *);
int sem_destroy(sem_t *);
int sem_getvalue(sem_t *, int *);
int sem_init(sem_t *, int, unsigned int); ✔
sem_t *sem_open(const char *, int, ...);
int sem_post(sem_t *); ✔
int sem_trywait(sem_t *);
int sem_unlink(const char *);
int sem_wait(sem_t *); ✔
p.4是历史邀请问题处理,p.5–6转入semaphore.h。核心配对是无名sem_init/sem_destroy、有名sem_open/sem_close/sem_unlink;wait/post操作许可。不能因为参数都带sem就混用销毁接口。
p.7–9:共享标志与八进制权限
课件程序与接口(p.7,原页片段)
int sem_init(sem_t *sem, int pshared, unsigned int value);
p.7 Parameters:
sem: pointer to the semaphore variable.
pshared: whether the semaphore is shared between processes (0 for no sharing).
value: semaphore initial value.
课件程序与接口(p.7,原页片段)
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
sem_t empty;
sem_init(&empty, 0, BUFFER_SIZE);
p.8 Purpose: initialize and open a named semaphore (inter-process).
课件程序与接口(p.8,原页片段)
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
课件伪代码(p.8,原页片段)
Parameters:
name: must start with / (e.g. /mysemaphore)
oflag: 0, O_CREAT, O_CREAT | O_EXCL.
mode: permission bits.
Permission bits control access to files or objects and are represented as a three- or four-digit number. Each digit represents permissions for the owner, group, and others, respectively.
Common values include:
4: Read (r)
2: Write (w)
1: Execute (x)
These values can be combined, such as 6 for read and write (rw-).
The first digit (e.g., 0) can set special permissions like SUID or SGID.
value: initial value.
课件程序与接口(p.9,原页片段)
sem_t *sem;
const char *sem_name = "/mysemaphore";
sem = sem_open(sem_name, O_CREAT | O_EXCL, 0644, 1);
课件伪代码(p.9,原页片段)
O_CREAT | O_EXCL: If the semaphore already exists, the function call fails.
0644:
6: The owner (the user who created the semaphore) has read and write permissions (4 + 2 = 6, i.e. rw-).
4: The group user (other users who belong to the same user group as the creator) has only read permission (4 = 4, i.e. r--).
4: Other users (other users who do not belong to the same group) have only read permission (4 = 4, i.e. r--).
无名 sem_init(&s,0,N)供同进程线程使用;跨进程无名信号量不仅要pshared非零,对象本身还必须位于共享内存。fork后普通全局变量的独立副本不满足这个条件。
有名例 sem_open("/mysemaphore",O_CREAT|O_EXCL,0644,1)要求名称尚不存在。0644是C八进制字面量:owner读写6,group和others读4;最前的0在这里表示数制,不是“设置SUID的第一位”。初始值1只在创建新对象时有作用,打开已有对象不重新初始化。
权限数字逐位读:原例 0644 中,C 代码前导 0 表示八进制字面量;后三位分别是所有者的 6=4+2(读写)、组的 4(读)、其他人的 4(读)。不能把这个前导 0 直接解释为“设置了 SUID/SGID”;原页对特殊权限的说法只应理解为更高的权限位另有用途。O_CREAT | O_EXCL 要求创建一个以前不存在的名字,已有同名对象时失败;只用 O_CREAT 则可以打开已存在对象,此时 value 不会重置其计数。
p.10–12:非阻塞尝试与正确关闭参数
p.10 Purpose: it is useful in cases where you don't want the process or thread to block while waiting for the semaphore.
sem_trywait is a non-blocking function that attempts to decrease the value of a semaphore. If the semaphore’s value is greater than 0, it decrements the value and proceeds. However, if the semaphore's value is 0 (unavailable), it returns immediately with a failure and sets errno to EAGAIN.
课件程序与接口(p.10,原页片段)
int sem_trywait(sem_t *sem);
课件程序与接口(p.10,原页片段)
if (sem_trywait(&sem) == 0) {
printf("Semaphore acquired!\n");
} else {
if (errno == EAGAIN) {
printf("Semaphore unavailable, do other things.\n");
}
}
p.11 Purpose: used to destroy the unnamed semaphore.
Only a semaphore that was created using sem_init() may be destroyed using sem_destroy().
课件程序与接口(p.11,原页片段)
int sem_destroy(sem_t *sem);
课件程序与接口(p.11,原页片段)
sem_destroy(&sem);
p.12 Purpose: used to indicate that the calling process is finished using the named semaphore.
You cannot call sem_close() for an unnamed semaphore (one created by sem_init()).
课件程序与接口(p.12,原页片段)
int sem_close(sem_t *sem);
课件程序与接口(p.12,原页片段)
sem_close(&sem);
trywait无许可时立即失败并置errno=EAGAIN;调用失败也可能有其他原因,不能全部当“稍后重试”。sem_destroy对应无名对象。sem_open返回的是 sem_t *sem,则应 sem_close(sem),p.12写 sem_close(&sem)会多一层指针;只有sem_t实体传地址才用&。
p.13–15:unlink删除名字,不强行作废已打开引用
p.13 Purpose: remove the named semaphore.
If one or more processes have the semaphore open when sem_unlink() is called, destruction of the semaphore is postponed until all references to the semaphore have been closed by calls to sem_close(), or the process exits.
课件程序与接口(p.13,原页片段)
int sem_unlink(const char *name);
课件程序与接口(p.13,原页片段)
sem_unlink(“/sem”);
p.14 Example: Semaphores in Process Synchronization
p.14 Problem: How to use <semaphore.h> to implement inter-process semaphores?
Background:
Threads share the same memory space, allowing direct access to semaphore variables of other threads.
However, processes have separate memory spaces. A process cannot access the semaphore variables of other processes.
Solution:
Named semaphores: use sem_open() to create or open a named semaphore.
Unnamed semaphore: the semaphore needs to be placed in shared memory, and the pshared parameter of sem_init() is set to non-zero (usually 1).
课件程序与接口(p.15,原页片段)
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
// Create a semaphore
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);
sem_wait(sem);
printf("Process A enters the critical section.\n");
sleep(5);
printf("Process A leaves the critical section.\n");
sem_post(sem);
sem_close(sem);
return 0;
}
课件程序与接口(p.15,原页片段)
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
// Open the semaphore created in Process A
sem_t *sem = sem_open("/mysem", 0);
sem_wait(sem);
printf("Process B enters the critical section.\n");
sleep(5);
printf("Process B leaves the critical section.\n");
sem_post(sem);
sem_close(sem);
return 0;
}
p.15 Tip: We can use ./a & ./b to run a and b concurrently.
However, & is not a binary operator. It is a unary postfix operator (i.e. one coming after a command) and instructing the shell to run the specified command in the background.
E.g. ./a & ./b means that a runs from the background, while b runs from foreground.
sem_unlink使名称不再用于新的打开,已有引用可继续使用,到最后引用关闭才释放对象。原a创建/mysem并进入临界区,b按名打开同一对象,两者各wait/post实现跨进程互斥。
./a & ./b只让a后台启动,不保证a已创建完再执行b的open;因此示例需要处理启动顺序或打开失败,而不是假定并发启动自动同步。所有sem_open应检查SEM_FAILED。
进程间共享还要满足存储条件:对无名信号量,把 pshared 改成非零只是声明使用方式;sem_t 对象本身仍必须位于进程共同可见的共享存储。两个进程各在私有栈里声明一个同名 C 变量,再分别初始化,并不会得到同一个信号量。有名信号量则由名称查找同一系统对象,所以 A 的 sem_open("/mysem", O_CREAT, 0644, 1) 与 B 的 sem_open("/mysem",0) 能取得对应句柄。B 若抢先于创建动作运行,打开可能失败;原例 ./a & ./b 只安排并发,不能保证 A 的创建已发生。
p.16–18:重新编译不重置系统中的有名对象
课件程序与接口(p.17,原页片段)
But what if we change the initial value of the semaphore to 2?We want both process A and B to enter the critical section.
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 2);
Let’s change the value and re-compile it.
Still only one process enters critical section. Our change does not work.
p.18 Why didn't our changes take effect?
Because even though our program ends, the named semaphore is still exist in memory.
Where it is? /dev/shm. If you do not delete it, it persists until shutdown.
Thus, we need to add sem_unlink() to remove named semaphores in our code.
原实验把初值1改2再编译,却仍只有一个许可,因为旧名字对应对象还在,O_CREAT打开它时忽略新的初值。程序结束或sem_close释放的是本进程引用,不等于unlink名字。p.18的/dev/shm位置是Linux实现线索,不是跨平台API保证。
补充:同一个名字也可能先后指向两个不同对象。 设进程 A 已打开一个有名信号量;删除该名字后,A 仍可能持有旧对象的有效引用。若进程 B 随后用相同名字创建新对象,B 打开的是新的那一份。两进程手里名字看似相同,所操作的许可计数却可能已互不相关。
所以清理旧名字不能只看“下一次创建成功了”。需要协调所有参与者:谁负责创建、什么时候允许打开、谁确认旧使用者全部退出、谁最后删除名称。否则为修复旧初值而随意 unlink,可能把一个本来共享的同步关系拆成两套。这里给的是对象生命周期推演,不是在本机创建或删除任何信号量。
p.19–21:清理也需要所有使用者协调
课件程序与接口(p.19,原页片段)
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
// Create a semaphore
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);
sem_wait(sem);
printf("Process A enters the critical section.\n");
sleep(5);
printf("Process A leaves the critical section.\n");
sem_post(sem);
sem_close(sem);
sem_unlink(/mysem");
return 0;
}
p.20 You can also use the unnamed semaphore to implement inter-process semaphores.
But it is harder than using named semaphore and beyond the scope of this course.
If you are interested in operating system, you can try it.
p.19补sem_unlink,但显示的字符串少了开头引号,应为 sem_unlink("/mysem")。若a在b打开之前就unlink,b仍会失败;应明确创建者、所有参与者已打开的同步点及清理责任,不能用末尾一行掩盖生命周期竞态。
p.20说明共享内存内的无名跨进程用法超出该Tutorial实现范围,本篇保留这个边界;p.21是结束页。wait/post的许可语义回看L15。
按名称、句柄、对象三层理解清理(补充)
可以把名字 /mysem 看作公开查找入口,sem_open返回的指针是当前进程访问对象的句柄,内核维护的许可状态是实际对象。sem_close关闭本进程的句柄;sem_unlink撤去查找名字;二者都不等于“向许可计数post一次”。
假设A先创建初值1的对象,B成功打开同一名字;A unlink后,A/B原有句柄仍可操作原对象。若C随后用同名O_CREAT重新创建,C可能取得一个全新的对象,即使字符串名字一样,它也不会与A/B形成共同互斥。这就是过早unlink然后重建可能让两组进程各自以为持有“同一把锁”的原因。生命周期协议必须包含“谁创建、何时参与者都已打开、谁撤名”,不能只看结尾有没有清理函数。
自测:在fork前对普通全局sem调用sem_init且pshared非零,是否自动变成跨进程共享?
没有。pshared声明预期用途,不负责把普通私有内存变成共享映射。两个进程必须访问同一份位于共享内存的信号量对象;否则只是操作地址数值看似相同的副本。有名信号量则通过共同名字取得同一对象,但仍需正确的创建/打开次序。
本讲核心考点
- [C] 区分有名与无名信号量的创建及清理接口。
- [C] 解释跨进程无名对象为什么还要共享内存。
- [C] 推断修改sem_open初值却无效果的原因。
- [C] 检查并发启动与unlink之间的生命周期竞态。
附录
本份没有需要移至附录的长推导;计算过程已在对应页正文写全。
来源与证据
-
原件:T06-信号量-2025T2.pptx(本地课件);PDF副本(本地课件) p.1–21。本文页码均为 PDF 页号。
-
考试证据:读了 课程大纲-2026T1.pdf、教学计划-2026T1.pdf、T08-期中参考-2025T2-题目-解答.pdf、T07-多级反馈队列-2025T2.pdf(p.13–15内嵌历史试题);未识别用途的 PDF:无
-
2026-10-13 内容补全:保留原页顺序与版本,补入对应页的程序、接口、字段、条件和演算;原程序片段保留课件行号、拼写与省略处,不能据此视为经过编译的完整软件。本文已有运行记录属于其标注日期,本次未重新运行课程容器或提交作业。