11.进程的同步与互斥
计数信号量及其初始化
和王道里面学的PV操作一摸一样,带个count变量,带个阻塞队列
c
//D:\code\x86\code\start\start\source\kernel\include\ipc\sem.h
#ifndef OS_SEM_H
#define OS_SEM_H
#include "tools/list.h"
/**
* 进程同步用的计数信号量
*/
typedef struct _sem_t {
int count; // 信号量计数
list_t wait_list; // 等待的进程列表
}sem_t;
void sem_init (sem_t * sem, int init_count);
#endif //OS_SEM_H
实现函数
c
//D:\code\x86\code\start\start\source\kernel\ipc\sem.c
#include "cpu/irq.h"
#include "core/task.h"
#include "ipc/sem.h"
/**
* 信号量初始化
*/
void sem_init (sem_t * sem, int init_count) {
sem->count = init_count;
list_init(&sem->wait_list);
}
发送和等待信号
生产者消费者问题
c
//D:\code\x86\code\start\start\source\kernel\include\ipc\sem.h
void sem_wait (sem_t * sem); //相当于P操作
void sem_notify (sem_t * sem); //相当于V操作
int sem_count (sem_t * sem); //放回count
实现函数
c
//D:\code\x86\code\start\start\source\kernel\ipc\sem.c
/**
* 申请信号量
*/
void sem_wait (sem_t * sem) {
irq_state_t irq_state = irq_enter_protection(); //临界区的保护
if (sem->count > 0) {
sem->count--; //大于0的情况
} else {
// 从就绪队列中移除,然后加入信号量的等待队列
task_t * curr = task_current(); //获取当前进程
task_set_block(curr); //阻塞当前任务,移出就绪队列
list_insert_last(&sem->wait_list, &curr->wait_node); //插入等待队列后面
task_dispatch();
}
irq_leave_protection(irq_state);
}
/**
* 释放信号量
*/
void sem_notify (sem_t * sem) {
irq_state_t irq_state = irq_enter_protection();
if (list_count(&sem->wait_list)) {
// 有进程等待,则唤醒加入就绪队列
list_node_t * node = list_remove_first(&sem->wait_list);
task_t * task = list_node_parent(node, task_t, wait_node);
task_set_ready(task);
task_dispatch();
} else {
sem->count++;
}
irq_leave_protection(irq_state);
}
/**
* 获取信号量的当前值
*/
int sem_count (sem_t * sem) {
irq_state_t irq_state = irq_enter_protection();
int count = sem->count;
irq_leave_protection(irq_state);
return count;
}
test函数
c
//如果一开始count给的初值为2,其他进程又没有释放这个资源,那么抢占资源的任务就会执行两次
//init.c
void init_task_entry(void) {
int count = 0;
for (;;) {
sem_wait(&sem);
log_printf("init task: %d", count++);
// task_switch_from_to(&init_task, task_first_task());
// sys_yield(); //自动放弃CPU让
}
}
// 放在开中断前,以避免定时中断切换至其它任务,而此时信号量还未初始化
sem_init(&sem, 2);
结果
test2
c
//init.c
int count = 0;
for (;;) {
log_printf("first task: %d", count++);
// 发消息给init task,可以打印了
sem_notify(&sem);
sys_msleep(1000);
// task_switch_from_to(task_first_task(), &init_task);
// sys_yield();
}
结果
互斥锁
临界资源互斥fan
之前是应用自己操作开关中断,太危险了,我们把互斥部分封装起来
这很像简化版读者写者问题
但是这里同一进程可以给临界区上很多把锁
c
//D:\code\x86\code\start\start\source\kernel\include\ipc\mutex.h
#ifndef MUTEX_H
#define MUTEX_H
#include "core/task.h"
#include "tools/list.h"
/**
* 进程同步用的计数信号量
*/
typedef struct _mutex_t {
task_t * owner; //锁的拥有者是谁
int locked_count; //这把锁已经上锁了多少次,和信号量计数不一样
list_t wait_list; //这把锁的等待队列
}mutex_t;
void mutex_init (mutex_t * mutex); //初始化
void mutex_lock (mutex_t * mutex); //上锁
void mutex_unlock (mutex_t * mutex); //解锁
#endif //MUTEX_H
实现函数
c
#include "cpu/irq.h"
#include "ipc/mutex.h"
/**
* 锁初始化
*/
void mutex_init (mutex_t * mutex) { //初始化函数
mutex->locked_count = 0; //上锁次数初始值为0
mutex->owner = (task_t *)0; //拥有者清0
list_init(&mutex->wait_list); //队列初始化
}
/**
* 申请锁
*/
void mutex_lock (mutex_t * mutex) {
irq_state_t irq_state = irq_enter_protection();
task_t * curr = task_current();
if (mutex->locked_count == 0) { //没有上锁的状态,是不是已经上锁了
// 没有任务占用,占用之
mutex->locked_count = 1; //上锁
mutex->owner = curr; //归属
} else if (mutex->owner == curr) { //已经上锁的状态,为自己,则二度上锁
// 已经为当前任务所有,只增加计数
mutex->locked_count++;
} else { //其他进程申请
// 有其它任务占用,则进入队列等待
task_t * curr = task_current();
task_set_block(curr); //阻塞起来(和王道里面一样没有外界资源,就阻塞起来)
list_insert_last(&mutex->wait_list, &curr->wait_node); //放到这个锁的资源的等待队列里面
task_dispatch(); //任务切换
}
irq_leave_protection(irq_state);
}
/**
* 释放锁
*/
void mutex_unlock (mutex_t * mutex) {
irq_state_t irq_state = irq_enter_protection();
// 只有锁的拥有者才能释放锁
task_t * curr = task_current(); //获取当前进程
if (mutex->owner == curr) { //锁都拥有者是不是自己
if (--mutex->locked_count == 0) { //减到0才释放锁,自己上多道锁,自己也需要解锁n次才能打开这个锁
// 减到0,释放锁
mutex->owner = (task_t *)0;
// 如果队列中有任务等待,则立即唤醒并占用锁
if (list_count(&mutex->wait_list)) { //是不是有进程在等
list_node_t * task_node = list_remove_first(&mutex->wait_list); //从等待队列移除
task_t * task = list_node_parent(task_node, task_t, wait_node);
task_set_ready(task); //插入就绪队列里面
// 在这里占用,而不是在任务醒后占用,因为可能抢不到
mutex->locked_count = 1;//把锁交出来
mutex->owner = task; //owner换人了
task_dispatch(); //任务切换
}
}
}
irq_leave_protection(irq_state);
}