首頁  >  文章  >  系統教程  >  Linux裝置驅動之字元裝置:一種描述和管理順序存取裝置的便捷方法

Linux裝置驅動之字元裝置:一種描述和管理順序存取裝置的便捷方法

王林
王林轉載
2024-02-13 16:54:161046瀏覽

你是否想過如何在Linux系統中為你的字元裝置編寫驅動程式?你是否想過如何在Linux系統中讓你的驅動程式實現一些基本的功能,例如開啟、關閉、讀取、寫入、控制等?你是否想過如何在Linux系統中讓你的驅動程式實現一些進階的功能,例如非同步通知、多重使用、記憶體映射等?如果你對這些問題感興趣,那麼本文將為你介紹一種實現這些目標的有效方法—Linux裝置驅動程式之字元裝置。字元設備是一種用於描述順序存取設備的資料結構,它可以讓你用一種簡單而統一的方式,將字元設備的資訊和屬性傳遞給內核,從而實現設備的識別和驅動。字元設備也是一種用於實現基本功能的機制,它可以讓你用一種標準而通用的方式,定義和使用各種字元設備的操作和命令,從而實現開啟、關閉、讀取、寫入、控制等功能。字符設備也是一種用於實現高級功能的框架,它可以讓你用一種靈活而可擴展的方式,定義和使用各種字符設備的接口和協議,從而實現異步通知、多路復用、內存映射等功能。本文將從字元裝置的基本概念、語法規則、編寫方法、註冊過程、操作方式等方面,為你詳細地介紹字元裝置在Linux裝置驅動程式中的應用和作用,幫助你掌握這種有用且強大的方法。

Linux裝置驅動之字元裝置:一種描述和管理順序存取裝置的便捷方法

#字元設備是3大類設備(字元設備、區塊設備和網路設備)中較簡單的一類設備,其驅動程式中完成的主要工作是初始化、新增和刪除cdev結構體,申請和釋放設備號,以及填入file_operations結構體中的操作函數,實作file_operations結構體中的read()、write()和ioctl()等函數是驅動設計的主體工作。


參考例程

#原始碼

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

使用者空間驗證

  1. #切換到root用戶

  2. 插入模組

    insmod globalmem.ko
    
  3. 建立設備節點(後續例程會顯示自動建立節點的方法)

    cat /proc/devices 找到主设备号major
    mknod /dev/globalmem0 c major 0 和 /dev/globalmem1 c major 1
    
  4. 讀寫測試

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

透過本文,我們了解了字元裝置在Linux裝置驅動程式中的應用和作用,學習如何撰寫、註冊、操作、修改和偵錯字元裝置。我們發現,字元設備是一種非常適合嵌入式系統開發的方法,它可以讓我們方便地描述和管理順序存取設備,實現基本功能和進階功能。當然,字元設備也有一些注意事項和限制,例如需要遵循語法規範、需要注意權限問題、需要注意效能影響等。因此,在使用字元設備時,我們需要有一定的硬體知識和經驗,以及良好的程式設計習慣和調試技巧。希望本文能為你提供一個入門級的指導,讓你對字元設備有初步的認識和理解。如果你想深入學習字元設備,建議你參考更多的資料和範例,以及自己動手實作和探索。

以上是Linux裝置驅動之字元裝置:一種描述和管理順序存取裝置的便捷方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除