Notes d'étude sur le développement de pilotes Linux [9]: E / S bloquantes et non bloquantes sous Linux

table des matières

Un, E / S bloquantes et non bloquantes

Deux méthodes de blocage et non de blocage d'applications

Trois, file d'attente

1. Attendez la tête de la file d'attente

2. En attente des éléments de la file d'attente

3. Élément de file d'attente ajouter / supprimer la tête de file d'attente

4. Attendez le réveil

5. En attente de l'événement

Quatre, sondage

1. Sélectionnez la fonction

2. Fonction Pol

3. fonction epoll

Cinq, fonction d'opération d'interrogation sous le pilote Linux

Six, exemple d'écriture de programme IO bloquant

1. La méthode d'attente de l'événement

2. La façon d'ajouter des éléments de file d'attente

Sept exemples d'écriture de programme IO non bloquant


Un, E / S bloquantes et non bloquantes

L'E / S se réfère ici à l'entrée / sortie, c'est-à-dire entrée / sortie, qui est l'opération d'entrée / sortie de l'application sur le variateur. Lorsque l'application fonctionne sur le pilote de périphérique, si la ressource de périphérique ne peut pas être obtenue, le blocage d'E / S suspendra le thread correspondant à l'application jusqu'à ce que la ressource de périphérique puisse être obtenue. Pour les E / S non bloquantes, le thread correspondant à l'application ne se bloquera pas, il continuera d'interroger et d'attendre jusqu'à ce que les ressources du périphérique soient disponibles, ou abandonnera simplement.

Le plus grand avantage du blocage de l'accès est que le processus peut entrer dans l'état dormant lorsque le fichier de périphérique est inopérant, de sorte que les ressources du processeur peuvent être libérées. Cependant, lorsque le fichier de l'appareil peut être exploité, le processus doit être réveillé.En général, le travail de réveil est terminé dans la fonction d'interruption. Le noyau Linux fournit une file d' attente pour réaliser le travail de réveil des processus bloqués

1. Schéma IO de blocage:

 

2. Schéma de principe des E / S non bloquantes:

Deux méthodes de blocage et non de blocage d'applications

Méthode de blocage:

    /*open*/
    fd = open(filename, O_RDWR);
    if (fd < 0){
        printf("open %s failed!!\r\n", filename);
        return -1;
    }
    ret = read(fd, &data, sizeof(data));

Manière non bloquante:

    /*open*/
    fd = open(filename, O_RDWR | O_NONBLOCK);
    if (fd < 0){
        printf("open %s failed!!\r\n", filename);
        return -1;
    }
    ret = read(fd, &data, sizeof(data));

Trois, file d'attente

1. Attendez la tête de la file d'attente

Pour utiliser une file d'attente dans le pilote, une tête de file d'attente doit être créée et initialisée. La tête de file d'attente est représentée par la structure wait_queue_head_t . La structure wait_queue_head_ t est définie dans le fichier include / linux / wait.h. Le contenu de la structure est le suivant:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

Après avoir défini la tête de file d'attente, elle doit être initialisée. Utilisez la fonction init_waitqueue_head pour initialiser la tête de file d'attente.

void init_waitqueue_head(wait_queue_head_t *q)

2. En attente des éléments de la file d'attente

La tête de la file d'attente est la tête d'une file d'attente. Chaque processus accédant au périphérique est un élément de file d'attente . Lorsque le périphérique n'est pas disponible, les éléments de la file d'attente correspondant à ces processus doivent être ajoutés à la file d'attente. Utilisez la macro DECLARE_WAITQUEUE pour définir et initialiser un élément de file d'attente. Le contenu de la macro est le suivant:

DECLARE_WAITQUEUE(name, tsk)

name est le nom de l'élément de file d'attente, tsk indique à quelle tâche (processus) appartient cet élément de file d'attente et est généralement défini sur current . Dans le noyau Linux, current équivaut à une variable globale, qui représente le processus actuel . Par conséquent, la macro DECLARE_WAITQUEUE crée et initialise un élément de file d'attente pour le processus en cours d'exécution.

3. Élément de file d'attente ajouter / supprimer la tête de file d'attente

Lorsque le périphérique est inaccessible, l'élément de file d'attente correspondant au processus doit être ajouté à la tête de file d'attente précédemment créée, et le processus ne peut entrer en état de veille qu'après avoir été ajouté à la tête de file d'attente. Lorsque le périphérique est accessible, supprimez l'élément de file d'attente correspondant au processus de la tête de file d'attente.

(1) Ajouter l'API d'élément de file d'attente:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q : La tête de la file d'attente où l'élément de file d'attente sera ajouté.

wait : l'élément de file d'attente à ajouter.

(2) Supprimez l'API de l'élément de file d'attente:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q : La tête de la file d'attente où l'élément de file d'attente sera ajouté.

wait : L'élément de la file d'attente à supprimer.

4. Attendez le réveil

Lorsque l'appareil est prêt à être utilisé, il est nécessaire de réactiver le processus qui est entré en état de veille. Les deux fonctions suivantes peuvent être utilisées pour se réveiller:

void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q)

Le paramètre q est la tête de la file d'attente à réveiller Ces deux fonctions vont réveiller tous les processus en tête de la file d'attente. La fonction wake_up peut réveiller les processus dans les états TASK_INTERRUPTIBLE et TASK_UNINTERRUPTIBLE, tandis que la fonction wake_up_interruptible peut uniquement réveiller les processus dans l'état TASK_INTERRUPTIBLE.

5. En attente de l'événement

En plus de vous réveiller activement, vous pouvez également définir la file d'attente pour attendre un événement. Lorsque cet événement est satisfait, le processus de la file d'attente sera automatiquement réveillé. Les fonctions de l'API liées aux événements en attente sont les suivantes:

wait_event (wq, condition)

Attente que la file d'attente avec wq en tête de la file d'attente soit réveillée, à condition que la condition soit remplie (true), sinon elle a été bloquée. Cette fonction mettra le processus à l'état TASK_UNINTERRUPTIBLE

wait_event_timeout (wq, condition, délai d'expiration)

La fonction est similaire à wait_event, mais cette fonction peut ajouter un délai d'expiration en quelques secondes. Cette fonction a une valeur de retour. Si elle renvoie 0, cela signifie que le délai d'expiration est écoulé et que la condition est fausse. S'il vaut 1, la condition est vraie, c'est-à-dire que la condition est satisfaite.

wait_event_interruptible (wq, condition)

Similaire à la fonction wait_event, mais cette fonction définit le processus sur TASK_INTERRUPTIBLE, ce qui signifie qu'il peut être interrompu par un signal.

wait_event_interruptible_timeout (wq, condition, délai)

Similaire à la fonction wait_event_timeout, cette fonction définit également le processus sur TASK_INTERRUPTIBLE, qui peut être interrompu par un signal.

Quatre, sondage

Si l'application utilisateur accède au périphérique de manière non bloquante, le pilote de périphérique doit fournir une méthode de traitement non bloquante, à savoir l'interrogation . L'interrogation, l'epoll et la sélection peuvent être utilisées pour gérer l'interrogation. L'application utilise les fonctions de sélection, d'epoll ou d'interrogation pour demander si l'appareil peut être utilisé, et s'il peut être utilisé, il lit depuis l'appareil ou écrit des données sur l'appareil. Lorsque l'application appelle la fonction select, epoll ou poll, la fonction poll dans le pilote de périphérique sera exécutée, il est donc nécessaire d'écrire la fonction poll dans le pilote de périphérique. Regardons d'abord les fonctions select, epoll ou poll utilisées dans l'application

1. Sélectionnez la fonction

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

nfds : parmi les trois types d'ensembles de descriptions de fichiers à surveiller, le plus grand descripteur de fichier plus un.

readfds, writefds et exceptfds : ces trois pointeurs pointent vers le jeu de descripteurs. Ces trois paramètres indiquent quels descripteurs sont concernés, quelles conditions doivent être remplies, etc., ces trois paramètres sont de type fd_set, et chaque bit de la variable de type fd_set Les deux représentent un descripteur de fichier. readfds est utilisé pour surveiller les modifications de lecture de l'ensemble de descripteurs spécifié, c'est-à-dire pour contrôler si ces fichiers peuvent être lus. Tant qu'il y a un fichier dans ces ensembles qui peut être lu, seclect renvoie une valeur supérieure à 0 pour indiquer que le fichier peut être lu. S'il n'y a pas de fichier à lire, il déterminera s'il a expiré en fonction du paramètre timeout. Vous pouvez définir readfs sur NULL, ce qui signifie que vous ne vous souciez pas des modifications de lecture de fichier. writefds est similaire à readfs, sauf que writefs est utilisé pour contrôler si ces fichiers peuvent être écrits. exceptfds est utilisé pour surveiller les anomalies de ces fichiers.

Par exemple, si nous voulons lire des données à partir d'un fichier de périphérique, nous pouvons définir une variable fd_set , qui doit être passée au paramètre readfds. Après avoir défini une variable fd_set, nous pouvons utiliser les macros suivantes pour fonctionner:

void FD_ZERO(fd_set *set) 
void FD_SET(int fd, fd_set *set) 
void FD_CLR(int fd, fd_set *set) 
int FD_ISSET(int fd, fd_set *set)

2. Fonction Pol

Dans un seul thread, le nombre de descripteurs de fichiers que la fonction de sélection peut surveiller a la plus grande limite, généralement 1024. Vous pouvez modifier le noyau pour augmenter le nombre de descripteurs de fichiers pouvant être surveillés, mais cela réduira l'efficacité! À ce stade, vous pouvez utiliser la fonction d'interrogation. La fonction d'interrogation n'est essentiellement pas différente de select, mais la fonction d'interrogation n'a pas de limite maximale de descripteur de fichier.

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

L' ensemble des descripteurs de fichiers à surveiller par fds et les événements à surveiller sont un tableau, et les éléments du tableau sont tous des structures

struct pollfd {

     int fd; / * descripteur de fichier * /

     événements courts; / * événement demandé * /

     revents courts; / * Evénements retournés * /

};

les événements sont les événements à surveiller. Les types d'événements qui peuvent être surveillés sont les suivants:

POLLIN a des données à lire.

POLLPRI a des données urgentes à lire.

POLLOUT peut écrire des données.

Une erreur s'est produite dans le descripteur de fichier spécifié par POLLERR.

Le descripteur de fichier spécifié par POLLHUP est suspendu.

POLLNVAL demande non valide.

POLLRDNORM équivaut à POLLIN

nfds : le nombre de descripteurs de fichiers à surveiller par la fonction d'interrogation.

timeout : délai d'expiration, en ms.

3. fonction epoll

Les fonctions traditionnelles de sélection et d'interrogation souffriront d'inefficacité à mesure que le nombre de fd surveillés augmente, et la fonction d'interrogation doit parcourir tous les descripteurs à chaque fois pour vérifier les descripteurs prêts, ce qui est une perte de temps. Pour cette raison, epoll a vu le jour. Epoll est prêt à gérer une grande concurrence, et les fonctions epoll sont souvent utilisées dans la programmation réseau. L'application doit d'abord utiliser la fonction epoll_create pour créer un handle epoll

int epoll_create(int size)

Cinq, fonction d'opération d'interrogation sous le pilote Linux

Lorsque l'application appelle la fonction de sélection ou d'interrogation pour effectuer un accès non bloquant au pilote, la fonction d'interrogation dans l'ensemble d'opérations file_operations du pilote est exécutée. Par conséquent, le rédacteur du pilote doit fournir la fonction d'interrogation correspondante. Le prototype de la fonction d'interrogation est le suivant:

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

filp : Le fichier de périphérique (descripteur de fichier) à ouvrir.

wait : pointeur vers la structure poll_table_struct, transmis par l'application. Généralement, ce paramètre est passé à la fonction poll_wait.

Six, exemple d'écriture de programme IO bloquant

https://github.com/denghengli/linux_driver/tree/master/15_blockio

1. La méthode d'attente de l'événement

#include <linux/wait.h> 
#include <linux/ide.h> 

/*定时器回调函数,在设备资源可用的时候唤醒等待队列中的线程*/
static void timer_func(unsigned long arg)
{
    struct key_dev *dev = (struct key_dev*)arg;
    ...
    wake_up(&dev->r_wait);/*唤醒等待队列中的线程*/
}
static  ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    ...
    /*等待等待队列中的 reles_value 事件,使访问的线程进入阻塞*/
    wait_event_interruptible(dev->r_wait, reles_value);
    ...
    return ret;
}
static int key_open(struct inode *inode, struct file *filp)
{ 
    ...
    /*初始化等待队列头*/  
    init_waitqueue_head(&dev->r_wait);
    return 0;
}

2. La façon d'ajouter des éléments de file d'attente

#include <linux/wait.h> 
#include <linux/ide.h> 

/*定时器回调函数,在设备资源可用的时候唤醒等待队列中的线程*/
static void timer_func(unsigned long arg)
{
    struct key_dev *dev = (struct key_dev*)arg;
    ...
    wake_up(&dev->r_wait);/*唤醒等待队列中的线程*/
}
static  ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    ...
	/*定义并初始化一个等待队列项*/
	DECLARE_WAITQUEUE(wait, current);
	if (reles_value == 0){
        add_wait_queue(&dev->r_wait, &wait);//将队列项加入等待队列
        __set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态,设置为可被打断的状态,可以接受信号 */
        schedule(); /* 进行一次任务切换 */
        
        /*等待被唤醒,可能被信号和可用唤醒*/
        if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
        __set_current_state(TASK_RUNNING); /*设置为运行状态 */
        remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
        return -ERESTARTSYS;
        }
        /*可用设备唤醒*/
        __set_current_state(TASK_RUNNING); /*设置为运行状态 */
        remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
	}
    ...
    return ret;
}
static int key_open(struct inode *inode, struct file *filp)
{ 
    ...
    /*初始化等待队列头*/  
    init_waitqueue_head(&dev->r_wait);
    return 0;
}

Sept exemples d'écriture de programme IO non bloquant

https://github.com/denghengli/linux_driver/tree/master/16_noblockio

 

 

Je suppose que tu aimes

Origine blog.csdn.net/m0_37845735/article/details/107307000
conseillé
Classement