Maison  >  Article  >  Tutoriel système  >  Périphérique de caractères du pilote de périphérique Linux : un moyen pratique de décrire et de gérer les périphériques à accès séquentiel

Périphérique de caractères du pilote de périphérique Linux : un moyen pratique de décrire et de gérer les périphériques à accès séquentiel

王林
王林avant
2024-02-13 16:54:161046parcourir

Vous êtes-vous déjà demandé comment écrire un pilote pour votre périphérique de caractère sous Linux ? Avez-vous déjà réfléchi à la manière de permettre à votre pilote d'implémenter certaines fonctions de base dans un système Linux, telles que l'ouverture, la fermeture, la lecture, l'écriture, le contrôle, etc. ? Avez-vous déjà réfléchi à la manière de permettre à votre pilote d'implémenter certaines fonctions avancées dans les systèmes Linux, telles que la notification asynchrone, le multiplexage, le mappage mémoire, etc. ? Si ces problèmes vous intéressent, cet article vous présentera une méthode efficace pour atteindre ces objectifs : le périphérique de caractère du pilote de périphérique Linux. Un périphérique de caractère est une structure de données utilisée pour décrire les périphériques à accès séquentiel. Il vous permet de transmettre les informations et les attributs du périphérique de caractère au noyau de manière simple et unifiée, réalisant ainsi l'identification du périphérique et le pilote. Les dispositifs de caractères sont également un mécanisme utilisé pour implémenter des fonctions de base. Ils vous permettent de définir et d'utiliser diverses opérations et commandes de dispositifs de caractères de manière standard et universelle, permettant ainsi l'ouverture, la fermeture, la lecture, l'écriture, le contrôle, etc. Les périphériques de caractères constituent également un cadre pour la mise en œuvre de fonctions avancées. Il vous permet de définir et d'utiliser diverses interfaces et protocoles de périphériques de caractères de manière flexible et extensible pour réaliser des notifications asynchrones, un multiplexage, un mappage de mémoire et d'autres fonctions. Cet article présentera en détail l'application et le rôle des périphériques de caractères dans les pilotes de périphériques Linux à partir des concepts de base, des règles grammaticales, des méthodes d'écriture, du processus d'enregistrement, des méthodes de fonctionnement, etc. des périphériques de caractères pour vous aider à maîtriser cette méthode utile et puissante.

Périphérique de caractères du pilote de périphérique Linux : un moyen pratique de décrire et de gérer les périphériques à accès séquentiel

Le périphérique de caractère est le type de périphérique le plus simple parmi les trois principaux types de périphériques (périphérique de caractère, périphérique de bloc et périphérique réseau). Le travail principal effectué dans son pilote consiste à initialiser, ajouter et supprimer la structure cdev, demander et publier le périphérique. numéro de périphérique, en plus de remplir les fonctions d'opération dans la structure file_operations, l'implémentation de fonctions telles que read(), write() et ioctl() dans la structure file_operations est le travail principal de la conception du pilote.


Routine de référence

Code source

/*
 * 虚拟字符设备globalmem实例:
 *  在globalmem字符设备驱动中会分配一片大小为 GLOBALMEM_SIZE(4KB)
 *  的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过
 *  Linux系统调用访问这片内存。
 */
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEV_NAME "globalmem" /* /dev中显示的设备名 */
#define DEV_MAJOR 0 /*
 指定主设备号,为0则动态获取 */

/* ioctl用的控制字 */
#define GLOBALMEM_MAGIC 'M'
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)

/*--------------------------------------------------------------------- local vars */
/*globalmem设备结构体*/
typedef struct {
    struct cdev cdev; /* 字符设备cdev结构体*/

#define MEM_SIZE 0x1000 /*全局内存最大4K字节*/
    unsigned char mem[MEM_SIZE]; /*全局内存*/
    struct semaphore sem; /*并发控制用的信号量*/
} globalmem_dev_t;

static int globalmem_major = DEV_MAJOR;
static globalmem_dev_t *globalmem_devp; /*设备结构体指针*/

/*--------------------------------------------------------------------- file operations */
/*文件打开函数*/
static int globalmem_open(struct inode *inodep, struct file *filep)
{
    /* 获取dev指针 */
    globalmem_dev_t *dev = container_of(inodep->i_cdev, globalmem_dev_t, cdev);
    filep->private_data = dev;
    return 0;
}
/*文件释放函数*/
static int globalmem_release(struct inode *inodep, struct file *filep)
{
    return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filep, char __user *buf, size_t len, loff_t *ppos)
{
    globalmem_dev_t *dev = filep->private_data;

    unsigned long p = *ppos; 
    int ret = 0;

    /*分析和获取有效的长度*/
    if (p > MEM_SIZE) {
        printk(KERN_EMERG "%s: overflow!\n", __func__);
        return - ENOMEM;
    }
    if (len > MEM_SIZE - p) {
        len = MEM_SIZE - p;
    }

    if (down_interruptible(&dev->sem)) /* 获得信号量*/
        return  - ERESTARTSYS;

    /*内核空间->用户空间*/
    if (copy_to_user(buf, (void*)(dev->mem + p), len)) {
        ret = - EFAULT;
    }else{
        *ppos += len;

        printk(KERN_INFO "%s: read %d bytes from %d\n", DEV_NAME, (int)len, (int)p);
        ret = len;
    }

    up(&dev->sem); /* 释放信号量*/

    return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filep, const char __user *buf, size_t len, 
loff_t *ppos)
{
    globalmem_dev_t *dev = filep->private_data;
    int ret = 0;

    unsigned long p = *ppos; 
    if (p > MEM_SIZE) {
        printk(KERN_EMERG "%s: overflow!\n", __func__);
        return - ENOMEM;
    }

    if (len > MEM_SIZE - p) {
        len = MEM_SIZE - p;
    }

    if (down_interruptible(&dev->sem)) /* 获得信号量*/
        return  - ERESTARTSYS;

    /*用户空间->内核空间*/
    if (copy_from_user(dev->mem + p, buf, len)) {
        ret =  - EFAULT;
    }else{
        *ppos += len;

        printk(KERN_INFO "%s: written %d bytes from %d\n", DEV_NAME, (int)len, (int)p);
        ret = len;
    }

    up(&dev->sem); /* 释放信号量*/

    return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filep, loff_t offset, int start)
{
    globalmem_dev_t *dev = filep->private_data;
    int ret = 0;

    if (down_interruptible(&dev->sem)) /* 获得信号量*/
        return  - ERESTARTSYS;

    switch (start) {
        case SEEK_SET:
            if (offset  MEM_SIZE) {
                printk(KERN_EMERG "%s: overflow!\n", __func__);
                return - ENOMEM;
            }

            ret = filep->f_pos = offset;
            break;
        case SEEK_CUR:
            if ((filep->f_pos + offset) f_pos + offset) > MEM_SIZE) {
                printk(KERN_EMERG "%s: overflow!\n", __func__);
                return - ENOMEM;
            }

            ret = filep->f_pos += offset;
            break;
        default:
            return - EINVAL;
            break;
    }

    up(&dev->sem); /* 释放信号量*/

    printk(KERN_INFO "%s: set cur to %d.\n", DEV_NAME, ret);

    return ret;
}
/* ioctl设备控制函数 */
static long globalmem_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    globalmem_dev_t *dev = filep->private_data;

    switch (cmd) {
        case MEM_CLEAR:
            if (down_interruptible(&dev->sem)) /* 获得信号量*/
                return  - ERESTARTSYS;

            memset(dev->mem, 0, MEM_SIZE);
            up(&dev->sem); /* 释放信号量*/

            printk(KERN_INFO "%s: clear.\n", DEV_NAME);
            break;
        default:
            return - EINVAL;
    }

    return 0;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops = {
    .owner = THIS_MODULE,
    .open         = globalmem_open,
    .release      = globalmem_release,
    .read         = globalmem_read,
    .write        = globalmem_write,
    .llseek       = globalmem_llseek,
    .compat_ioctl = globalmem_ioctl
};

/*---------------------------------------------------------------------*/
/*初始化并注册cdev*/
static int globalmem_setup(globalmem_dev_t *dev, int minor)
{
    int ret = 0;
    dev_t devno = MKDEV(globalmem_major, minor);

    cdev_init(&dev->cdev, &globalmem_fops);
    dev->cdev.owner = THIS_MODULE;

    ret = cdev_add(&dev->cdev, devno, 1);
    if (ret) {
        printk(KERN_NOTICE "%s: Error %d dev %d.\n", DEV_NAME, ret, minor);
    }

    return ret;
}
/*设备驱动模块加载函数*/
static int __init globalmem_init(void)
{
    int ret = 0;
    dev_t devno; 

    /* 申请设备号*/
    if(globalmem_major){
        devno = MKDEV(globalmem_major, 0);
        ret = register_chrdev_region(devno, 2, DEV_NAME);
    }else{ /* 动态申请设备号 */
        ret = alloc_chrdev_region(&devno, 0, 2, DEV_NAME);
        globalmem_major = MAJOR(devno);
    }

    if (ret return ret;
    }

    /* 动态申请设备结构体的内存,创建两个设备 */
    globalmem_devp = kmalloc(2*sizeof(globalmem_dev_t), GFP_KERNEL);
    if (!globalmem_devp) {
        unregister_chrdev_region(devno, 2);
        return - ENOMEM;
    }

    ret |= globalmem_setup(&globalmem_devp[0], 0); /* globalmem0 */
    ret |= globalmem_setup(&globalmem_devp[1], 1); /* globalmem1 */
    if(ret)
        return ret;

    init_MUTEX(&globalmem_devp[0].sem); /*初始化信号量*/
    init_MUTEX(&globalmem_devp[1].sem);

    printk(KERN_INFO "globalmem: up %d,%d.\n", MAJOR(devno), MINOR(devno));
    return 0;
}
/*模块卸载函数*/
static void __exit globalmem_exit(void)
{
    cdev_del(&globalmem_devp[0].cdev);
    cdev_del(&globalmem_devp[1].cdev);
    kfree(globalmem_devp);
    unregister_chrdev_region(MKDEV(globalmem_major, 0), 2);
    printk(KERN_INFO "globalmem: down.\n");
}

/* 定义参数 */
module_param(globalmem_major, int, S_IRUGO);

module_init(globalmem_init);
module_exit(globalmem_exit);

/* 模块描述及声明 */
MODULE_AUTHOR("Archie Xie ");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("A char device module just for demo.");
MODULE_ALIAS("cdev gmem");
MODULE_VERSION("1.0");

Vérification de l'espace utilisateur

  1. Passer à l'utilisateur root

  2. Insérer un module

    insmod globalmem.ko
    
  3. Créez des nœuds de périphérique (les routines suivantes montreront comment créer automatiquement des nœuds)

    cat /proc/devices 找到主设备号major
    mknod /dev/globalmem0 c major 0 和 /dev/globalmem1 c major 1
    
  4. Test de lecture et d'écriture

    echo "hello world" > /dev/globalmem
    cat /dev/globalmem
    

Grâce à cet article, nous comprenons l'application et le rôle des périphériques de caractères dans les pilotes de périphériques Linux et apprenons comment écrire, enregistrer, exploiter, modifier et déboguer les périphériques de caractères. Nous avons constaté que les dispositifs à caractères constituent une méthode très appropriée pour le développement de systèmes embarqués. Ils nous permettent de décrire et de gérer facilement les dispositifs à accès séquentiel et d'implémenter des fonctions de base et avancées. Bien entendu, les périphériques de caractères comportent également certaines précautions et restrictions, telles que la nécessité de suivre les spécifications de syntaxe, de prêter attention aux problèmes d'autorisation, de prêter attention aux impacts sur les performances, etc. Par conséquent, lorsque nous utilisons des périphériques de caractères, nous devons avoir certaines connaissances et expériences en matière de matériel, ainsi que de bonnes habitudes de programmation et des compétences en débogage. J'espère que cet article pourra vous fournir un guide d'entrée de gamme et vous donner une compréhension préliminaire des dispositifs de caractères. Si vous souhaitez en savoir plus sur les dispositifs de personnages, il est recommandé de vous référer à davantage de documents et d'exemples, ainsi que de pratiquer et d'explorer par vous-même.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer