这两天看linux kernel driver部分的心得 :
下面的网站不错, 总结了 2.6与 2.4 的差异 ,差异挺大, 尤其在编译module方面 ,和 任务队列方面。 还有实时调度方面 。
执行完schedule()后, 进程会马上切换吗?
>>是 ,马上就切换了。
调用schedule_timeout()的条件:
必须处于进程上下文中(也就是不能是中断和bh 中),并能不能持有锁。
由此推广之:内核的线程要睡眠的话, 必须不能在中断中, 且不能hold on 锁。 否则系统可能锁死 。
还可以用schedule_timeout_interruptible();
schedule_timeout_uninterruptible();
还要注意的是:HZ和进程调度切换, 一定定时器的关系。
HZ是时钟频率, 表示 1s内时钟中断的次数,i386 HZ=100 , 意味1s内cpu的中断100次 , 关系是: 只有时钟中断到来时 , 才会产生可能产生进程切换,
因为 周期为10ms , 所以即使在2ms的时候, 产生了中断, 那么也必须再等8ms 才可以调用中断处理函数。 如果 HZ被提高到 1000的那么准确度就会非常高 。
但是这样带来的问题时 :加重了处理器CPU的处理时钟中断的负担。
摘自网站ibm :http://www-128.ibm.com/developerworks/cn/linux/l-inside/index.html
工作队列接口
工作队列接口是在2.5的开发过程中引入的,
用于取代任务队列接口(用于调度内核任务)。每个工作队列有一个专门的线程,
所有来自运行队列的任务在进程的上下文中运行(这样它们可以休眠)。
驱动程序可以创建并使用它们自己的工作队列,或者使用内核的一个工作队列。工作队列用以下方式创建:
涉及到共享一个工作队列
struct workqueue_struct *create_workqueue(const char *name);
在这里 name 是工作队列的名字。
工作队列任务可以在编译时或者运行时创建。任务需要封装为一个叫做 work_struct 的结构体。
在编译期初始化一个工作队列任务时要用到:
DECLARE_WORK(name, void (*function)(void *), void *data);
在这里 name 是 work_struct 的名字,function 是当任务被调度时调用的函数,data 是指向那个函数的指针。
在运行期初始化一个工作队列时要用到:
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
用下面的函数调用来把一个作业(一个类型为work_struct 结构的工作队列作业/任务)加入到工作队列中:
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue, struct work_struct
*work, unsigned long delay);
在queue_delay_work()中指定 delay,是为了保证至少在经过一段给定的最小延迟时间以后,工作队列中的任务才可以真正执行。
工作队列中的任务由相关的工作线程执行,可能是在一个无法预期的时间(取决于负载,中断等等),或者是在一段延迟以后。
任何一个在工作队列中等待了无限长的时间也没有运行的任务可以用下面的方法取消:
int cancel_delayed_work(struct work_struct *work);
如果当一个取消操作的调用返回时,任务正在执行中,那么这个任务将继续执行下去,但不会再加入到队列中。
清空工作队列中的所有任务使用:
void flush_workqueue(struct workqueue_struct *queue);
销毁工作队列使用:
void destroy_workqueue(struct workqueue_struct *queue);
----------------------------------------------
一定要掌握 kernel里面自带的list的用法, 这是个双链表的实现, 在kernel到处是它的应用。
/*
* Simple doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
--
/* 下面这些就非常有用了*/
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in. //包含了list head的那个大的结构体
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop counter.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \ /* 注意双链表的header是没有数据项的 */
for (pos = (head)->next; prefetch(pos->next), pos != (head); \ //所以没有取header
pos = pos->next)
/**
* __list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop counter.
* @head: the head for your list.
*
* This variant differs from list_for_each() in that it's the
* simplest possible list iteration code, no prefetching is done.
* Use this for code that knows the list to be very short (empty
* or 1 entry) most of the time.
*/
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
// -----------------------------------------------
kernel 2.6.17-8 里面的一个函数:
/*
* The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just
* wake everything up. If it's an exclusive wakeup (nr_exclusive == small +ve
* number) then we wake all the non-exclusive tasks and one exclusive task.
*
* There are circumstances in which we can try to wake a task which has already
* started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
* zero in this (rare) case, and we handle it by continuing to scan the queue.
*/
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int sync, void *key)
{
struct list_head *tmp, *next;
list_for_each_safe(tmp, next, &q->task_list) { //kernel里面到处是这种写法,要习惯
wait_queue_t *curr;
unsigned flags;
curr = list_entry(tmp,wait_queue_t , task_list);
flags = curr->flags;
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) &&
!--nr_exclusive)
break;
}
}
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop counter.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
//--------------------------注意 2.6.x 与 2.4.x 的等待队列的区别(__wait_queue)
//2.4.x typedef struct __wait_queue wait_queue_t;
struct __wait_queue { typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int sync, void *key);
unsigned int flags; int default_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key);
#define WQ_FLAG_EXCLUSIVE 0x01
struct task_struct * task;//包含了一个真正的任务 struct __wait_queue {
struct list_head task_list; unsigned int flags;
#if WAITQUEUE_DEBUG #define WQ_FLAG_EXCLUSIVE 0x01
long __magic; void *private;
long __waker; wait_queue_func_t func;
#endif struct list_head task_list;
}; };
从中看出的主要的区别是: 2.6.x 增加了 private 字段 , 而且把原来的task_struct 结构体,换成了wait_queue_func_t func(一个函数)
2.4.x 用的是 try_to_wake_up
static inline int try_to_wake_up(struct task_struct * p, int synchronous) //仅仅是把进程加入到可运行队列中去.
{
unsigned long flags;
int success = 0;
/*
* We want the common case fall through straight, thus the goto.
*/
spin_lock_irqsave(&runqueue_lock, flags);
p->state = TASK_RUNNING; // change the process state
if (task_on_runqueue(p)) //如果已经在可运行队列中了,就可以退出了。
goto out;
add_to_runqueue(p); //added the tail of the running queue
if (!synchronous || !(p->cpus_allowed & (1UL << smp_processor_id())))
reschedule_idle(p);
success = 1;
out:
spin_unlock_irqrestore(&runqueue_lock, flags);
return success;
}
2.6.x 用的是这样的, 完全没有用try_to_wake_up()
flags = curr->flags;
if (curr->func(curr, mode, sync, key) &&
(flags & WQ_FLAG_EXCLUSIVE) &&
!--nr_exclusive) //变成 0 ,也就是当nr_exclusive = 1的情况下,就跳出
break;
-----------------------
关于自旋锁的学习:
spinlock的愿意和目的是保证数据修改的原子性(不被其他的CPU中断) , 所以 没有理由的spinlock锁住的临界区停住。
而且,一旦代码被spinlock锁住,将不能进行进程切换,进行空转 ,知道获得锁为止 。 被spinlock锁住的临界区,不能停 , 更不能进行进程切换。
也就是说 , 执行spinlock后, 进程讲独占CPU,直到获得锁为止 。
以下是我的理解:
1>进程在用spinlock()后获得了lock后, 进程仍有可能被切换, 这个没有关系, 因为如果切换到的进程要修改上个结构,仍要获得锁 ,这样不就自旋了吗?这样进程反而不会切换了, 造成死锁了。
2>
经常用的写法
#define INT_MAX ((int)(~0U>>1))
#define INT_MIN (-INT_MAX - 1)
#define UINT_MAX (~0U)
#define LONG_MAX ((long)(~0UL>>1))
#define LONG_MIN (-LONG_MAX - 1)
#define ULONG_MAX (~0UL)
关于 linux kernel 2.6.x 多了一个 timer的函数:
static inline void setup_timer(struct timer_list * timer,
void (*function)(unsigned long),
unsigned long data)
{
timer->function = function;
timer->data = data;
init_timer(timer);
}
注意kernel里面的GCC 扩展用法:
/*
* min()/max() macros that also do
* strict type-checking.. See the
* "unnecessary" pointer comparison.
*/
#define min(x,y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
#define max(x,y) ({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x > _y ? _x : _y; })
#define abs(x) ({ \
int __x = (x); \
(__x < 0) ? -__x : __x; \
})
kernel很多的 这样的代码: 对于一个结构, 有编译期初始化,还有运行期初始化的。
工作队列任务可以在编译时或者运行时创建。任务需要封装为一个叫做 work_struct 的结构体。
在编译期初始化一个工作队列任务时要用到:
DECLARE_WORK(name, void (*function)(void *), void *data);
在这里 name 是 work_struct 的名字,function 是当任务被调度时调用的函数,data 是指向那个函数的指针。
在运行期初始化一个工作队列时要用到:
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
---------------------
其他的比较高级的功能(跟module有关,与driver无关,实际上还是改内核)
intercepting system call (截获系统调用) , 即重新实现某个系统调用 ,比如sys_open
这个实现起来比较容易 ,可以参照 syscall.c 来实现自己的截获。
另外,除了传统的加系统调用的方法(直接编译到kernel中) , 还可以利用module的方式来实现自己的系统调用
---------------
注意put_user() 和 get_user() 都是可能sleep的, 所以, 不可以在中断禁止后,调用他们,这样他们永远不会醒过来!
中断禁止后, 意味着停止了进程切换,如果这个时候当前进程sleep住了, 那么将不会被唤醒 。 差不多死锁了。
所以在中断被禁掉的地方, 一定不能调用可能sleep的函数, 比如 sleep_on xxx () , kmalloc(size,KFP_KERNEL) (这个分配内存的函数可能会睡眠,这个时候, 应该使用 kmalloc(size,GFP_ATOMIC)
-------------
很多时候,我们熟悉怎么在user space中写应用程序, 比如发signal , 那么signal() , kill() ,send_queue 那么在kernel中肯定有对应系统调用服务历程, 那么这样子, 如果我们想要在kernel中发信号, 那就可以调用do_kill() 甚至,还可以send_sig_info() 什么的更底层函数。
还比如fork() 对应的do_fork() , 那么就可以用do_fork() 创建进程了。
还有常用的do_gettimeofday() 函数, 在kernel编程中也是常用的。
------------------------------------------
在kernel中编程中的注意事项:
1>没有内存保护。 可能会破坏内核映象。
2>x86 只有6k作用的内核堆栈 ,而且要和中断程序, 所以不要使用太多的局部变量。
3>已经禁止了中断,却在这后面调用了可能睡眠的函数, 比如 ,kmalloc(size,GFP_KERNEL);
copy_to_user/copy_from_user/verify_area/memcpy_tofs/memcpy_fromfs/ 等等这些
还有put_user/get_user 这些在外国人的unreliable kernel hacking 倒是有简单的介绍。
--------------------
其实真正有意思的,还是改内核, 而不是driver。
--------
例子:
DECLARE_WAIT_QUEUE_HEAD(WaitQ);
一旦阻塞:
wait_event_interruptible(WaitQ, ! Already_Open);
阻塞系统调用的例子:
这里要涉及到等待队列(WaitQ) ,和唤醒机制。
------------------
关于module 本身的module.h
#ifdef CONFIG_MODULE_UNLOAD
unsigned int module_refcount(struct module *mod);
void __symbol_put(const char *symbol);
#define symbol_put(x) __symbol_put(MODULE_SYMBOL_PREFIX #x)
void symbol_put_addr(void *addr);
/* Sometimes we know we already have a refcount, and it's easier not
to handle the error case (which only happens with rmmod --wait). */
static inline void __module_get(struct module *module)
{
if (module) {
BUG_ON(module_refcount(module) == 0);
local_inc(&module->ref[get_cpu()].count);
put_cpu();
}
}
static inline int try_module_get(struct module *module)
{
int ret = 1;
if (module) {
unsigned int cpu = get_cpu();
if (likely(module_is_live(module)))
local_inc(&module->ref[cpu].count);
else
ret = 0;
put_cpu();
}
return ret;
}
static inline void module_put(struct module *module)
{
if (module) {
unsigned int cpu = get_cpu();
local_dec(&module->ref[cpu].count);
/* Maybe they're waiting for us to drop reference? */
if (unlikely(!module_is_live(module)))
wake_up_process(module->waiter);
put_cpu();
}
}
阅读(1813) | 评论(0) | 转发(0) |