信号量机制中的down和up函数

转自:https://blog.csdn.net/fzubbsc/article/details/37737159

参考:

https://blog.csdn.net/liuxd3000/article/details/17913363

http://blog.chinaunix.net/uid-25845340-id-3017214.html 

https://blog.csdn.net/xiao229404041/article/details/7031776

查阅文件:

kernel\linux\linux-4.4.3\kernel\locking\semaphore.c

kernel\linux\linux-4.4.3\include\linux\semaphore.h

DOWN操作:linux内核中,对信号量的DOWN操作有如下几种:

1、void down(struct semaphore *sem); //不可中断

down接口用于请求一个信号量。此函数的调用将会到致调用线程的睡眠,  直到获取到信号。同时,该函数的调用不允许中断。
在此函数中首先进行信号量资源数的查看,如果信号量数据(count)不为0,则把其减1,并返回,调用成功;否则调用__down进行等待,调用者进行睡眠。

2、int down_interruptible(struct semaphore *sem);//可中断

该函数功能和down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。

 

3、int down_killable(struct semaphore *sem);//睡眠的进程可以因为受到致命信号而被唤醒,中断获取信号量的操作。

down_killable与down_interruptible相近,最终传入的__down_common的实参有所不同(TASK_KILLABLE和TASK_INTERRUPTIBLE),所以,在此不再进行分析。

4、int down_trylock(struct semaphore *sem);//试图获取信号量,若无法获得则直接返回1而不睡眠。返回0则 表示获取到了信号量

down_trylock接口用于试着获取一个信号量,但是,此接口不会引起调用者的睡眠。不管有无可用信号量,都马上进行返回,如果返回0,则获取信号量成功,如果返回1,则获取失败。所以,在调用此接口时,必须进行返回的值的查看,看是否获取成功。

5、int down_timeout(struct semaphore *sem,long jiffies);//表示睡眠时间是有限制的,如果在jiffies指明的时间到期时仍然无法获得信号量,则将返回错误码。

down_timeout接口的实现过程与down接口的实现过程差不多,只是此接口 可以自定义超时时间,也就是如果在超时间内不能得到信号量,调用者会因为超时而自行唤醒。其实现过程如下,请注意超时参数的传入。其中TASK_UNINTERRUPTIBLE

在以上五种函数中,驱动程序使用的最频繁的就是down_interruptible函数,以下将对该函数进行分析。

down_interruptible函数的定义如下:

函数分析:函数首先通过spin_lock_irqsave的调用来保证对sem->count操作的原子性。如果count>0,表示当前进程可以获得信号量,将count的值减1然后退出。如果count不大于0,表明当前进程无法获取信号量,则调用__down_interruptible,后者会继续调用__down_common。

__down_common 函数定义如下:

static inline int __sched __down_common(struct semaphore *sem, longstate,

longtimeout)

{

struct task_struct *task= current;

struct semaphore_waiterwaiter;

list_add_tail(&waiter.list,&sem->wait_list);

waiter.task = task;

waiter.up = 0;

for (;;) {

if(signal_pending_state(state, task))

gotointerrupted;

if (timeout <=0)

gototimed_out;

__set_task_state(task,state);

spin_unlock_irq(&sem->lock);

timeout =schedule_timeout(timeout);

spin_lock_irq(&sem->lock);

if (waiter.up)

return 0;

}

timed_out:

list_del(&waiter.list);

return -ETIME;

interrupted:

list_del(&waiter.list);

return -EINTR;

}

函数分析:在__down_common函数数执行了以下操作。

(1)将当前进程放到信号量成员变量wait_list所管理的队列中。

(2)在一个for循环中把当前的进程状态这是为TASK_INTERRUPTIBLE,在调用schedule_timeout使当前进程进入睡眠状态,函数将停留在schedule_timeout调用上,知道再次被调度执行。

(3) 当该进程再一次被调度时,按原因执行相应的操作:如果waiter.up不为0说明进程被该信号量的up操作所唤醒,进程可以获得信号量。如果进程是因为被用户空间的信号所中断或超时信号所引起的唤醒,则返回相应的错误代码。 

UP操作:LINUX内核只提供了一个up函数

up函数定义如下:

void up(struct semaphore *sem)

{

unsigned long flags;


spin_lock_irqsave(&sem->lock,flags);

if(likely(list_empty(&sem->wait_list)))

sem->count++;

else

__up(sem);

spin_unlock_irqrestore(&sem->lock,flags);

}

函数分析:如果sem的wait_list队列为空,则表明没有其他进程正在等待该信号量,那么只需要把sem的count加1即可。如果wait_list队列不为空,则说明有其他进程正睡眠在wait_list上等待该信号,此时调用__up(sem)来唤醒进程:

__up()函数定义如下:

static noinline void __sched __up(struct semaphore *sem)

{

struct semaphore_waiter*waiter = list_first_entry(&sem->wait_list,

structsemaphore_waiter, list);

list_del(&waiter->list);

waiter->up = 1;

wake_up_process(waiter->task);

}

函数分析:在函数中,调用了wake_up_process来唤醒进程,这样进程就从之前的__down_interruptible调用中的timeout=schedule_timeout(timeout)处醒来,wait-up=1, __down_interruptible返回0,进程获得了信号量。

up()与down()函数之间的联系:由上面对两个函数的分析可以知道,__down_common函数中timeout=schedule_timeout(timeout) 有着很重要的作用。

Note:一个进程在调用down_interruptible()之后,如果sem<0,那么就进入到可中断的睡眠状态并调度其它进程运行, 但是一旦该进程收到信号,那么就会从down_interruptible函数中返回。并标记错误号为:-EINTR。一个形象的比喻:传入的信号量为1好比天亮,如果当前信号量为0,进程睡眠,直到(信号量为1)天亮才醒,但是可能中途有个闹铃(信号)把你闹醒。又如:小强下午放学回家,回家了就要开始吃饭嘛,这时就会有两种情况:情况一:饭做好了,可以开始吃;情况二:当他到厨房去的时候发现妈妈还在做,妈妈就对他说:“你先去睡会,待会做好了叫你。”小强就答应去睡会,不过又说了一句:“睡的这段时间要是小红来找我玩,你可以叫醒我。”小强就是down_interruptible,想吃饭就是获取信号量,睡觉对应这里的休眠,而小红来找我玩就是中断休眠。

使用可被中断的信号量版本的意思是,万一出现了semaphore的死锁,还有机会用ctrl+c发出软中断,让等待这个内核驱动返回的用户态进程退出。而不是把整个系统都锁住了。在休眠时,能被中断信号终止,这个进程是可以接受中断信号的!比如你在命令行中输入# sleep 10000,按下ctrl + c,就给上面的进程发送了进程终止信号。信号发送给用户空间,然后通过系统调用,会把这个信号传给递给驱动。信号只能发送给用户空间,无权直接发送给内核的,那1G的内核空间,我们是无法直接去操作的。 --------------------- 本文来自 liuxd3000 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/liuxd3000/article/details/17913363?utm_source=copy

  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哲学家进餐问题是经典的并发编程问题,其场景为五位哲学家围坐在一个圆桌旁,每位哲学家需要交替地进行思考和进餐,而进餐需要使用两个相邻的叉子,因此如果每位哲学家都拿起自己左边的叉子,就会导致死锁。 解决这个问题的方法之一就是利用信号量机制。我们可以为每个叉子都创建一个信号量,表示该叉子是否可用。在每位哲学家进行进餐时,先判断其左右两个叉子是否都可用,如果都可用,则分别将左右两个叉子的信号量减1,表示占用了这两个叉子,然后开始进餐。进餐完成后,将左右两个叉子的信号量加1,表示释放了这两个叉子。 以下是使用信号量机制解决哲学家进餐问题的实验报告: 一、实验目的 掌握信号量机制在解决并发编程问题的应用,理解哲学家进餐问题的场景和解决方法。 二、实验环境 操作系统:Linux 编程语言:C语言 三、实验原理 1. 信号量机制 信号量是一种特殊的变量,用于在多进程或多线程之间同步和互斥访问共享资源。信号量的值可以被多个进程或线程访问和修改,但只能通过特定的操作进行修改。一般来说,信号量可以用于以下两个目的: (1) 用于同步进程或线程,保证它们按照某种顺序执行。 (2) 用于互斥访问共享资源,保证同时只有一个进程或线程访问共享资源。 信号量的操作包括两种:P操作和V操作。P操作用于申请信号量,V操作用于释放信号量。具体来说,P操作会将信号量的值减1,如果减1后的值为负数,则表示资源已被占用,进程或线程需要等待;V操作会将信号量的值加1,如果加1后的值为非负数,则表示资源已释放,进程或线程可以继续执行。 2. 哲学家进餐问题 哲学家进餐问题是一种典型的并发编程问题,场景为五位哲学家围坐在一个圆桌旁,每位哲学家需要交替地进行思考和进餐,而进餐需要使用两个相邻的叉子,因此如果每位哲学家都拿起自己左边的叉子,就会导致死锁。为了避免死锁,需要采用一定的算法来保证哲学家们能够交替地进餐,而不会出现所有哲学家都在等待叉子的情况。 一种常用的解决方法是利用信号量机制,为每个叉子都创建一个信号量,表示该叉子是否可用。在每位哲学家进行进餐时,先判断其左右两个叉子是否都可用,如果都可用,则分别将左右两个叉子的信号量减1,表示占用了这两个叉子,然后开始进餐。进餐完成后,将左右两个叉子的信号量加1,表示释放了这两个叉子。 四、实验步骤 1. 定义信号量和哲学家的结构体: ``` #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define N 5 // 哲学家数量 typedef struct { sem_t fork_left; // 左边的叉子 sem_t fork_right; // 右边的叉子 int id; // 哲学家编号 } philosopher_t; ``` 2. 创建哲学家和叉子的信号量: ``` int main() { philosopher_t philosophers[N]; sem_t forks[N]; for (int i = 0; i < N; i++) { sem_init(&forks[i], 0, 1); // 初始化叉子的信号量为1,表示可用 } for (int i = 0; i < N; i++) { philosophers[i].id = i; sem_init(&philosophers[i].fork_left, 0, 1); // 初始化左边叉子的信号量为1,表示可用 sem_init(&philosophers[i].fork_right, 0, 1); // 初始化右边叉子的信号量为1,表示可用 } // ... } ``` 3. 创建哲学家线程和进餐函数: ``` void *eat(void *arg) { philosopher_t *p = (philosopher_t *) arg; int id = p->id; sem_t *fork_left = &p->fork_left; sem_t *fork_right = &p->fork_right; while (1) { printf("Philosopher %d is thinking\n", id); sleep(1); printf("Philosopher %d is hungry\n", id); sem_wait(fork_left); // 申请左边的叉子 printf("Philosopher %d picks up fork %d\n", id, id); sem_wait(fork_right); // 申请右边的叉子 printf("Philosopher %d picks up fork %d\n", id, (id + 1) % N); printf("Philosopher %d is eating\n", id); sleep(1); sem_post(fork_left); // 释放左边的叉子 sem_post(fork_right); // 释放右边的叉子 printf("Philosopher %d puts down fork %d and fork %d\n", id, id, (id + 1) % N); } } int main() { philosopher_t philosophers[N]; sem_t forks[N]; // ... for (int i = 0; i < N; i++) { philosophers[i].id = i; sem_init(&philosophers[i].fork_left, 0, 1); sem_init(&philosophers[i].fork_right, 0, 1); pthread_t tid; pthread_create(&tid, NULL, eat, &philosophers[i]); // 创建哲学家线程 } pthread_exit(NULL); } ``` 五、实验结果 运行程序后,可以看到五位哲学家交替地进行思考和进餐,没有出现死锁的情况。 ``` Philosopher 0 is thinking Philosopher 1 is thinking Philosopher 2 is thinking Philosopher 3 is thinking Philosopher 4 is thinking Philosopher 0 is hungry Philosopher 0 picks up fork 0 Philosopher 0 picks up fork 1 Philosopher 0 is eating Philosopher 1 is hungry Philosopher 1 picks up fork 1 Philosopher 1 picks up fork 2 Philosopher 1 is eating Philosopher 2 is hungry Philosopher 2 picks up fork 2 Philosopher 2 picks up fork 3 Philosopher 2 is eating Philosopher 3 is hungry Philosopher 3 picks up fork 3 Philosopher 3 picks up fork 4 Philosopher 3 is eating Philosopher 4 is hungry Philosopher 4 picks up fork 4 Philosopher 4 picks up fork 0 Philosopher 4 is eating Philosopher 0 puts down fork 0 and fork 1 Philosopher 0 is thinking Philosopher 1 puts down fork 1 and fork 2 Philosopher 1 is thinking Philosopher 2 puts down fork 2 and fork 3 Philosopher 2 is thinking Philosopher 3 puts down fork 3 and fork 4 Philosopher 3 is thinking Philosopher 4 puts down fork 4 and fork 0 Philosopher 4 is thinking Philosopher 0 is hungry Philosopher 0 picks up fork 0 Philosopher 0 picks up fork 1 Philosopher 0 is eating Philosopher 1 is hungry Philosopher 1 picks up fork 1 Philosopher 1 picks up fork 2 Philosopher 1 is eating Philosopher 2 is hungry Philosopher 2 picks up fork 2 Philosopher 2 picks up fork 3 Philosopher 2 is eating Philosopher 3 is hungry Philosopher 3 picks up fork 3 Philosopher 3 picks up fork 4 Philosopher 3 is eating Philosopher 4 is hungry Philosopher 4 picks up fork 4 Philosopher 4 picks up fork 0 Philosopher 4 is eating Philosopher 0 puts down fork 0 and fork 1 Philosopher 0 is thinking Philosopher 1 puts down fork 1 and fork 2 Philosopher 1 is thinking Philosopher 2 puts down fork 2 and fork 3 Philosopher 2 is thinking Philosopher 3 puts down fork 3 and fork 4 Philosopher 3 is thinking Philosopher 4 puts down fork 4 and fork 0 Philosopher 4 is thinking ``` 六、实验结论 本实验利用信号量机制解决了哲学家进餐问题,保证了每位哲学家交替地进行思考和进餐,避免了死锁的情况。通过实验可以看出,信号量机制是一种非常有效的并发编程解决方法,具有很高的实用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值