Linux ドライバーを作成するプロセスでは、デバイスのブロッキング/ノンブロッキング読み取りおよび書き込みは非常に重要なテクノロジです。効率的なデータ転送とイベント処理を実現し、システムのパフォーマンスと応答速度を向上させます。今回は、Linuxドライバ技術(5)_デバイスブロッキング/ノンブロッキング読み書きの実装原理と関連技術について掘り下げていきます。
待機キューは、プロセス スケジューリングのためのカーネル内の非常に重要なデータ構造です。そのタスクはリンク リストを維持することです。リンク リストの各ノードは PCB (プロセス コントロール ブロック) です。 カーネルは、PCB の待機キュー内のすべてのプロセスを、特定のウェイクアップ条件が発生するまでスリープするようにスケジュールします。 Linux I/O 多重化の記事でアプリケーション層でのブロッキング IO とノンブロッキング IO の使用についてはすでに説明しましたが、この記事では主にドライバーでデバイス IO のブロッキングおよびノンブロッキングの読み取りと書き込みを実装する方法について説明します。明らかに、このブロッキング関連メカニズムを実装するには、待機キュー メカニズムが必要です。この記事のカーネル ソース コードはバージョン 3.14.0 を使用しています。
インターフェイスは読み取りおよび書き込みデバイス プロセスのプロセス (カーネル) 空間にも表示されます。 、条件が満たされていない場合、デバイスの読み取りおよび書き込みのユーザー プロセスがスリープ状態に入った場合でも、インターフェース関数はプロセスをスリープ状態にします。これはよくブロッキングと呼ばれるものです。一言で言えば、デバイス ファイルの読み取りおよび書き込みのブロックの本質は、ドライバーがドライバー内でデバイス ファイルのブロックを実装することです。読み取りおよび書き込みのプロセスは次のように要約できます。
1. 定義 - 待機キューのヘッドを初期化します
#「
」wait_queue_head_t
–36–>このキューで使用されるスピン ロック–27–>キュー全体を「繋ぐ」リンク
」#次に、初期化マクロを見てみましょう: リーリー
#「
」
DECLARE_WAIT_QUEUE_HEAD()_timeout** のバージョンはタイムアウト後に返されるタイムアウト バージョンを示します。この命名規則はカーネル API のあらゆる場所で見られます。–60–>受信文字列名に基づいて name という名前の待機キュー ヘッドを作成します –57–>上記の task_list フィールドの初期化には、カーネル標準の初期化マクロは使用されません。 。 。
」
#2. このプロセスを待機キューに追加しますイベントを待機キューに追加します。つまり、プロセスはスリープ状態に入り、条件が true になるまで戻りません。 **_interruptible
のバージョンはスリープを中断できることを示し、
“
wait_event
└── wait_event
└── _wait_event
├── abort_exclusive_wait
├── finish_wait
├── prepare_to_wait_event
└── ___wait_is_interruptible”
244 #define wait_event(wq, condition) \ 245 do { \ 246 if (condition) \ 247 break; \ 248 __wait_event(wq, condition); \ 249 } while (0)
“
wait_event
–246–>如果condition为真,立即返回
–248–>否则调用__wait_event”
194 #define ___wait_event(wq, condition, state, exclusive, ret, cmd) \ 195 ({ \ 206 for (;;) { \ 207 long __int = prepare_to_wait_event(&wq, &__wait, state);\ 208 \ 209 if (condition) \ 210 break; \ 212 if (___wait_is_interruptible(state) && __int) { \ 213 __ret = __int; \ 214 if (exclusive) { \ 215 abort_exclusive_wait(&wq, &__wait, \ 216 state, NULL); \ 217 goto __out; \ 218 } \ 219 break; \ 220 } \ 222 cmd; \ 223 } \ 224 finish_wait(&wq, &__wait); \ 225 __out: __ret; \ 226 })
“
___wait_event
–206–>死循环的轮询
–209–>如果条件为真,跳出循环,执行finish_wait();进程被唤醒
–212–>如果进程睡眠的方式是interruptible的,那么当中断来的时候也会abort_exclusive_wait被唤醒
–222–>如果上面两条都不满足,就会回调传入的schedule(),即继续睡眠”
struct wait_queue_head_t xj_waitq_h; static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset) { if(!condition) //条件可以在中断处理函数中置位 wait_event_interruptible(&xj_waitq_h,condition); } static file_operations fops = { .read = demo_read, }; static __init demo_init(void) { init_waitqueue_head(&xj_waitq_h); }
对于普通的非阻塞IO,我们只需要在驱动中注册的read/write接口时不使用阻塞机制即可,这里我要讨论的是IO多路复用,即当驱动中的read/write并没有实现阻塞机制的时候,我们如何利用内核机制来在驱动中实现对IO多路复用的支持。下面这个就是我们要用的API
int poll(struct file *filep, poll_table *wait); void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
当应用层调用select/poll/epoll机制的时候,内核其实会遍历回调相关文件的驱动中的poll接口,通过每一个驱动的poll接口的返回值,来判断该文件IO是否有相应的事件发生,我们知道,这三种IO多路复用的机制的核心区别在于内核中管理监视文件的方式,分别是位,数组,链表,但对于每一个驱动,回调的接口都是poll。
struct wait_queue_head_t waitq_h; static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts) { unsigned int mask = 0; poll_wait(filp, &wwaitq_h, pts); if(counter){ mask = (POLLIN | POLLRDNORM); } return mask; } static struct file_operations fops = { .owner = THIS_MODULE, .poll = demo_poll, }; static __init demo_init(void) { init_waitqueue_head(&xj_waitq_h); }
刚才我们讨论了如何使用等待队列实现阻塞IO,非阻塞IO,其实关于等待队列,内核还提供了很多其他API用以完成相关的操作,这里我们来认识一下
//在等待队列上睡眠 sleep_on(wait_queue_head_t *wqueue_h); sleep_on_interruptible(wait_queue_head_t *wqueue_h); //唤醒等待的进程 void wake_up(wait_queue_t *wqueue); void wake_up_interruptible(wait_queue_t *wqueue);
总之,设备阻塞/非阻塞读写是Linux驱动程序编写过程中不可或缺的一部分。它可以实现高效的数据传输和事件处理,提高系统的性能和响应速度。希望本文能够帮助读者更好地理解Linux驱动技术(五) _设备阻塞/非阻塞读写的实现原理和相关技术。
以上がLinuxドライバ技術の詳細解説(5)_デバイスブロッキング/ノンブロッキング読み書きの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。