Driver中使用的内核机制
1. 互斥与同步
自旋锁
三种主要自旋锁函数
spin_lock
spin_lock_irq/spin_lock_irqsave
spin_lock_bh
使用spin_lock时要明确知道该锁不会在中断处理程序中使用。
在任何情况下使用spin_lock_irq都是安全的,它关闭当前CPU硬件中断,禁止内核抢占。使用spin_lock_irqsave在于你不期望在离开临界区后,改变中断的开启关闭状态!进入临界区是关闭的,离开后它同样应该是关闭的!
spin_lock_bh()中首先会调用local_bh_disable()禁止当前CPU的软件中断。
如果被保护的共享资源只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和spin_unlock_bh来保护。
当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和 spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和 spin_unlock_bh是最恰当的,它比其他两个快。
信号量
信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用(_trylock的变种能够在中断上下文使用),当尝试获取某信号量失败时CPU会来回切换到其他进程
互斥锁
互斥锁上特殊的信号量,将该特殊semaphore的count值初始化为1,获取锁和释放锁对应信号量的DOWN和UP操作
struct mutex cmd_lock;
struct mutex cmd_lock;
mutex_init(&cmd->cmd_lock);
mutex_destroy(&cmd->cmd_lock);
mutex_lock(&cmd->cmd_lock);
mutex_unlock(&cmd->cmd_lock);
原子变量与位操作
所谓的原子操作即是保证指令以原子的方式执行,它在执行过程中不被打断。它包括了原子整数操作和原子位操作,在内核中分别定义于include\linux\types.h和arch\x86\include\asm\bitops.h
原子位操作
2.阻塞操作
在linux驱动程序中,可使用等待队列(wait queue)来实现阻塞进程的唤醒,以队列为基础数据结构,与进程调度机制紧密结合,用于视线内核的异步事件通知机制,也可用于同步对系统资源的访问。(信号量在内核中也依赖等待队列来实现)
在软件开发中任务经常由于某种条件没有得到满足而不得不进入睡眠状态,然后等待条件得到满足的时候再继续运行,进入运行状态。这种需求需要等待队列机制的支持。Linux中提供了等待队列的机制,该机制在内核中应用很广泛。
等待队列在linux内核中有着举足轻重的作用,很多linux驱动都或多或少涉及到了等待队列。因此,对于linux内核及驱动开发者来说,掌握等待队列是必须课之
Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头 (wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来。下面具体介绍。
wait_queue_head_twaitq;
init_waitqueue_head(&cmd->waitq);
wait_event_timeout(wq,condition, timeout)
也与wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且condition为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回0.
在我们驱动中,向fireware发送命令时,要求命令在timeout之内返回,否则认为命令没有成功返回;发送命令后会wait_event_timeout阻塞线程,直到收到了fireware的命令返回,这时wake_up阻塞的线程,且能够判断condition为真,wait_event_timeout返回为剩余时间,如果超过了timeout时间还未收到返回命令,则wait_event_timeout直接返回0.
3. 延迟操作
除了中断处理进程用到的底半部机制,用在进程上下文的延迟操作是work queue。
(一)利用系统共享的工作队列添加工作:
第一步:声明或编写一个工作处理函数
void my_func();
第二步:创建一个工作结构体变量,并将处理函数和参数的入口地址赋给这个工作结构体变量
DECLARE_WORK(my_work,my_func,&data);//编译时创建名为my_work的结构体变量并把函数入口地址和参数地址赋给它;如果不想要在编译时就用DECLARE_WORK()创建并初始化工作结构体变量,也可以在程序运行时再用INIT_WORK()创建
struct work_structmy_work; //创建一个名为my_work的结构体变量,创建后才能使用INIT_WORK()
INIT_WORK(&my_work,my_func,&data);//初始化已经创建的my_work,其实就是往这个结构体变量中添加处理函数的入口地址和data的地址,通常在驱动的open函数中完成
第三步:将工作结构体变量添加入系统的共享工作队列
schedule_work(&my_work);//添加入队列的工作完成后会自动从队列中删除schedule_delayed_work(&my_work,tick); //延时tick个滴答后再提交工作
(二)创建自己的工作队列来添加工作
第一步:声明工作处理函数和一个指向工作队列的指针
void my_func();
struct workqueue_struct*p_queue;
第二步:创建自己的工作队列和工作结构体变量(通常在open函数中完成)
p_queue=create_workqueue("my_queue");//创建一个名为my_queue的工作队列并把工作队列的入口地址赋给声明的指针
struct work_structmy_work;
INIT_WORK(&my_work,my_func,&data);//创建一个工作结构体变量并初始化,和第一种情况的方法一样
第三步:将工作添加入自己创建的工作队列等待执行
queue_work(p_queue,&my_work); //作用与schedule_work()类似,不同的是将工作添加入p_queue指针指向的工作队列而不是系统共享的工作队列
第四步:删除自己的工作队列
destroy_workqueue(p_queue);//一般是在close函数中删除
4. Linux内核链表
4.1 链表初始化
struct athwl_priv {
struct wiphy *wiphy;
/* virtual interfacelist */
spinlock_t list_lock;
struct list_headvif_list;
…
}
INIT_LIST_HEAD(&priv->vif_list);
struct athwl_vif {
struct net_device*ndev; /* Linux net device */
struct wireless_dev wdev; /* Linux wireless device */
struct athwl_priv*priv;
char name[IFNAMSIZ];
enum athwl_mode mode;
struct list_headvif_node; /* node for virtualinterface list */
…
}
4.2插入操作
在表头插入:
static inline voidlist_add(struct list_head *new, struct list_head *head);
在表尾插入:
static inline void list_add_tail(structlist_head *new, struct list_head *head);
list_add_tail(&vif->vif_node,&priv->vif_list);
4.3删除
static inline voidlist_del(struct list_head *entry);
4.4遍历
a)由链表节点到数据项
/**
* list_entry - get thestruct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embeddedin.
* @member: the name of the list_struct within thestruct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr,type, member)
b)遍历宏
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#definelist_for_each(pos, head) \
for (pos = (head)->next; pos !=(head); pos = pos->next)
它实际上是一个for循环,利用传入的pos作为循环变量,从表头head开始,逐项向后(next方向)移动pos,直至又回到head
大多数情况下,遍历链表的时候都需要获得链表节点数据项,也就是说list_for_each()和list_entry()总是同时使用。对此Linux给出了一个list_for_each_entry()宏:
#definelist_for_each_entry(pos, head, member) ……
与list_for_each()不同,这里的pos是数据项结构指针类型,而不是(struct list_head *)。
list_for_each_entry(vif,&priv->vif_list, vif_node);