Heim  >  Artikel  >  System-Tutorial  >  Detaillierte Erläuterung der Linux-Treibertechnologie (5)_Geräteblockierendes/nichtblockierendes Lesen und Schreiben

Detaillierte Erläuterung der Linux-Treibertechnologie (5)_Geräteblockierendes/nichtblockierendes Lesen und Schreiben

WBOY
WBOYnach vorne
2024-02-15 16:00:23388Durchsuche

Beim Schreiben von Linux-Treibern ist das blockierende/nicht blockierende Lesen und Schreiben von Geräten eine sehr wichtige Technologie. Es kann eine effiziente Datenübertragung und Ereignisverarbeitung erreichen und so die Systemleistung und Reaktionsgeschwindigkeit verbessern. In diesem Artikel befassen wir uns mit der Linux-Treibertechnologie (5)_Die Implementierungsprinzipien und verwandte Technologien zum Blockieren/Nichtblockieren von Lesen und Schreiben von Geräten.

详解Linux驱动技术(五) _设备阻塞/非阻塞读写

Wartewarteschlange ist eine sehr wichtige Datenstruktur im Kernel für die Prozessplanung. Ihre Aufgabe besteht darin, eine verknüpfte Liste zu verwalten. Der Kernel hängt die Leiterplatte ein die Warteschlange Alle Prozesse in sind so lange in den Ruhezustand versetzt, bis eine bestimmte Aufwachbedingung eintritt. Die Verwendung von blockierenden und nicht blockierenden E/A auf der Anwendungsebene habe ich bereits im Artikel „Linux-E/A-Multiplexing“ besprochen. In diesem Artikel wird hauptsächlich die Implementierung des blockierenden und nicht blockierenden Lesens und Schreibens von Geräte-E/A im Treiber erläutert. Offensichtlich ist der Warteschlangenmechanismus erforderlich, um diesen blockierenden Mechanismus zu implementieren. Der Kernel-Quellcode dieses Artikels verwendet Version 3.14.0

Implementierung von Geräteblockierungs-IO

Wenn wir die E/A der Gerätedatei lesen und schreiben, wird die entsprechende Schnittstelle im Treiber schließlich zurückgerufen, und diese Schnittstellen werden auch im Prozessbereich (Kernel) des Lese- und Schreibgeräteprozesses angezeigt Wenn die Bedingungen nicht erfüllt sind, führt die Schnittstellenfunktion dazu, dass der Prozess in den Ruhezustand wechselt. Auch wenn der Benutzerprozess des Lesens und Schreibens des Geräts in den Ruhezustand wechselt, wird dies oft als blockiert bezeichnet. Kurz gesagt besteht das Wesentliche beim Blockieren von Lese- und Schreibgeräten darin, dass der Treiber das Blockieren von Gerätedateien im Treiber implementiert. Der Lese- und Schreibvorgang kann wie folgt zusammengefasst werden:

1. Definition – Warteschlangenkopf initialisieren
//定义等待队列头
wait_queue_head_t waitq_h;
//初始化,等待队列头
init_waitqueue_head(wait_queue_head_t *q);
 //或
//定义并初始化等待队列头
DECLARE_WAIT_QUEUE_HEAD(waitq_name);

Unter den oben genannten Optionen definiert und initialisiert die letzte direkt einen Warteheader. Wenn Sie jedoch globale Variablen verwenden, um Parameter innerhalb des Moduls zu übergeben, hängt es von Ihren Anforderungen ab, welche Sie verwenden.

Wir können den Quellcode verfolgen und sehen, was die obigen Zeilen bewirken:

//include/linux/wait.h 
 35 struct __wait_queue_head { 
 36         spinlock_t              lock;
 37         struct list_head        task_list;
 38 };
 39 typedef struct __wait_queue_head wait_queue_head_t;

wait_queue_head_t –36–>Der von dieser Warteschlange verwendete Spin-Lock
–27–>Der Link, der die gesamte Warteschlange „aneinanderreiht“

Dann werfen wir einen Blick auf das Initialisierungsmakro:

 55 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                           \
 56         .lock           = __SPIN_LOCK_UNLOCKED(name.lock),              \
 57         .task_list      = { &(name).task_list, &(name).task_list } }
 58 
 59 #define DECLARE_WAIT_QUEUE_HEAD(name) \
 60         wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

DECLARE_WAIT_QUEUE_HEAD() –60–>Erstellen Sie einen Warteschlangenkopf mit dem Namen „name“ basierend auf der eingehenden Zeichenfolge „name
“. –57–>Um das obige task_list-Feld zu initialisieren, wird das Kernel-Standardinitialisierungsmakro nicht verwendet. Ich bin sprachlos. . .

2. Fügen Sie diesen Vorgang zur Warteschlange hinzu

Fügen Sie Ereignisse zur Warteschlange hinzu, d. h. der Prozess wechselt in den Ruhezustand und kehrt erst zurück, wenn die Bedingung wahr ist. Die Version von **_interruptible

gibt an, dass der Ruhezustand unterbrochen werden kann, und die Version von _timeout** gibt die Timeout-Version an, die zurückgegeben wird, wenn ein Timeout auftritt. Diese Namenskonvention ist überall in der Kernel-API zu sehen.

void wait_event(wait_queue_head_t *waitq_h,int condition);
void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition);
void wait_event_timeout(wait_queue_head_t *waitq_h,int condition);
void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition);

Das ist der Kern der Warteschlange, werfen wir einen Blick darauf

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多路复用的实现

对于普通的非阻塞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);
}

其他API

刚才我们讨论了如何使用等待队列实现阻塞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驱动技术(五) _设备阻塞/非阻塞读写的实现原理和相关技术。

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der Linux-Treibertechnologie (5)_Geräteblockierendes/nichtblockierendes Lesen und Schreiben. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:lxlinux.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen