1. ioctl
这其实是个杂物箱,不推荐使用。因为每传递不同的参数就等于一个独立的系统调用。推荐使用sysfs。
2. 阻塞型I/O
驱动程序在调用read\write时,应该能实现类型于linux系统调用read/write时的阻塞行为。
要阻塞进程,要明确能让进程安全进入阻塞状态的两个规则:
1是不能在原子性的操作中阻塞,如拥有自旋锁、seqlock、rcu锁、禁止中断时也不可以。持有信号量时可以阻塞,但影响性能;
2是阻塞进程被唤醒后,处于一个未知的状态,不知自己已睡多久也不知等待的事件到底是否为真,所以必须检查等待事件是否真已发生。
阻塞进程的task_struct会在一个“等待队列”上。内核代码如下初始化一个等待队列:
DECLARE_WAIT_QUEUE_HEAD(name);
内核代码想要将当前进程阻塞可调用
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
以上函数将在条件condition不满足时,将当前进程加入等待队列queue,并根据是否可中断将进程状态置为TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE,最后调用shedule进行调度。
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \ ----这里判断是为了防止在加入等待队列前,等待条件已经发生。如果不判断,则会错过这次事件。
break; \ ----break之前事件发生没问题。因为这里进程已经加入等待队列了。
if (!signal_pending(current)) { \
schedule(); \
另一面,阻塞的进程可通过其他执行线程(非用户态线程,指内核某个执行代码)唤醒,通常调用如下:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
它们会唤醒在这个queue上阻塞的所有进程。如果不需要全部唤醒,可以设置独占等待。独占等待的进程只会唤醒第一个。
这里注意有个竞争条件,就是对应资源如果只能被用一次,那么被唤醒的进程会有竞争,在内核中这种问题较难调试。
I/O缓冲区在此引入,read/write可不需要直接与设备直接交换数据,而是与驱动程序维护的缓冲区进行I/O,这会减少用户和内核态切换显著提高效率。
如:write到慢速设备时,如果少量数据时要等待设备,则阻塞了。被唤醒后再写入少量数据又阻塞,如此反复时,内核不停在调度本进程。如果实现了缓冲区,那么第一次write直接写到缓冲区后返回。缓冲的数据可在以后的中断中送给设备。
非阻塞时,stdio要注意返回值可能被误认为EOF。
3. SIGIO信号、异步通知
在flip->f_owner中填入进程id、并在设备中设置FASYNC标识后,输入文件在数据到达后,发送信号SIGIO到进程f_owner。应用程序只能假设socket与终端有异步通知的能力。
阅读(709) | 评论(0) | 转发(0) |