[Linux驱动开发二]最简单的字符设备

目录

一、基础知识

1.1 字符设备的定义

1.2 基本函数说明

1.2.1 MAJOR宏

1.2.2 MINOR宏

1.2.3 MKDEV宏

1.2.4 register_chrdev_region()

1.2.5 alloc_chrdev_region()

1.2.6 unregister_chrdev_region()

二、字符设备设计流程

2.1 字符设备属性构建

2.2 字符设备方法构建

2.3 字符设备申请设备号

2.4  字符设备分配存储空间

2.5 字符设备操作

2.6 字符设备清除 

三、编译运行

3.1 完整代码

3.2 编译运行

3.2.1 接口创建

3.2.2 字符设备读操作

3.2.3 字符设备写操作

3.2.4 拓展--手动创建主设备号

四、小结

五、往期内容


脚本下载链接

一、基础知识

1.1 字符设备的定义

        linux下通常有三种设备:字符设备、块设备、网络设备等等。字符设备和块设备均以一个文件节点形式显示在文件系统的/dev目录下。本章节要介绍的字符(char)设备是指无需缓冲即可直接进行读写的设备, 如鼠标,键盘,串口设备等能够像字节流一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常要有open, close, read, write等系统调用。

1.2 基本函数说明

1.2.1 MAJOR宏

        提取主设备号,主设备号用来对应驱动程序,相同主设备号的设备使用相同的驱动程序。

1.2.2 MINOR宏

        提取次设备号,次设备号用于区分同一类型的多个设备,与主设备号结合使用就唯一标识了

一个设备。

1.2.3 MKDEV宏

        将主设备号和次设备号转换成dev_t类型

1.2.4 register_chrdev_region()

        已知主设备号的情况下用于申请设备号。

1.2.5 alloc_chrdev_region()

        若主设备号为0,则内核自动为设备分配设备号。

1.2.6 unregister_chrdev_region()

        释放已经分配的设备号。

示例代码:

#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

/* 定义主设备号和次设备号*/
int hello_major = HELLO_MAJOR;
int hello_minor = 0;
int hello_nr_devs = HELLO_NR_DEVS;

/* 将主设备号和次设备号结合,高12位为主设备号,低20位为次设备号 */
dev_t hello_devt;

...

/* 若主设备号不为0,则手动分配*/
if (hello_major){
    /* 将主设备号和次设备号进行合并成dev_t类型的设备号 */
    hello_dev = MKDEV(hello_major, hello_minor);
    ret = register_chrdev_region(hello_dev, hello_nr_devs, "hello_chr");
}
/* 若主设备号为0,自动分配设备号 */
else{
    ret = alloc_chrdev_region(&hello_dev, hello_minor, hello_nr_devs, "hello_chr");
    /* 若申请成功,则利用MAJOR提取设备号 */
    hello_major = MAJOR(hello_dev);
}

...

/* 释放设备号 */
unregister_chrdev_region(hello_dev, hello_nr_dev);

二、字符设备设计流程

[Linux驱动开发二]最简单的字符设备_第1张图片

2.1 字符设备属性构建

#include 

struct hello_char_dev{
    struct cdev cdev;
    char c;  /* 自定义属性 */
};

/* 定义指向hello_char_dev的指针 */
struct hello_char_dev *hc_devp;

2.2 字符设备方法构建

int hc_open(struct inode *inode, struct file *filp){
    printk(KERN_INFO "open hc_dev%d %d\n", iminor(inode), MINOR(inode->i_cdev->dev));
    return 0;
}

ssize_t hc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){
    printk(KERN_INFO "read hc_dev\n");
    return 0;
}

ssize_t hc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){
    printk(KERN_INFO "write hc_dev\n");
    return count;
}

int hc_release(struct inode *inode, struct file *filp){
    printk(KERN_INFO "release hc_dev\n");
    return 0;
}

/* 将函数填充至结构体中 */
struct file_operations hc_fops = {
    .owner = THIS_MODULE,
    .read  = hc_read,
    .write = hc_write,
    .open = hc_open,
    .release = hc_release,
};

2.3 字符设备申请设备号

static int __init hello_init(void){
    int ret = 0, i = 0;

    /* 若主设备号不为0,则手动分配*/
    if (hello_major){
        devt = MKDEV(hello_major, hello_minor);
        ret = register_chrdev_region(devt, hello_nr_devs, "hello_chr");  /* 使用指定的设备号分配 */
    }
    /* 自动分配设备号*/
    else{
        ret = alloc_chrdev_region(&devt, hello_minor, hello_nr_devs, "hello_chr");
        hello_major = MAJOR(devt);
    }

    if (ret < 0){
        printk(KERN_WARNING "hello: can't get major %d\n", hello_major);
        goto fail;
    }
    else{
        ;
    }

2.4  字符设备分配存储空间

hc_devp = kzalloc(sizeof(struct hello_char_dev) * hello_nr_devs, GFP_KERNEL);  /* 给字符设备分配空间 */

2.5 字符设备操作

for (i = 0; i < hello_nr_devs; i++){
        /* 初始化字符设备结构 */
        cdev_init(&hc_devp[i].cdev, &hc_fops);  
        hc_devp[i].cdev.owner = THIS_MODULE;
        /* 添加至内核 */
        ret = cdev_add(&hc_devp[i].cdev, MKDEV(hello_major, hello_minor + i), 1);  
    }

2.6 字符设备清除 

static void __exit hello_exit(void){
    int i = 0;
    
    /* 将字符设备移除 */
    for (i = 0; i < hello_nr_devs; i++){
        cdev_del(&hc_devp[i].cdev);
    }
    /* 释放内存*/
    kfree(hc_devp);
    /* 移除模块时释放设备号*/
    unregister_chrdev_region(devt, hello_nr_devs);  
}

三、编译运行

3.1 完整代码

#include 
#include 
#include 
#include 

/* 定义主设备号和设备个数*/
#define HELLO_MAJOR 0
#define HELLO_NR_DEVS 2

int hello_major = HELLO_MAJOR;
int hello_minor = 0;
int hello_nr_devs = HELLO_NR_DEVS;

/* 将主设备号和次设备号结合,高12位为主设备号,低20位为次设备号 */
dev_t devt;

/* 利用模块参数的方式,将设备号和设备个数作为模块参数,方便在加载模块时动态调整参数值 */
module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);
module_param(hello_nr_devs, int, S_IRUGO);

/* 实际的字符设备结构, 类似于面向对象中的继承 */
struct hello_char_dev{
    struct cdev cdev;
    /* 自定义属性 */
    char c;  
};

struct hello_char_dev *hc_devp;

int hc_open(struct inode *inode, struct file *filp){
    printk(KERN_INFO "open hc_dev%d %d\n", iminor(inode), MINOR(inode->i_cdev->dev));
    return 0;
}

ssize_t hc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){
    printk(KERN_INFO "read hc_dev\n");
    return 0;
}

ssize_t hc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){
    printk(KERN_INFO "write hc_dev\n");
    return count;
}

int hc_release(struct inode *inode, struct file *filp){
    printk(KERN_INFO "release hc_dev\n");
    return 0;
}

/* 字符设备的操作函数 */
struct file_operations hc_fops = {
    .owner = THIS_MODULE,
    .read  = hc_read,
    .write = hc_write,
    .open = hc_open,
    .release = hc_release,
};

static int __init hello_init(void){
    int ret = 0, i = 0;

    printk(KERN_INFO "---BEGIN HELLO LINUX MODULE---\n");

    /* 若主设备号不为0,则手动分配*/
    if (hello_major){
        devt = MKDEV(hello_major, hello_minor);
        ret = register_chrdev_region(devt, hello_nr_devs, "hello_chr");  /* 使用指定的设备号分配 */
    }
    else{
        ret = alloc_chrdev_region(&devt, hello_minor, hello_nr_devs, "hello_chr");  /* 动态分配主设备号 */
        hello_major = MAJOR(devt);
    }

    if (ret < 0){
        printk(KERN_WARNING "hello: can't get major %d\n", hello_major);
        goto fail;
    }
    else{
        ;
    }

    hc_devp = kzalloc(sizeof(struct hello_char_dev) * hello_nr_devs, GFP_KERNEL);  /* 给字符设备分配空间 */

    if (!hc_devp){
        printk(KERN_WARNING "alloc mem failed");
        ret = -ENOMEM;
        goto failure_kzalloc;
    }
    else{
        ;
    }

    for (i = 0; i < hello_nr_devs; i++){
        cdev_init(&hc_devp[i].cdev, &hc_fops);  /* 初始化字符设备结构 */
        hc_devp[i].cdev.owner = THIS_MODULE;
        ret = cdev_add(&hc_devp[i].cdev, MKDEV(hello_major, hello_minor + i), 1);  /* 添加至内核 */

        if (ret){
            printk(KERN_WARNING "fail add hc_dev%d", i);
        }
    }
    printk(KERN_INFO "---END HELLO LINUX MODULE---\n");
    return 0;

failure_kzalloc:
    unregister_chrdev_region(devt, hello_nr_devs);
fail:
    return ret;
}

static void __exit hello_exit(void){
    int i = 0;

    for (i = 0; i < hello_nr_devs; i++){
        cdev_del(&hc_devp[i].cdev);
    }
    kfree(hc_devp);
    unregister_chrdev_region(devt, hello_nr_devs);  /* 移除模块时释放设备号*/
    printk(KERN_INFO "GOODBYE LINUX\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("XZX");
MODULE_VERSION("V1.0");

3.2 编译运行

3.2.1 接口创建

        将字符设备加载至内核后(具体可参考链接),通过"cat  /proc/devices"命令查看设备号。

[Linux驱动开发二]最简单的字符设备_第2张图片

 我们在/dev/目录下发现系统并没有自动生成相应的操作接口,本章节我们先利用"mknod"命令手

动创建。

[Linux驱动开发二]最简单的字符设备_第3张图片

上述两个文件就对应了我们在驱动中申请的字符设备。

3.2.2 字符设备读操作

接下来,我们对第一个字符设备(dev0)进行读操作

利用"cat  /dev/hc_dev0"读取第一个字符设备,通过"sudo dmesg"查看。

3.2.3 字符设备写操作

         我们对第二个设备(dev1)进行写操作,通过"dmesg"查看内容如下所示。

3.2.4 拓展--手动创建主设备号

        由于我们之前将主设备号作为了模块参数,所以可以在加载模块时 实时对主设备号(hello_major)进行赋值,我们将主设备号赋值为11,如下图所示。

 通过"cat  /proc/devices" 查看主设备号,发现设置成功,如图所示:

 [Linux驱动开发二]最简单的字符设备_第4张图片

四、小结

        本章主要是针对最基础的字符设备的创建进行讲解,其中仍然有许多需要改进的地方,比如

自动生成设备节点、进一步开发read(),write()以及实现ioctl函数等,这些在后续章节都会进行补

充。

五、往期内容

        [Linux驱动开发一] 最简单的内核模块

你可能感兴趣的:(Linux驱动编程,c语言,linux,驱动开发)