Home  >  Article  >  System Tutorial  >  Linux device driver character device: a convenient way to describe and manage sequential access devices

Linux device driver character device: a convenient way to describe and manage sequential access devices

王林
王林forward
2024-02-13 16:54:161041browse

Have you ever wondered how to write a driver for your character device in a Linux system? Have you ever thought about how to enable your driver to implement some basic functions in a Linux system, such as opening, closing, reading, writing, controlling, etc.? Have you ever thought about how to enable your driver to implement some advanced functions in Linux systems, such as asynchronous notification, multiplexing, memory mapping, etc.? If you are interested in these issues, then this article will introduce you to an effective method to achieve these goals-Linux device driver character device. A character device is a data structure used to describe sequential access devices. It allows you to pass the information and attributes of a character device to the kernel in a simple and unified way, thereby realizing device identification and driver. Character devices are also a mechanism used to implement basic functions. It allows you to define and use various character device operations and commands in a standard and universal way, thereby enabling opening, closing, reading, writing, controlling, etc. Function. Character devices are also a framework for implementing advanced functions. It allows you to define and use various character device interfaces and protocols in a flexible and extensible way to achieve asynchronous notification, multiplexing, memory Mapping and other functions. This article will introduce the application and role of character devices in Linux device drivers in detail from the basic concepts, grammatical rules, writing methods, registration process, operation methods, etc. of character devices to help you master this useful and powerful method. .

Linux device driver character device: a convenient way to describe and manage sequential access devices

Character device is the simpler type of device among the three major types of devices (character device, block device and network device). The main work completed in its driver is to initialize, add and delete the cdev structure, apply for and release the device number, and filling in the operation functions in the file_operations structure. Implementing functions such as read(), write(), and ioctl() in the file_operations structure is the main work of driver design.


Reference routine

Source code

/*
 * 虚拟字符设备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");

User Space Authentication

  1. Switch to root user

  2. Insert module

    insmod globalmem.ko
    
  3. Create device nodes (the subsequent routines will show how to automatically create nodes)

    cat /proc/devices 找到主设备号major
    mknod /dev/globalmem0 c major 0 和 /dev/globalmem1 c major 1
    
  4. Reading and Writing Test

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

Through this article, we understand the application and role of character devices in Linux device drivers, and learn how to write, register, operate, modify and debug character devices. We found that character devices are a very suitable method for embedded system development. It allows us to easily describe and manage sequential access devices and implement basic and advanced functions. Of course, character devices also have some precautions and limitations, such as the need to follow syntax specifications, pay attention to permission issues, pay attention to performance impacts, etc. Therefore, when using character devices, we need to have certain hardware knowledge and experience, as well as good programming habits and debugging skills. I hope this article can provide you with an entry-level guide and give you a preliminary understanding of character devices. If you want to learn more about character devices, it is recommended that you refer to more materials and examples, as well as practice and explore on your own.

The above is the detailed content of Linux device driver character device: a convenient way to describe and manage sequential access devices. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:lxlinux.net. If there is any infringement, please contact admin@php.cn delete