Linux设备驱动之semaphore机制
在Linux系统中,信号号是一种重要的加锁机制,特别在互斥型资源中,semaphore更能很好的工作。
1: semaphore结构体定义
在Linux2.6.35内核中,semaphore的实现机制与以前的版本一点不同,在其中去除了DECLARE_MUTEX_LOCKED这个初始化互斥宏定义,但是,又添加了一个特别重要的函数,down_killable,这个函数的添加,使此版本的semaphore机制比以往的更强。semaphore结构全定义如下所示。
从此结构体中可以看从,semaphore机制也就是自旋锁的包装,在后面的分析中,读者将会明白我为什么会这样说。
2: semaphore提供的操作函数
在semaphore机制中,系统给我们提供也几个函数用于操作,正因为内核给
我们提供了这些函数接口,我们才能应用其来更好的完成我们对互斥型资料的加锁,使其能准确无误的在SMP或者多线程中应用。semaphore提供接口函数如下所示。
DECLARE_MUTEX
init_MUTEX
init_MUTEX_LOCKED
down
down_interruptible
down_killable
down_trylock
down_timeout
up
3 DECLARE_MUTEX接口分析
DECLARE_MUTEX接口为一个宏定义,此定义调用另一个宏定义来完成对
互斥型信号量进行初始化,其原型如下。
此宏用于初始化一个名为name的信号量,并把其资料数初始化为1。其中name为信号的名字,name为符合系统的标号,且不需提前定义。比如我们想定义一个名为my_semaphore的互斥型信号量,并把它初始化为1,我们将应用这个接口来实现,实现代码如下。
从DECLARE_MUTEX宏义看出,此宏是通过调用另一个宏来实现的,此宏定义实现如下。
4 init_MUTEX接口分析
init_MUTEX接口用于动态初始化一个信号量,此接口也是一个宏定义,通
过调用sema_init函数来完成,此宏定义原型如下。
其中sem为struct semaphore结构体变量,所以,在应用此宏来初始化一个信号前,必须定义一个struct semaphore结构体变量。此宏初始化一个名为sem的信号量,并把其资料初始化为1。
在此宏中我们看到,宏定义是通过调用sema_init函数来完成初始化的,所可,可以这样说,真正初始化sem信号量的应该是sema_init函数。为了读者方便查看,所以在此把sema_init函数的实现过程列出,关于此函数请读者自行查看\include\linux\semaphore.h文件,这样读者对semaphore的理解更加深入。sema_init函数实现如下。
5 init_MUTEX_LOCKED接口分析
init_MUTEX_LOCKED接口的实现与init_MUTEX接口的实现过程是一样的,
只是此接口把资源数初始化为0。所以在此不再进行分析,其实现如下。
6 __down_common函数分析
在进行分析互斥信号量相关操作之前,进行分析一个特别重要的函数,因为
这个函数非常重要,semaphore的加锁操作都是基于这个函数实现的,所以,在分析它们之前进行分析这个函数,读者更加能看懂semaphore的实现过程。此函数的原型如下。
在此函数中应用到的task_struct相关的知识,对于task_struct的知识在这个函数本人不进行分析,同时也不进行说明,因为task_struct机制是一个复杂的机制,不是三言两语就能分析的。在此函数中同样应用自旋锁的知识,关于自旋锁的知识本人将会在自旋锁机制相关的文章进行分析,在此文章中就不进行分析。本函数中一个重要的是超时调度,这一调度的实现如下。由于应用自旋锁加以保护,所以,超时调试过程有可中断,也不可中断,这就要看传入的state是允许中断还是不允许中断。超时调度实现过程如下。
7 down接口分析
down接口用于请求一个信号量。此函数的调用将会到致调用线程的睡眠,
直到获取到信号。同时,该函数的调用不允许中断。
在此函数中首先进行信号量资源数的查看,如果信号量数据(count)不为0,则把其减1,并返回,调用成功;否则调用__down进行等待,调用者进行睡眠。实现过程如下。
在此函数中引起调用者进行睡眠的函数是__down函数,在等待中能不能中断同样是由于__down函数引起,所以,关键点在于__down函数,__down函数的实现如下。
在__down函数中,通过调用__down_common函数来进行相关的操作,在上面的分析中我提到,传给__down_common函数的state参数将决定调用者是否可以中断,所以,down接口在睡眠过程中不能中断就是因为传入的实参决定。所以,在编过程中,最好很用此接口,尽量应用down_interruptible、 down_killable接口替代。MAX_SCHEDULE_TIMEOUT为一个宏定义,实定义如下。
LONG_MAX同时也是一个宏定义,其定义如下。
所以,此接口不会因为超时而用,因为需要等待232-1个时钟滴答才能超时,这是一个很长的时间。所以,如果不使用up接口进行唤醒,整个进程将死掉。
9 down_interruptible接口分析
down_interruptible接口也是用于获取一个信号量,与down接口不同的是,
此接口在等待过程中是可以被中断的,是正常返回还是被中断返回通过返回值进行判断,其它与down相同,所以,在此不再进行分析。
10 down_killable接口分析
down_killable与down_interruptible相同,只是传入的__down_common的实
参不同,所以,在此不再进行分析。
11 down_trylock接口分析
down_trylock接口用于试着获取一个信号量,但是,此接口不会引起调用者
的睡眠。不管有无可用信号量,都马上进行返回,如果返回0,则获取信号量成功,如果返回1,则获取失败。所以,在调用此接口时,必须进行返回的值的查看,看是否获取成功。其实现过程如下。
12 down_timeout接口分析
down_timeout接口的实现过程与down接口的实现过程差不多,只是此接口
可以自定义超时时间,也就是如果在超时间内不能得到信号量,调用者会因为超时而自行唤醒。其实现过程如下,请注意超时参数的传入。
13 up接口分析
up接口用于唤醒处于等待的线程,对于某些不能获取信号量的线程,如果不
强制唤醒,那么也许会造此线程的死掉,所以,才有up接口。此接口的实现比较简单,所以不进行详细的分析,其实现如下。
14 关于其它说明
从开始学习嵌入式Linux到现在也有差不多4个月的时间了,从一味的看书
到走进Linux内核,这是本人的一个重大的进步,很多时候知其然而不知其所以然,写不出好的程序,所以,深入Linux是每个学习嵌入式Linux驱动开发学习者的必经之路,同时也是一个生的事。
Linux内核的不断发展,每一个版本之间都或多或少的有改变,如果不进行入其内核进行分析,那么这样就等于把自己限定于某一内核版本内,对自己的技术的提高、发展有很大的影响。同时,在工作中,不是你知道什么,会什么就做什么,而是需要做什么,不管你会与会都必须得做,所以,能不能深入Linux内核在一点上把不同能力的人给区分了。
在能不能看懂Linux内核的问题上,起决定作用的是个人的C语言能力,这也是C语言好坏的体现,如果C语言不好,那么就不能可看懂Linux内核,那么也就决定了个人的发展。所以说在嵌入式C语言是决定个人发展的重大条件,同时也是个人能力的体现。
在Linux系统中,信号号是一种重要的加锁机制,特别在互斥型资源中,semaphore更能很好的工作。
1: semaphore结构体定义
在Linux2.6.35内核中,semaphore的实现机制与以前的版本一点不同,在其中去除了DECLARE_MUTEX_LOCKED这个初始化互斥宏定义,但是,又添加了一个特别重要的函数,down_killable,这个函数的添加,使此版本的semaphore机制比以往的更强。semaphore结构全定义如下所示。
1
2
3
4
5
|
struct
semaphore {
spinlock_t lock;
/* 自旋锁结构体变量 */
unsigned
int
count;
/* 用于计录资料数量 */
struct
list_head wait_list;
/* 内部链表结构体变量 */
};
|
从此结构体中可以看从,semaphore机制也就是自旋锁的包装,在后面的分析中,读者将会明白我为什么会这样说。
2: semaphore提供的操作函数
在semaphore机制中,系统给我们提供也几个函数用于操作,正因为内核给
我们提供了这些函数接口,我们才能应用其来更好的完成我们对互斥型资料的加锁,使其能准确无误的在SMP或者多线程中应用。semaphore提供接口函数如下所示。
DECLARE_MUTEX
init_MUTEX
init_MUTEX_LOCKED
down
down_interruptible
down_killable
down_trylock
down_timeout
up
3 DECLARE_MUTEX接口分析
DECLARE_MUTEX接口为一个宏定义,此定义调用另一个宏定义来完成对
互斥型信号量进行初始化,其原型如下。
1
2
|
#define DECLARE_MUTEX(name) \
struct
semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
|
此宏用于初始化一个名为name的信号量,并把其资料数初始化为1。其中name为信号的名字,name为符合系统的标号,且不需提前定义。比如我们想定义一个名为my_semaphore的互斥型信号量,并把它初始化为1,我们将应用这个接口来实现,实现代码如下。
1
|
DECLARE_MUTEX(my_semaphore);
|
从DECLARE_MUTEX宏义看出,此宏是通过调用另一个宏来实现的,此宏定义实现如下。
1
2
3
4
5
6
|
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
|
4 init_MUTEX接口分析
init_MUTEX接口用于动态初始化一个信号量,此接口也是一个宏定义,通
过调用sema_init函数来完成,此宏定义原型如下。
1
|
#define init_MUTEX(sem) sema_init(sem, 1)
|
其中sem为struct semaphore结构体变量,所以,在应用此宏来初始化一个信号前,必须定义一个struct semaphore结构体变量。此宏初始化一个名为sem的信号量,并把其资料初始化为1。
在此宏中我们看到,宏定义是通过调用sema_init函数来完成初始化的,所可,可以这样说,真正初始化sem信号量的应该是sema_init函数。为了读者方便查看,所以在此把sema_init函数的实现过程列出,关于此函数请读者自行查看\include\linux\semaphore.h文件,这样读者对semaphore的理解更加深入。sema_init函数实现如下。
1
2
3
4
5
6
|
static
inline
void
sema_init(
struct
semaphore *sem,
int
val)
{
static
struct
lock_class_key __key;
*sem = (
struct
semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map,
"semaphore->lock"
, &__key, 0);
}
|
5 init_MUTEX_LOCKED接口分析
init_MUTEX_LOCKED接口的实现与init_MUTEX接口的实现过程是一样的,
只是此接口把资源数初始化为0。所以在此不再进行分析,其实现如下。
1
|
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
|
6 __down_common函数分析
在进行分析互斥信号量相关操作之前,进行分析一个特别重要的函数,因为
这个函数非常重要,semaphore的加锁操作都是基于这个函数实现的,所以,在分析它们之前进行分析这个函数,读者更加能看懂semaphore的实现过程。此函数的原型如下。
1
2
|
static
inline
int
__sched __down_common(
struct
semaphore *sem,
long
state,
long
timeout)
|
在此函数中应用到的task_struct相关的知识,对于task_struct的知识在这个函数本人不进行分析,同时也不进行说明,因为task_struct机制是一个复杂的机制,不是三言两语就能分析的。在此函数中同样应用自旋锁的知识,关于自旋锁的知识本人将会在自旋锁机制相关的文章进行分析,在此文章中就不进行分析。本函数中一个重要的是超时调度,这一调度的实现如下。由于应用自旋锁加以保护,所以,超时调试过程有可中断,也不可中断,这就要看传入的state是允许中断还是不允许中断。超时调度实现过程如下。
1
|
timeout = schedule_timeout(timeout);
|
7 down接口分析
down接口用于请求一个信号量。此函数的调用将会到致调用线程的睡眠,
直到获取到信号。同时,该函数的调用不允许中断。
在此函数中首先进行信号量资源数的查看,如果信号量数据(count)不为0,则把其减1,并返回,调用成功;否则调用__down进行等待,调用者进行睡眠。实现过程如下。
1
2
3
4
5
6
7
8
9
10
11
|
void
down(
struct
semaphore *sem)
{
unsigned
long
flags;
spin_lock_irqsave(&sem->lock, flags);
if
(likely(sem->count > 0))
sem->count--;
else
__down(sem);
spin_unlock_irqrestore(&sem->lock, flags);
}
|
在此函数中引起调用者进行睡眠的函数是__down函数,在等待中能不能中断同样是由于__down函数引起,所以,关键点在于__down函数,__down函数的实现如下。
1
2
3
4
5
|
static
noinline
void
__sched __down(
struct
semaphore *sem)
{
__down_common(sem,TASK_UNINTERRUPTIBLE,\
MAX_SCHEDULE_TIMEOUT);
}
|
在__down函数中,通过调用__down_common函数来进行相关的操作,在上面的分析中我提到,传给__down_common函数的state参数将决定调用者是否可以中断,所以,down接口在睡眠过程中不能中断就是因为传入的实参决定。所以,在编过程中,最好很用此接口,尽量应用down_interruptible、 down_killable接口替代。MAX_SCHEDULE_TIMEOUT为一个宏定义,实定义如下。
1
|
#define MAX_SCHEDULE_TIMEOUT LONG_MAX
|
LONG_MAX同时也是一个宏定义,其定义如下。
1
|
#define LONG_MAX ((long)(~0UL>>1))
|
所以,此接口不会因为超时而用,因为需要等待232-1个时钟滴答才能超时,这是一个很长的时间。所以,如果不使用up接口进行唤醒,整个进程将死掉。
9 down_interruptible接口分析
down_interruptible接口也是用于获取一个信号量,与down接口不同的是,
此接口在等待过程中是可以被中断的,是正常返回还是被中断返回通过返回值进行判断,其它与down相同,所以,在此不再进行分析。
10 down_killable接口分析
down_killable与down_interruptible相同,只是传入的__down_common的实
参不同,所以,在此不再进行分析。
11 down_trylock接口分析
down_trylock接口用于试着获取一个信号量,但是,此接口不会引起调用者
的睡眠。不管有无可用信号量,都马上进行返回,如果返回0,则获取信号量成功,如果返回1,则获取失败。所以,在调用此接口时,必须进行返回的值的查看,看是否获取成功。其实现过程如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int
down_trylock(
struct
semaphore *sem)
{
unsigned
long
flags;
int
count;
spin_lock_irqsave(&sem->lock, flags);
count = sem->count - 1;
if
(likely(count >= 0))
sem->count = count;
spin_unlock_irqrestore(&sem->lock, flags);
return
(count < 0);
}
|
12 down_timeout接口分析
down_timeout接口的实现过程与down接口的实现过程差不多,只是此接口
可以自定义超时时间,也就是如果在超时间内不能得到信号量,调用者会因为超时而自行唤醒。其实现过程如下,请注意超时参数的传入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int
down_timeout(
struct
semaphore *sem,
long
jiffies)
{
unsigned
long
flags;
int
result = 0;
spin_lock_irqsave(&sem->lock, flags);
if
(likely(sem->count > 0))
sem->count--;
else
result = __down_timeout(sem, jiffies);
spin_unlock_irqrestore(&sem->lock, flags);
return
result;
}
|
13 up接口分析
up接口用于唤醒处于等待的线程,对于某些不能获取信号量的线程,如果不
强制唤醒,那么也许会造此线程的死掉,所以,才有up接口。此接口的实现比较简单,所以不进行详细的分析,其实现如下。
1
2
3
4
5
6
7
8
|
static
noinline
void
__sched __up(
struct
semaphore *sem)
{
struct
semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
struct
semaphore_waiter, list);
list_del(&waiter->list);
waiter->up = 1;
wake_up_process(waiter->task);
}
|
14 关于其它说明
从开始学习嵌入式Linux到现在也有差不多4个月的时间了,从一味的看书
到走进Linux内核,这是本人的一个重大的进步,很多时候知其然而不知其所以然,写不出好的程序,所以,深入Linux是每个学习嵌入式Linux驱动开发学习者的必经之路,同时也是一个生的事。
Linux内核的不断发展,每一个版本之间都或多或少的有改变,如果不进行入其内核进行分析,那么这样就等于把自己限定于某一内核版本内,对自己的技术的提高、发展有很大的影响。同时,在工作中,不是你知道什么,会什么就做什么,而是需要做什么,不管你会与会都必须得做,所以,能不能深入Linux内核在一点上把不同能力的人给区分了。
在能不能看懂Linux内核的问题上,起决定作用的是个人的C语言能力,这也是C语言好坏的体现,如果C语言不好,那么就不能可看懂Linux内核,那么也就决定了个人的发展。所以说在嵌入式C语言是决定个人发展的重大条件,同时也是个人能力的体现。