Explication détaillée des fonctions du framework de pilotes

Le cadre de l’article est basé sur le chapitre précédent

https://blog.csdn.net/qq_52749711/article/details/132409329
sont presque les mêmes. Les noms ici ont été modifiés, mais ils restent fidèles à leurs origines.

fonction d'entrée module_init

Comme la fonction principale en mode utilisateur, elle appelle la fonction entre parenthèses à exécuter, et la fonction à l'intérieur est la fonction d'entrée.
Par exemple :

static int  pin5_drv_init(void);//函数声明
module_init(pin5_drv_init)

La fonction contient du code tel que le pilote d'enregistrement

fonction de sortie module_exit

Fonctions qui doivent être exécutées après l'exécution, le principe est le même que ci-dessus

module_exit(pin5_drv_exit);

La fonction contient du code pour désinstaller le pilote, etc.

registre_chrdev

register_chrdevLa fonction est l'une des fonctions du noyau Linux utilisées pour enregistrer les pilotes de périphériques de caractères. Un périphérique de caractères est un périphérique qui interagit avec des flux de caractères, tels que des terminaux, des ports série, etc. Dans le noyau Linux, le pilote de périphérique de caractères implémente des opérations telles que la lecture et l'écriture de périphériques de caractères via des fonctions d'opération de fichiers.

La fonction est généralement déclarée comme suit :

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

Description du paramètre :

major: Spécifiez le numéro de périphérique principal. Pour les périphériques de type caractère, le numéro de périphérique principal est le numéro qui identifie de manière unique un pilote de périphérique. Le numéro principal du périphérique peut MAJOR(dev_t dev)être obtenu via une macro.
name: Spécifiez le nom du périphérique, utilisé pour afficher le nom du périphérique dans /proc/devices.
fops: file_operationsPointeur vers une structure qui contient les fonctions opérationnelles du pilote de périphérique de caractères, telles que la lecture, l'écriture, l'ouverture, la fermeture, etc.

valeur de retour :

En cas d'enregistrement réussi, le numéro de périphérique principal attribué est renvoyé.
Lorsque l'enregistrement échoue, une valeur négative est renvoyée, généralement un code d'erreur.
Après avoir utilisé register_chrdevla fonction pour enregistrer le pilote de périphérique de caractères, vous devez appeler cette fonction dans la fonction d'initialisation du module. Par exemple:

#include <linux/module.h>
#include <linux/fs.h>

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // Open operation implementation
    return 0;
}

static int my_chardev_release(struct inode *inode, struct file *file)
{
    
    
    // Release operation implementation
    return 0;
}

static struct file_operations my_fops = {
    
    
    .open = my_chardev_open,
    .release = my_chardev_release,
    // Other operation implementations
};

static int __init my_chardev_init(void)
{
    
    
    int major = register_chrdev(0, "my_chardev", &my_fops);
    if (major < 0) {
    
    
        printk(KERN_ALERT "Failed to register char device\n");
        return major;
    }
    printk(KERN_INFO "Registered char device with major number %d\n", major);
    return 0;
}

static void __exit my_chardev_exit(void)
{
    
    
    unregister_chrdev(major, "my_chardev");
    printk(KERN_INFO "Unregistered char device\n");
}

module_init(my_chardev_init);
module_exit(my_chardev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Character Device Driver");

my_chardev_open (fonction ouverte)

my_chardev_openIl s'agit d'une fonction du pilote de périphérique de caractères, utilisée pour gérer l'opération d'ouverture du périphérique. Lors de l'enregistrement d'un périphérique de caractères, vous devez struct file_operationsdéfinir un pointeur vers votre propre fonction d'ouverture implémentée dans la structure. Le noyau appellera cette fonction lorsque le périphérique sera ouvert.

Voici la définition de la fonction my_chardev_open :

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // Open operation implementation
    return 0;
}

Dans cette fonction, vous pouvez effectuer des tâches liées à l'opération d'ouverture de l'appareil, telles que l'initialisation de l'état de l'appareil, l'allocation de ressources, l'enregistrement du nombre d'ouvertures de l'appareil, etc. Cette fonction recevra deux paramètres :

struct inode *inode: Il s'agit d'un pointeur vers le nœud d'index (inode) du fichier et contient des informations de métadonnées sur le fichier. Lorsque le périphérique est ouvert, le noyau transmet l'inode correspondant à la fonction open.

  • struct inode *inode:

  • inode(Nœud d'index) contient les informations de métadonnées du fichier, telles que les autorisations, la taille, l'utilisateur, etc. Il identifie de manière unique un fichier dans le système de fichiers.
    Vous pouvez utiliser i_modeles champs pour obtenir les informations d'autorisation du fichier, et les champs i_uidet i_gidpour obtenir les ID d'utilisateur et de groupe du fichier.

  • i_privateLes champs peuvent être utilisés pour stocker des données privées liées à l'appareil. Vous pouvez définir ce champ lors de l'initialisation du pilote.

struct file *file: Il s'agit d'un pointeur vers une structure de données représentant le fichier. Il contient des informations relatives aux opérations sur les fichiers, telles que le mode d'accès, l'emplacement du fichier, etc.

  • fichier de structure *fichier:

  • La structure du fichier contient des informations relatives à l'ouverture du fichier, telles que l'emplacement du fichier, le mode d'accès, etc.

  • Le champ f_pos indique le décalage de position actuel du fichier.

  • Le champ f_flags contient les indicateurs utilisés lors de l'ouverture du fichier, tels que lecture, écriture, ajout, etc.

  • Le champ f_mode contient le mode d'accès lors de l'ouverture du fichier, qui peut être déterminé par des opérations sur les bits.

  • Le champ private_data peut être utilisé pour stocker des données privées liées aux opérations sur les fichiers, et vous pouvez le définir lors de l'ouverture du fichier.

La valeur de retour d'une fonction est un entier, généralement utilisé pour indiquer si l'opération a réussi. Si l'opération d'ouverture réussit, il est d'usage de renvoyer 0, indiquant qu'aucune erreur ne s'est produite. Si une erreur se produit, une valeur négative peut être renvoyée, correspondant à un code d'erreur différent.

Voici un exemple montrant comment my_chardev_openimplémenter l’opération open dans une fonction :

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // Perform device-specific tasks during open, if any
    printk(KERN_INFO "Device opened\n");
    
    // Increment the device's open count (if you want to track it)
    try_module_get(THIS_MODULE);
    
    return 0; // Return 0 to indicate success
}

Dans l'exemple ci-dessus, nous utilisons printkune fonction pour générer un journal indiquant que l'appareil a été ouvert. Si vous souhaitez suivre le nombre de fois qu'un périphérique a été ouvert, vous pouvez try_module_get(THIS_MODULE)incrémenter le nombre de références du module noyau en utilisant . module_put(THIS_MODULE)De cette façon, vous pouvez décrémenter le nombre de références lorsque l'appareil est éteint .

En bref, my_chardev_openla fonction vous permet d'effectuer certaines opérations lorsque le périphérique de caractères est ouvert. Vous pouvez écrire le code d'opération d'ouverture approprié en fonction des caractéristiques et des besoins du périphérique.

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // 访问 inode 信息
    printk(KERN_INFO "文件权限: %o\n", inode->i_mode & 0777);
    printk(KERN_INFO "文件所有者用户ID: %d\n", inode->i_uid.val);
    printk(KERN_INFO "文件所有者组ID: %d\n", inode->i_gid.val);

    // 访问文件信息
    printk(KERN_INFO "文件位置: %lld\n", file->f_pos);
    printk(KERN_INFO "文件标志: %x\n", file->f_flags);

    // 在文件结构中设置 private_data
    file->private_data = /* 在此处添加你的私有数据 */;

    return 0;
}

fonction d'écriture

Le prototype de fonction pin5_writeressemble à la fonction d'opération d'écriture dans le pilote de périphérique de caractères, qui est utilisée pour écrire des données de l'espace utilisateur vers le périphérique. Laissez-moi vous expliquer ce que signifient les paramètres de cette fonction :

file: Il s'agit d'un pointeur de fichier struct représentant un fichier ouvert et il contient des informations relatives au fichier ouvert. Ce paramètre spécifie le fichier à écrire.
buf: Il s'agit d'un pointeur vers un tampon d'espace utilisateur contenant les données à écrire sur le périphérique. __user est un indicateur indiquant qu'il s'agit de données de l'espace utilisateur et qu'elles doivent donc être traitées avec précaution dans l'espace du noyau.
count: C'est le nombre d'octets à écrire, spécifiant la longueur des données dans le tampon.
ppos: Il s'agit d'un pointeur vers le type loff_t, indiquant le décalage de position actuel du fichier. Lors d'une opération d'écriture, le noyau devra peut-être mettre à jour cet emplacement.

La valeur de retour de la fonction est de type ssize_t, indiquant le nombre d'octets écrits, ou si une erreur se produit, une valeur négative est renvoyée, correspondant à un code d'erreur différent.
Voici une implémentation simplifiée d'un exemple de fonction pin5_write pour illustrer son fonctionnement :

static ssize_t pin5_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    
    
        printk("pin5_write\n");
        return 0;
}

Dans le développement réel du pilote, vous devez implémenter la fonction pin5_write en fonction des caractéristiques et des exigences du périphérique. Par exemple, vous pouvez ajouter des opérations telles que l'écriture de données sur le périphérique et la mise à jour des décalages de fichiers selon vos besoins. Dans le même temps, assurez-vous d'une copie et d'une vérification appropriées des données entre l'espace noyau et l'espace utilisateur pour garantir la sécurité et la stabilité.

#include <linux/fs.h>
#include <linux/uaccess.h>

// 假设你的设备在打开时已经被初始化为pin5设备

static ssize_t pin5_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    
    
    ssize_t written = 0;

    // 验证用户空间内存并将数据从用户空间复制到内核空间
    if (!access_ok(VERIFY_READ, buf, count))
        return -EFAULT;

    // 在内核中分配一个临时缓冲区
    char *kernel_buf = kmalloc(count, GFP_KERNEL);
    if (!kernel_buf)
        return -ENOMEM;

    // 从用户空间复制数据到内核缓冲区
    if (copy_from_user(kernel_buf, buf, count)) {
    
    
        kfree(kernel_buf);
        return -EFAULT;
    }

    // 在这里执行将数据写入设备的操作,示例中省略

    // 记录写入的字节数
    written = count;

    // 释放临时分配的内存
    kfree(kernel_buf);

    // 更新文件位置偏移
    *ppos += written;

    return written;
}

Dans le code ci-dessus, nous vérifions d'abord si la mémoire de l'espace utilisateur est accessible et utilisons access_okdes fonctions pour y parvenir. Nous avons ensuite alloué un tampon temporaire dans l'espace noyau et utilisé copy_from_userune fonction pour copier les données de l'espace utilisateur vers l'espace noyau.

La partie suivante devrait être l'opération réelle d'écriture des données du tampon du noyau vers le périphérique. Cela peut impliquer le fonctionnement des registres des appareils, la transmission de données, etc., qui peuvent être mis en œuvre en fonction des caractéristiques de votre appareil.

Enfin, nous enregistrons le nombre d'octets écrits et libérons la mémoire temporairement allouée. De plus, le décalage de position du fichier a été mis à jour pour maintenir la position correcte du fichier après les opérations d'écriture.

Il convient de noter que lors du développement réel du pilote, vous devez le gérer de manière appropriée en fonction des caractéristiques du périphérique, de la version du noyau et des exigences. Il est très important de garantir l'exactitude et la sécurité des données ainsi que la gestion raisonnable de la mémoire.

classe_créer

La fonction class_create est une fonction du noyau Linux utilisée pour créer des classes de périphériques. Les classes d'appareils sont un moyen d'organiser et de gérer les appareils qui permettent de regrouper les appareils associés et de fournir des propriétés et des opérations communes pour ces appareils. Ceci est utile pour la gestion des périphériques de caractères, des périphériques de bloc, etc.

struct class *class_create(struct module *owner, const char *name);

Description du paramètre :

owner: Spécifie le module du noyau qui possède cette classe. Généralement, vous pouvez le définir sur THIS_MODULE, ce qui signifie que le module qui a créé la classe est le module actuel.
name: Spécifiez le nom de la classe de périphérique. Ce nom sera affiché dans le répertoire /sys/class et est utilisé pour identifier la classe de périphérique.
valeur de retour :

En cas de succès, renvoie un pointeur vers la classe de périphérique nouvellement créée.
En cas d'échec, un pointeur d'erreur est renvoyé.
Après avoir appelé class_createla fonction, vous pouvez enregistrer les périphériques concernés dans cette classe, puis le noyau créera les fichiers de périphérique correspondants pour ces périphériques et /sys/classcréera les répertoires correspondants sous le répertoire.

Voici un exemple montrant comment utiliser la fonction class_create :

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static struct class *my_class;

static int __init my_module_init(void)
{
    
    
    my_class = class_create(THIS_MODULE, "my_device_class");
    if (IS_ERR(my_class)) {
    
    
        printk(KERN_ALERT "Failed to create class\n");
        return PTR_ERR(my_class);
    }

    // Create and register devices here

    printk(KERN_INFO "Module initialized\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    
    
    class_destroy(my_class);
    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Device Class Module");

Dans l'exemple ci-dessus, class_createune classe de périphériques nommée est d'abord créée à l'aide d'une fonction "my_device_class". Vous pouvez ensuite ajouter les appareils pertinents à cette classe en les créant et en les enregistrant dans la fonction d'initialisation du module. Lorsque le module se termine, utilisez class_destroyla fonction pour détruire la classe de périphérique.

Il convient de noter que class_create et les fonctions de fonctionnement des périphériques associées sont utilisées pour le développement du module du noyau. Si vous développez un pilote de périphérique caractère ou un autre type de module de noyau, vous pouvez utiliser ces fonctions pour gérer les classes de périphériques et les périphériques selon vos besoins.

périphérique_créer

device_createfunction est une fonction utilisée pour créer un périphérique dans le noyau Linux. Elle /sys/class/crée un sous-répertoire pour les classes de périphériques et crée un fichier de périphérique dans ce sous-répertoire. Cette fonction est généralement class_createutilisée conjointement avec des fonctions permettant d'associer un appareil à une classe d'appareil spécifique.

Voici device_createle prototype de la fonction :

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

Description du paramètre :

class: Spécifiez la classe de périphérique à laquelle appartient le périphérique. Ce paramètre est généralement le pointeur de classe de périphérique renvoyé par la fonction class_create.
parent: Spécifie le périphérique parent du périphérique. Généralement, il peut être défini sur NULL.
devt: Spécifiez le numéro de périphérique de l'appareil. Vous pouvez utiliser la macro MKDEV (majeur, mineur) pour créer le numéro de périphérique.
drvdata: Spécifie le pointeur de données privées de l'appareil.
fmt: Spécifiez le format du nom du périphérique, utilisé pour créer des sous-répertoires et des fichiers de périphérique sous /sys/class/.

valeur de retour :

En cas de succès, renvoie un pointeur vers le périphérique nouvellement créé.
En cas d'échec, un pointeur d'erreur est renvoyé.
Voici un exemple montrant comment utiliser la fonction device_create :

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static struct class *my_class;
static struct device *my_device;

static int __init my_module_init(void)
{
    
    
    my_class = class_create(THIS_MODULE, "my_device_class");
    if (IS_ERR(my_class)) {
    
    
        printk(KERN_ALERT "Failed to create class\n");
        return PTR_ERR(my_class);
    }

    // Create and register devices here

    my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
    if (IS_ERR(my_device)) {
    
    
        printk(KERN_ALERT "Failed to create device\n");
        class_destroy(my_class);
        return PTR_ERR(my_device);
    }

    printk(KERN_INFO "Module initialized\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    
    
    device_destroy(my_class, MKDEV(0, 0));
    class_destroy(my_class);
    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Device Module");

Dans l'exemple ci-dessus, nous class_createcréons d'abord une classe de périphériques à l'aide de fonctions. Ensuite, device_createun appareil nommé sous cette classe a été créé à l’aide de function "my_device". A la sortie du module, nous utilisons device_destroyune fonction pour détruire le périphérique, puis class_destroyune fonction pour détruire la classe de périphérique.

Il convient de noter que device_createles fonctions de fonctionnement des périphériques associées sont utilisées pour le développement du module du noyau. Si vous développez un pilote de périphérique de caractères ou un autre type de module de noyau, vous pouvez utiliser ces fonctions pour créer et gérer des périphériques selon vos besoins.

ioremap

Dans le développement du noyau Linux, ioremaples fonctions sont utilisées pour mapper les adresses physiques à l'espace du noyau afin que le noyau puisse accéder directement aux registres matériels, à la mémoire périphérique, etc. à ces adresses. En effet, l'accès aux registres matériels nécessite généralement l'utilisation d'instructions d'E/S spéciales, et ces instructions ne peuvent être exécutées qu'en mode noyau.

ioremapLe prototype de la fonction est le suivant :

void __iomem *ioremap(resource_size_t phys_addr, size_t size);

Description du paramètre :

phys_addr: L'adresse physique à mapper.
size: La taille de la carte, en octets.
valeur de retour :

En cas de succès, renvoie un pointeur vers la zone cartographiée.
En cas d'échec, un pointeur nul est renvoyé.
Avant d'utiliser ioremapla fonction, vous devez vous assurer que votre adresse physique est valide et n'est pas utilisée par des tiers. Après le mappage, vous pouvez utiliser des pointeurs dans le noyau pour accéder au contenu de la zone mappée, tout comme pour accéder à la mémoire ordinaire.

Ce qui suit est un exemple montrant comment utiliser la fonction ioremap pour accéder aux registres matériels

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>

static void __iomem *hw_regs;

static int __init my_module_init(void)
{
    
    
    // 为物理地址0x12345678映射一个大小为4字节的映射区域
    hw_regs = ioremap(0x12345678, 4);
    if (!hw_regs) {
    
    
        printk(KERN_ALERT "Failed to remap hardware registers\n");
        return -ENOMEM;
    }

    // 使用映射后的指针来访问寄存器
    unsigned int reg_value = readl(hw_regs);
    printk(KERN_INFO "Hardware register value: %u\n", reg_value);

    printk(KERN_INFO "Module initialized\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    
    
    // 取消映射
    iounmap(hw_regs);

    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample I/O Remapping Module");

Dans l'exemple ci-dessus, nous utilisons d'abord ioremapune fonction pour 0x12345678mapper l'adresse physique à l'espace du noyau, puis utilisons readlla fonction pour lire la valeur du registre matériel à partir de la zone mappée. iounmapUtilisez des fonctions pour démapper lorsque le module se ferme .

Il convient de noter que ioremap et les fonctions associées sont généralement utilisées pour la programmation matérielle de bas niveau et doivent être utilisées avec prudence pour garantir que vous respectez les spécifications et les exigences du périphérique matériel lors de l'accès à la zone mappée.

MODULE_LICENSE("GPL");

MODULE_LICENSE("GPL");
MODULE_AUTHOR(« Votre nom »);
MODULE_DESCRIPTION(« Exemple de module de remappage d'E/S »);

MODULE_LICENSE("GPL");:
Cette macro est utilisée pour spécifier le type de licence du module noyau. Dans le développement du noyau Linux, il est important de suivre différents types de licences. "GPL" signifie GNU General Public License, qui est une licence de logiciel open source qui nécessite que le code dérivé soit également conforme à la GPL. Si le module utilise une licence différente, vous pouvez spécifier ici d'autres types de licence, tels que "LGPL", "MIT", etc.

MODULE_AUTHOR("Your Name");:
Cette macro est utilisée pour spécifier les informations sur l'auteur du module noyau. Vous devez remplacer « Votre nom » par votre vrai nom ou votre logo.

MODULE_DESCRIPTION("Sample I/O Remapping Module");:
Cette macro est utilisée pour fournir une brève description du module du noyau. Ici, vous pouvez décrire la fonction, le but ou les caractéristiques du module. Une description du module est généralement affichée lorsque le module est chargé.

Ces macros fournissent un moyen standardisé d'enregistrer et d'afficher des informations sur les modules du noyau, aidant ainsi les développeurs à comprendre les caractéristiques et l'arrière-plan du module. Lorsque d'autres développeurs ou responsables du noyau examinent votre code, ils peuvent facilement comprendre les informations de base sur le module.

Finition

Si vous avez des questions, n'hésitez pas à les poser et nous pourrons progresser ensemble.

Je suppose que tu aimes

Origine blog.csdn.net/qq_52749711/article/details/132431438
conseillé
Classement