CSCI3150 T08 期中题与解答核验
课程:CSCI3150 Introduction to Operating Systems · 2025T2 本讲:T08 期中题与解答核验 · 原件 T08-期中讲解-2025T2-题目.pptx(本地课件) · PDF页码副本(本地课件) p.1–24 定位:以历史原题和参考答案练习推理,同时分清评分答案与程序在全部交错下的正确性。 前后:演练 L02、L13、L14、L15。
逐页伴读
p.1–3:这是一份历史期中讲评
p.1 Tutorial 8: Midterm Solution
CSCI3150 Introduction to Operating Systems, Spring 2026
p.3 All materials used in this tutorial:
https://github.com/henryhxu/CSCI3150/tree/2026-Spring/tutorial/T08
封面2026年3月12日,p.2的作业3截止3月29日属于旧学期。p.3平均分分别0.44、0.46、1.77、0.82,表中总平均写3.33;四项相加却是3.49,差0.16。没有原始成绩数据,不能自行决定哪一格正确。中位数也不可相加来恢复总中位数。
表(p.3):课件完整字段与状态。 下表保留原页术语和数值;读写轨迹的同一格按原页由上到下排列,空格以“—”表示。
| Question | 1 | 2 | 3 | 4 | Total |
|---|---|---|---|---|---|
| Avg | 0.44 | 0.46 | 1.77 | 0.82 | 3.33 |
| Median | 0.5 | 0.5 | 2 | 1 | 3.5 |
| Max | 1 | 1 | 2 | 1 | 5 |
| SD | 0.38 | 0.43 | 0.47 | 0.33 | 1.23 |
p.4–6:旧评分政策与第一题题面
p.4 Overall Assessment
4 assignments (50%)
5 ureply quizzes (5%) + midterm (5%)
Final (40%)
Midterm serves as a wake-up quiz
Want to do better?
Try bonus question in assignments
Don’t miss the following ureply quizzes in tutorial
Prepare better for final exam
p.5 Policy
Only accept appeals for incorrect judgement(The correct answer was accidentally judged as wrong)
Time limit: 23:59:59 p.m., Fri, Mar 13
Contact TAs
DENG Yangtao & FENG Yicheng
p.4的作业50%、五次quiz5%加期中5%、期末40%仅适用于该历史版本;p.5申诉截止3月13日同样已过。另读了期中题目与解答PDF(本地课件):文件实际4页,封面自称5页,以下按实际PDF页号引用。
Q1(a)(试卷p.1,Tutorial p.6):多选,选出关于系统调用与进程的正确陈述。A用户系统调用需要user→kernel模式转换;B成功exec后仍可访问原栈变量;C成功fork子进程从main第一条重新开始;D用dup2把stdout重定向到文件后再也不能向stdout打印。参考答案A。
p.7–9:A正确,B和C各错在一个概念
p.7 A:
correct. When a user process makes a system call, it requests services from the operating system’s kernel. To execute these privileged operations, the CPU must switch from user mode to kernel mode. This is a fundamental mechanism of operating systems.
p.8 B:
exec() replaces the entire address space of the current process with a new program, including the stack, heap, and data segments. None of the original variables are accessible anymore.
p.9 C:
After fork() , the child process continues execution from the point where fork() returns, not from the beginning of main() .
A描述真实系统调用进入内核的基本机制。B在题目成功exec前提下错误,新程序替换原映像,原栈对象不继续存在;若exec失败返回则不能套这结论。C错误,fork父子从返回点继续,只是返回值不同,子0、父正PID。
p.10–11:重定向保留stdout角色,线程有独立栈指针
p.10 D:
After redirection, the process can still "print" to standard output—the output just goes to the file instead of the terminal.
p.11 Explanation
A: The heap is a region of a process’s memory used for dynamic memory allocation. It is accessible by all threads of the same process, facilitating data sharing and communication within the process.
B: File descriptors are handles used by a process to access files or I/O resources. In a multi-threaded process, all threads share the same set of file descriptors.
C: The stack is used to store a thread’s execution history, including function calls, return addresses, and local variables. The stack pointer points to the top of the current thread’s stack.
D: Global variables are stored in the process’s data segment.
(You can also refer to page 7-8 in lec4-threads)
D错误:stdout仍是fd1,重定向只是让它指向文件,所以还能打印,文字目的地改变。
Q1(b)完整选项(试卷p.1):同进程两线程不共享哪项?A Heap;B File descriptors;C Stack pointer;D Global variables。答案C。堆、fd表、全局对象属于共享进程资源;栈指针属于线程执行状态,切换时分别保存。不要把“栈指针不共享”误推成“通过指针绝无可能访问别的线程栈对象”。
p.12–13:第二题先写fork返回规则
p.12 A quick review of fork():
The fork() creates a new child process, by duplicating the parent process.
Child Process Start Point: After a successful fork(), both the parent and child processes continue execution independently from the point immediately following the fork() call.
p.13 The fork() function returns different values to the parent and child processes
In the Parent Process: Returns the Process ID (PID) of the newly created child process (a positive integer).
In the Child Process: Returns 0.
On Failure: Returns -1 in the parent process.
图p.12父子都从fork之后继续;p.13分成功父返回正PID、子返回0、失败返回-1。本题答案隐含fork全部成功,并按每次printf调用计数;如果改变输出缓冲环境,还需另行讨论fork复制未刷新的stdio缓冲,不能从题目模型泛化所有运行方式。
p.14–15:Q2完整程序与进程树
p.15 parent
i=0:
parent, child#1(pid=0) -> #
i=1:
parent, child#1 -> !!
i=2:
parent, child#1, child#2(pid=0), child#3(pid=0), -> ##
i=3:
parent, child#1, child#2, child#3, -> !!!!
试卷p.1–2原题核心程序如下,保留其 void main 教学写法;实际标准C入口应为int main。问题(a)包含Shell新建的初始程序进程共有多少;(b)#和!各输出多少。
#include <stdio.h>
#include <unistd.h>
void main() {
int i;
int pid;
for (i=0; i<4; i++) {
if (i % 2 == 0) {
pid = fork();
if (pid == 0) printf("#\n");
} else {
printf("!\n");
}
}
return;
}
| i | 开始进程数 | 动作 | 结束进程数 | 本轮输出 |
|---|---|---|---|---|
| 0 | 1 | 唯一进程fork | 2 | 新子打印#一次 |
| 1 | 2 | 都不fork | 2 | !两次 |
| 2 | 2 | 两进程各fork | 4 | 新子各打印#,两次 |
| 3 | 4 | 都不fork | 4 | !四次 |
故共4个程序进程,其中3个新子;#为1+2=3,!为2+4=6。课件列举的九行不是唯一调度输出顺序,因不同进程可交错;计数相同不表示全局顺序固定。
p.16–18:Q3题面与同步接口
课件程序与接口(p.16,原页片段)
Question 3: Imagine you run a restaurant specializing in shawarma, a popular street food across the Arab world. You have four workers: a meat supplier, a bread supplier, and two producers who make the shawarma.
The meat supplier only delivers meat when the rotisserie is completely empty, after which the meat volume reaches its maximum capacity. The bread supplier continuously provides bread one by one until a specified limit is reached. Each producer requires one portion of meat and two pieces of bread to make a shawarma.
You are required to fill in the **TODOs [1] - [6]** in the following simulating code using the **Options (A - J)** provided below. **Each option can be used once, multiple times, or not at all.** Write down the **Letters (A - J)** as your answer.
Hint -- condition variable related POSIX APIs that may be used:
int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex); -- fall asleep
int pthread_cond_signal(pthread_cond_t *cv); -- wake up other thread(s)
p.17 Purpose: wait for a condition variable to be signaled, releasing the associated mutex.
课件程序与接口(p.17,原页片段)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
p.17 Parameters:
cond: pointer to the condition variable.
mutex: pointer to an associated mutex (must be locked before calling).
课件程序与接口(p.17,原页片段)
pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
p.17 use while loops for pthread_cond_wait to handle spurious wakeups.
p.18 Purpose: wake up one (or more) thread(s) waiting on a condition variable.
课件程序与接口(p.18,原页片段)
int pthread_cond_signal(pthread_cond_t *cond);
课件程序与接口(p.18,原页片段)
pthread_mutex_lock(&mutex);
// Change the condition
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
题面:肉供应者、面包供应者、两个制作人。肉仅完全用尽时补满MAX_MEAT=5;面包逐个补到MAX_BREAD=10;一份shawarma耗1肉、2面包。要求从A–J选项填六个TODO,可重复使用。试卷p.2–3给代码与答案,但未保留A–J完整选项列表,不能伪造其余选项。
初始meat=bread=shawarma=0,所有访问共用mutex,另有cond_meat和cond_bread。肉供应者的if条件为TODO1,补满后的通知为TODO2;面包if条件TODO3、加一后的通知TODO4;producer两个循环分别是 while(meat<1){TODO5;} 与 while(bread<2){TODO6;},随后meat--、bread-=2、shawarma++。p.17–18提醒wait释放并重新取得mutex,signal通知等待者。
p.19:Q3原答案与跨等待的缺陷
课件程序与接口(p.19,原页片段)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_MEAT 5
#define MAX_BREAD 10
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_meat = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_bread = PTHREAD_COND_INITIALIZER;
int meat = 0;
int bread = 0;
int shawarma = 0;
void* meat_supplier(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
if (meat == 0) {
meat = MAX_MEAT;
printf("Meat supplier refilled the meat.\n");
pthread_cond_signal(&cond_meat);
}
pthread_mutex_unlock(&mutex);
}
return NULL;
}
课件程序与接口(p.19,原页片段)
void* bread_supplier(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
if (bread < MAX_BREAD) {
bread++;
printf("Bread supplier added a bread. Total bread: %d\n", bread);
pthread_cond_signal(&cond_bread); // Notify producers
}
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void* producer(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
while (meat < 1) {
pthread_cond_wait(&cond_meat, &mutex); // Wait for meat
}
while (bread < 2) {
pthread_cond_wait(&cond_bread, &mutex); // Wait for bread
}
// Produce one shawarma
meat--;
bread -= 2;
printf("Producer made a shawarma.\n");
shawarma++;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
课件程序与接口(p.19,原页片段)
refill if empty;
else do nothing
课件程序与接口(p.19,原页片段)
add bread if not full;
else do nothing
参考答案字母依次 A、E、D、F、G、H,对应可见代码:
| TODO | 原答案含义 |
|---|---|
| 1 | meat == 0 |
| 2 | pthread_cond_signal(&cond_meat) |
| 3 | bread < MAX_BREAD |
| 4 | pthread_cond_signal(&cond_bread) |
| 5 | pthread_cond_wait(&cond_meat,&mutex) |
| 6 | pthread_cond_wait(&cond_bread,&mutex) |
这与原评分键一致,但两个独立while不足以保证两个制作人在所有交错下安全。代码审查反例(补充):可达状态meat=1、bread=0。P1通过肉检查,因缺面包释放mutex等待;供应者逐次补面包,P2抢先拿到2片并消费最后1肉,使meat=0;在肉供应者尚未补货前再供应2片面包,P1被唤醒、重新得锁,跳出面包循环后不再检查肉,执行meat--得到-1。
修复原则是在真正扣减前重新验证组合谓词 meat>=1 && bread>=2,任一等待释放锁后回到组合检查,并设计与资源变化匹配的通知机制。这里的反例是对程序正确性的分析,不冒充官方更正,也不据此擅自改原题评分字母。
补充:把Q3反例拆成可逐行检查的状态。 两个while各自正确,不代表两者的结论能跨越一次wait一起保留。下面从原程序可达的肉1、面包0状态开始;表中操作均在同一mutex保护下,wait会释放它。
| 步 | 动作 | meat | bread | P1掌握的信息 |
|---|---|---|---|---|
| 1 | P1通过肉检查,因缺面包wait | 1 | 0 | 只知道过去某时刻肉够 |
| 2 | 供应者补两片面包 | 1 | 2 | P1尚未重得锁 |
| 3 | P2抢先制作一份 | 0 | 0 | 旧肉结论已失效 |
| 4 | 面包供应者再补两片,肉供应者尚未运行 | 0 | 2 | P1可因面包通知醒来 |
| 5 | P1只重查bread,随后扣肉 | −1 | 0 | 发生资源不变量违反 |
不是mutex失效,而是P1主动wait期间允许别人合法改变meat。正确判据应在最终扣减前同时成立;每当释放过锁,此前依赖共享状态的判断都要重新确认。参考填空字母仍按来源保留,反例指出的是完整程序的额外缺陷,不能把两种证据混在一起。
补充:为什么在同一把锁下检查过,还需要重查。 锁的保证只覆盖连续持有它的区间。P1 对 meat 的观察发生在第一次持锁区间;随后 wait 因 bread 不足而释放锁,其他制作人就能在中间改变 meat。重新得到同一把锁只意味着现在又有互斥权,不意味着共享变量恢复成上一次放锁前的值。
因此正确性条件必须贴在实际消费动作之前:此时同时满足肉与面包的需求,才能把它们一起扣减。把两个 while 机械地看成两个“已经完成的证明”,会漏掉它们之间存在释放锁的路径。本节的修复原则是组合条件与通知协议,不是简单把第二个 while 改名,也不是删掉等待以求不再释放锁。原参考答案仍按来源保留,这段解释只明确其并发适用边界。
p.20–22:Q4完整资源约束
课件程序与接口(p.20,原页片段)
In your shawarma restaurant, customers must have a seat and then receive service from a waiter before they can begin ordering. When the order is taken, the waiter leaves. When the meal is finished, the customer leaves the seat. This process can be simulated in C syntax (All the necessary header files have already been included).
You are required to fill in the TODOs in the code. Each TODO only need one line of code.
Hint -- semaphore related POSIX APIs that may be used:
int sem_wait(sem_t *);
int sem_post(sem_t *);
p.21 sem_wait()
If the value of the semaphore was one or higher when called sem_wait(), return right away.
It will cause the caller to suspend execution waiting for a subsequent post.
When negative, the thread will be blocked
课件程序与接口(p.21,原页片段)
1 int sem_wait(sem_t *s) {
2 decrement the value of semaphore s by one
3 wait if value of semaphore s is negative
4 }
p.22 sem_post()
Simply increments the value of the semaphore.
If there is a thread waiting to be woken, wakes one of them up.
课件程序与接口(p.22,原页片段)
1 int sem_post(sem_t *s) {
2 increment the value of semaphore s by one
3 if there are one or more threads waiting, wake one
4 }
题面与试卷p.3–4一致:6座位、4服务员、12顾客;顾客先得座位,再得服务员才能order;点单完成服务员离开,eat结束顾客释放座位。两个信号量初值seats=6、waiters=4。代码四个TODO位置依次在order前两行、order后eat前、eat后。wait获取一个许可,不足阻塞;post归还一个许可。
p.23–24:Q4答案与容量边界
课件程序与接口(p.23,原页片段)
#define NUM_SEATS 6 // Number of available seats
#define NUM_WAITERS 4 // Number of available waiters
#define NUM_CUSTOMERS 12 // Number of customers
sem_t seats; // Semaphore for available seats
sem_t waiters; // Semaphore for available waiters
void* customer(void* type) {
sem_wait(&seats);
sem_wait(&waiters);
order(); // The customer is ordering.
sem_post(&waiters);
eat(); // The customer finishes eating.
sem_post(&seats);
pthread_exit(NULL);
}
int main() {
pthread_t customers[NUM_CUSTOMERS];
sem_init(&seats, 0, NUM_SEATS);
sem_init(&waiters, 0, NUM_WAITERS);
for (int i = 0; i < NUM_CUSTOMERS; i++) {
pthread_create(&customers[i], NULL, customer, &customer_types[i]);
}
/* ... */
}
p.23 Requirements: customers must have a seat and then receive service from a waiter before they can begin ordering. When the order is taken, the waiter leaves. When the meal is finished, the customer leaves the seat.
Try to get a seat via wait()
Try to get a waiter via wait()
Order()
Release the waiter via post()
Eat()
Release the seat via post()
四行依次为 sem_wait(&seats);、sem_wait(&waiters);、sem_post(&waiters);、sem_post(&seats);。同一时刻占座不超过6,接受点单服务不超过4,吃饭者也占座但不占服务员。前六人可占满座位,其中最多四人点单、其余等服务员,余下六人等座位;已有顾客点完会释放服务员,故这个获取顺序自身未形成资源环。
图中的编译错误提醒不能对sem_t直接做 seats--,必须走同步API。p.24为Q&A。本篇A类证据仅证明这些题型出现在历史Spring2026期中,不证明当期试卷安排。
自测:Q4若顾客吃完才释放服务员,座位/服务员计数仍不超限,为什么仍不符合原题?
安全容量没超,不表示协议语义正确。原题规定服务员点单结束就离开;吃饭期间继续占用服务员,会无谓降低其他顾客点单机会,也改变了资源寿命。检查程序至少有三层:是否违反容量、是否可能卡死、是否在题目规定的时刻获取和释放。只检查信号量初值不能覆盖后两层。
本讲核心考点
- [A-历史期中Q1] 系统调用、exec、fork、重定向与线程共享资源判断。
- [A-历史期中Q2] 按循环轮次画fork进程树并计输出。
- [A-历史期中Q3] 根据资源谓词选择CV等待和通知;官方填空答案与完整正确性分别核验。
- [A-历史期中Q4] 资源计数信号量的获取与释放位置。
附录
本篇已在对应页逐题列出完整可取得题面、条件、推导与结果;Q3缺失的A–J完整选项表保留为来源缺项。历史试卷页数标注与成绩均分不一致也已原位注明。
来源与证据
-
原件:T08-期中讲解-2025T2-题目.pptx(本地课件);PDF副本(本地课件) p.1–24。本文页码均为 PDF 页号。
-
考试证据:读了 课程大纲-2026T1.pdf、教学计划-2026T1.pdf、T08-期中参考-2025T2-题目-解答.pdf、T07-多级反馈队列-2025T2.pdf(p.13–15内嵌历史试题);未识别用途的 PDF:无
-
2026-10-13 内容补全:保留原页顺序与版本,补入对应页的程序、接口、字段、条件和演算;原程序片段保留课件行号、拼写与省略处,不能据此视为经过编译的完整软件。本文已有运行记录属于其标注日期,本次未重新运行课程容器或提交作业。