信号量的实现

信号量的实现

信号量的实现 #

信号师的实现需要三个问题:等待、互斥、唤醒(即重新激活等待的代码执行流)。

这就需要一种全新的数据结构来解决这些问题。根据上面的问题,这个数据结构至少需要一个变量来表示互斥,比如大于 0 则代码执行流可以继续运行,等于 0 则让代码执行流进入等待状态。还需要一个等待链,用于保存等待的代码执行流。

这个数据结构的实现代码如下所示。

#define SEM_FLG_MUTEX 0
#define SEM_FLG_MULTI 1
#define SEM_MUTEX_ONE_LOCK 1
#define SEM_MULTI_LOCK 0
//等待链数据结构,用于挂载等待代码执行流(线程)的结构,
//里面有用于挂载代码执行流的链表和计数器变量,这里我们先不深入研究这个数据结构。
typedef struct s_KWLST
{
    spinlock_t wl_lock;
    uint_t   wl_tdnr;
    list_h_t wl_list;
}kwlst_t;
//信号量数据结构
typedef struct s_SEM
{
    spinlock_t sem_lock;//维护sem_t自身数据的自旋锁
    uint_t sem_flg;//信号量相关的标志
    sint_t sem_count;//信号量计数值
    kwlst_t sem_waitlst;//用于挂载等待代码执行流(线程)结构
}sem_t;

搞懂了信号量的结构,我们再来看看信号量的一般用法,注意信号量在使用之前需要先进行初始化。这里假定信号量数据结构中的 sem_count 初始化为 1, sem_waitlst 等待链初始化为空。

使用信号量的步骤: 第一步,获取信号量。

  1. 首先对用于保护信号量自身的自旋锁 sem_lock 进行加锁。
  2. 对信号值 sem_count 执行“减 1”操作,并检查其值是否小于 0。
  3. 上步中检查 sem_count 如果小于 0,就让进程进入等待状态并且将其挂入 sem_waitlst 中,然后调度其它进程运行。否则表示获取信号量成功。当然最后别忘了对自旋锁 sem_lock 进行解锁。

第二步,代码执行流开始执行相关操作,例如读取键盘缓冲区。第三步,释放信号量。

  1. 首先对用于保护信号量自身的自旋锁 sem_lock 进行加锁。
  2. 对信号值 sem_count 执行“加 1”操作,并检查其值是否大于 0。
  3. 上步中检查 sem_count 值如果大于 0,就执行唤醒 sem_waitlst 中进程的操作,并且需要调度进程时就执行进程调度操作,不管 sem_count 是否大于 0(通常会大于 0)都标记信号量释放成功。当然最后别忘了对自旋锁 sem_lock 进行解锁。

下面我们来看看实现上述这些功能的代码,按照理论书籍上说,信号量有两个操作:down,up,代码如下。


//获取信号量
void krlsem_down(sem_t* sem)
{
    cpuflg_t cpufg;
start_step:
    krlspinlock_cli(&sem->sem_lock,&cpufg);
    if(sem->sem_count<1)
    {//如果信号量值小于1,则让代码执行流(线程)睡眠
        krlwlst_wait(&sem->sem_waitlst);
        krlspinunlock_sti(&sem->sem_lock,&cpufg);
        krlschedul();//切换代码执行流,下次恢复执行时依然从下一行开始执行,所以要goto开始处重新获取信号量
        goto start_step;
    }
    sem->sem_count--;//信号量值减1,表示成功获取信号量
    krlspinunlock_sti(&sem->sem_lock,&cpufg);
    return;
}
//释放信号量
void krlsem_up(sem_t* sem)
{
    cpuflg_t cpufg;
    krlspinlock_cli(&sem->sem_lock,&cpufg);
    sem->sem_count++;//释放信号量
    if(sem->sem_count<1)
    {//如果小于1,则说数据结构出错了,挂起系统
        krlspinunlock_sti(&sem->sem_lock,&cpufg);
        hal_sysdie("sem up err");
    }
    //唤醒该信号量上所有等待的代码执行流(线程)
    krlwlst_allup(&sem->sem_waitlst);
    krlspinunlock_sti(&sem->sem_lock,&cpufg);
    krlsched_set_schedflgs();
    return;
}

上述代码中的 krlspinlock_cli,krlspinunlock_sti 两个函数,只是对前面自旋锁函数的一个封装,krlschedul、krlwlst_wait、krlwlst_allup、 krlsched_set_schedflgs 这几个函数会在进程相关课程进行探讨。

Viewpoint #

From #

08 | 锁:并发操作中,解决数据同步的四种方法

自旋锁算法的实现