文章目录
- 一、引言
- 二、进程的同步与互斥
- 2.1 进程合作
- 2.2 共享资源
- 2.3 临界区与临界资源
- 2.4 同步机构设计准则
- 三、互斥的软件方法
- 四、硬件指令机制
- 五、信号量机制
- 5.1 整型信号量
- 5.2 结构型信号量
- 5.3 用信号量实现互斥
- 5.4 用信号量实现同步
- 六、经典进程同步问题
- 6.1 生产者—消费者问题
- 6.2 哲学家就餐问题
- 6.3 读者—写者问题
- 七、进程通信
- 7.1 共享存储区系统
- 7.2 管道通信系统
- 7.3 消息传递系统
- 7.4 客户机 - 服务器系统
一、引言
现代操作系统通过进程管理实现多道程序并发执行,但并发执行的进程可能因为合作或竞争资源而需要同步。
二、进程的同步与互斥
2.1 进程合作
- 合作进程需要协调运行以完成任务。例如,一个进程可能负责计算,另一个进程则负责输入输出。
代码示例:
// 进程 P1 和 P2 合作计算 X = fun1(y) * fun2(z)
// P1 计算 fun1(y)
int X, y, z;
X = fun1(y);// P2 计算 fun2(z)
int result = fun2(z);
X *= result;// 注释: fun1 和 fun2 是需要实现的函数,用于计算表达式的结果
2.2 共享资源
- 并发进程可能因竞争独占性资源而相互制约,如打印机。
代码示例:
int x = 1; // x 表示打印机的空闲数量void P1() {while (1) {x = x - 1;if (x < 0) { // 如果请求打印机时它正忙,则等待// 等待打印机变为空闲的逻辑代码}// 使用打印机的逻辑代码x = x + 1;}
}void P2() {while (1) {x = x - 1;if (x < 0) { // 如果请求打印机时它正忙,则等待// 等待打印机变为空闲的逻辑代码}// 使用打印机的逻辑代码x = x + 1;}
}
// 注释: 此代码示例展示了两个进程如何通过一个共享变量来控制对打印机的访问
2.3 临界区与临界资源
- 临界资源一次仅允许一个进程使用,需要通过进入和退出临界区来控制。
代码示例:
int resource = 1; // 表示临界资源是否可用void critical_section() {while (resource) {// 循环等待资源变为空闲}resource = 0; // 占用资源// 访问临界资源的代码resource = 1; // 释放资源
}
// 注释: 此示例代码展示了如何使用一个简单的忙等待机制来实现对临界资源的访问控制
2.4 同步机构设计准则
- 包括空闲让进、忙则等待、有限等待和让权等待。
三、互斥的软件方法
- 早期通过软件方法实现临界区互斥访问。
代码示例:
int turn = 0; // 令牌变量,用于控制两个进程的互斥访问void P0() {while (1) {while (turn != 0) {// 等待获取令牌}// 进入临界区critical_section();turn = 1; // 退出临界区,通知下一个进程可以进入remainder_section();}
}void P1() {while (1) {while (turn != 1) {// 等待获取令牌}// 进入临界区critical_section();turn = 0; // 退出临界区,通知下一个进程可以进入remainder_section();}
}
// 注释: 此代码示例展示了使用令牌机制来实现两个进程之间的互斥
四、硬件指令机制
- 利用硬件指令解决临界区问题。
代码示例:
int lock = 0; // 锁变量,用于控制对临界区的访问void P1() {while (1) {while (TS(lock)) {// 测试并设置锁,如果锁已经被设置,则忙等待}// 进入临界区critical_section();lock = 0; // 退出临界区,释放锁remainder_section();}
}void P2() {while (1) {while (TS(lock)) {// 测试并设置锁,如果锁已经被设置,则忙等待}// 进入临界区critical_section();lock = 0; // 退出临界区,释放锁remainder_section();}
}
// 注释: TS是测试并设置的硬件指令,此代码示例展示了如何使用硬件指令来实现互斥
五、信号量机制
- 信号量用于实现进程同步。
5.1 整型信号量
- 使用整型变量表示信号量。
代码示例:
Semaphore s = 1;void wait(Semaphore s) {while (s <= 0) {// 忙等待,直到信号量大于0}s = s - 1; // 信号量减1
}void signal(Semaphore s) {s = s + 1; // 信号量加1
}
// 注释: 此代码示例展示了整型信号量的wait和signal操作
5.2 结构型信号量
- 不存在忙等待问题的进程同步机制。
代码示例:
Semaphore s = {.value = 1, .L = NULL};void wait(Semaphore s) {s.value--;if (s.value < 0) {// 如果信号量小于0,则进程进入等待队列block(s.L);}
}void signal(Semaphore s) {s.value++;if (s.value <= 0) {// 如果信号量小于等于0,则唤醒等待队列中的一个进程wakeup(s.L);}
}
// 注释: 此代码示例展示了结构型信号量的wait和signal操作,包括阻塞和唤醒进程
5.3 用信号量实现互斥
- 使用信号量实现进程互斥。
代码示例:
Semaphore mutex = 1;void P1() {while (1) {wait(mutex); // 请求互斥信号量// 进入临界区critical_section();signal(mutex); // 释放互斥信号量// 退出临界区remainder_section();}
}void P2() {while (1) {wait(mutex); // 请求互斥信号量// 进入临界区critical_section();signal(mutex); // 释放互斥信号量// 退出临界区remainder_section();}
}
// 注释: 此代码示例展示了如何使用信号量来实现两个进程之间的互斥
5.4 用信号量实现同步
- 解决进程间的同步问题。
代码示例:
Semaphore plate = 1, apple = 0, orange = 0;void father() {while (1) {prepare_an_apple();wait(plate); // 请求盘子信号量put_an_apple_in_the_plate();signal(apple); // 释放苹果信号量}
}void mother() {while (1) {prepare_an_orange();wait(plate); // 请求盘子信号量put_an_orange_in_the_plate();signal(orange); // 释放橘子信号量}
}void son() {while (1) {wait(orange); // 请求橘子信号量get_an_orange_from_the_plate();signal(plate); // 释放盘子信号量eat();}
}void daughter() {while (1) {wait(apple); // 请求苹果信号量get_an_apple_from_the_plate();signal(plate); // 释放盘子信号量eat();}
}
// 注释: 此代码示例展示了如何使用信号量来解决生产者和消费者之间的同步问题
六、经典进程同步问题
- 解决进程同步的经典问题。
6.1 生产者—消费者问题
- 生产者与消费者通过缓冲区共享数据。
代码示例:
Semaphore empty = n, full = 0, mutex = 1;
Product buffer[n];void producer() {while (1) {produce_a_new_product();wait(empty); // 等待空的缓冲区wait(mutex); // 请求互斥信号量buffer[in] = product;in = (in + 1) % n;signal(mutex); // 释放互斥信号量signal(full); // 释放产品信号量}
}void consumer() {while (1) {wait(full); // 等待产品wait(mutex); // 请求互斥信号量product = buffer[out];out = (out + 1) % n;signal(mutex); // 释放互斥信号量signal(empty); // 释放空缓冲区信号量consume_product();}
}
// 注释: 此代码示例展示了生产
6.2 哲学家就餐问题
- 多个哲学家围绕圆桌就餐。
代码示例:
Semaphore fork[5];void philosopher(int i) {while (1) {think();wait(fork[i]); // 拿起左边的叉子wait(fork[(i+1) % 5]); // 拿起右边的叉子eat();signal(fork[i]); // 放下左边的叉子signal(fork[(i+1) % 5]); // 放下右边的叉子}
}
// 注释: 此代码示例展示了如何使用信号量来解决哲学家就餐问题
6.3 读者—写者问题
- 读者和写者共享文件。
代码示例:
Semaphore rmutex = 1, wmutex = 1, readcount = 0;void reader() {while (1) {wait(rmutex); // 请求读者互斥信号量if (readcount == 0) wait(wmutex); // 如果没有读者在读,则需要请求写者互斥信号量readcount++; // 增加读者数量signal(rmutex); // 释放读者互斥信号量// 读取文件的代码wait(rmutex); // 请求读者互斥信号量readcount--; // 减少读者数量if (readcount == 0) signal(wmutex); // 如果所有读者读完,则释放写者互斥信号量signal(rmutex); // 释放读者互斥信号量}
}void writer() {while (1) {wait(wmutex); // 请求写者互斥信号量// 写入文件的代码signal(wmutex); // 释放写者互斥信号量}
}
// 注释: 此代码示例展示了读者优先的读者写者问题的信号量解决方案
七、进程通信
- 进程通信的方式。
7.1 共享存储区系统
- 通过内存中的共享存储区域进行通信。
代码示例:
SharedMemory shm;void writer() {attach_shared_memory(shm); // 将共享内存附加到进程地址空间// 写入共享内存的代码detach_shared_memory(shm); // 从进程地址空间分离共享内存
}void reader() {attach_shared_memory(shm); // 将共享内存附加到进程地址空间// 从共享内存读取数据的代码detach_shared_memory(shm); // 从进程地址空间分离共享内存
}
// 注释: 此代码示例展示了共享存储区系统进行进程间通信的方法
7.2 管道通信系统
- 使用管道文件进行通信。
代码示例:
Pipe pipe;void writer() {write_to_pipe(pipe, data); // 将数据写入管道
}void reader() {data = read_from_pipe(pipe); // 从管道读取数据
}
// 注释: 此代码示例展示了管道通信系统进行进程间通信的方法
7.3 消息传递系统
- 通过消息传递进行通信。
代码示例:
void send_message(int destination, Message message) {send(destination, message); // 向目标进程发送消息
}void receive_message(Message *message) {receive(message); // 接收消息
}
// 注释: 此代码示例展示了消息传递系统进行进程间通信的方法
7.4 客户机 - 服务器系统
使用客户机-服务器模型进行通信。
代码示例:
Socket client_socket, server_socket;void client() {connect_to_server(client_socket, server_socket); // 客户端连接到服务器send_request(client_socket); // 发送请求receive_response(client_socket); // 接收响应
}void server() {server_socket = listen_for_client_connection(); // 服务器监听客户端连接client_socket = accept_connection(server_socket); // 接受连接receive_request(client_socket); // 接收请求send_response(client_socket); // 发送响应
}
// 注释: 此代码示例展示了客户机-服务器系统进行进程间通信的方法