Linux设备驱动之多个同类设备共用一套驱动

简介: Linux设备驱动之多个同类设备共用一套驱动

1. 应用场景

比如我们的设备上有很多一样的usb接口,这些usb接口都需要有驱动才能工作,那么是每个usb都一套单独的驱动程序么?显然不是的,这些usb接口属于同一类设备,用户对他们的操作方法完全一致,只不过不是同一个设备,所以他们可以复用同一套驱动代码,在代码中去判断用户要操作哪个设备,然后去open/read/write这个设备。

2. 如何区分不同的设备

前面说过,每个设备都有一个唯一的标识符–设备号,那么对于同一类设备,它们的主设备号是一样的,次设备号是不一样的,用来区分它们,当用户想要操作哪个具体的设备,就会打开这个设备对应的设备文件(inode结构体),并自动在内核中创建对应的file结构体,这个file结构体中就保存了用户操作的所有信息,最终会传给我们的内核驱动,驱动再根据这个file结构体和inode结构体来判断用户具体要操作的哪个设备,然后去read/write这个具体的设备。

案例:

hanp@hanp:/dev/usb$ ls -lcrw------- 1 root root 180, 0  3月 11 17:29 hiddev0crw------- 1 root root 180, 1  3月 11 17:29 hiddev1

我的主机下面的两个usb设备,他们共用了一套usb驱动,但是他们的设备号是不一样的(180,0)和(180,1),主设备号都是180表示都属于同一类设备(usb设备),次设备号分别是0和1,表示这是两个不同的设备。

3. 代码实现

#include <linux/kernel.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/fs.h>#include <asm/uaccess.h>#define NUM_OF_DEVICES    2int major = 255;/* 两个设备,所以有两套结构体 *//* 设备0对应的设备结构体是hello_dev[0], 设备1对应的设备结构体是hello_dev[1] */struct hello_device {    dev_t devno;    struct cdev cdev;    char data[128];    char name[16];}hello_dev[NUM_OF_DEVICES];struct class * hello_class;const char DEVNAME[] = "hello_device";int hello_open(struct inode * ip, struct file * fp){    printk("%s : %d\n", __func__, __LINE__);        /* 获取用户打开的设备对应的设备结构体 hello_dev[0] 或者 hello_dev[1] */    struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);        /* open的时候,通过container_of能够获取到用户要打开的那个设备的设备结构体,所有需要把这个结构体通过file指针的     * private_data参数传递给read/write */    fp->private_data = dev;        /* 一般用来做初始化设备的操作 */    /* ... */        return 0;}int hello_close(struct inode * ip, struct file * fp){    printk("%s : %d\n", __func__, __LINE__);        /* 一般用来做和open相反的操作,open申请资源,close释放资源 */    /* ... */        return 0;}ssize_t hello_read(struct file * fp, char __user * buf, size_t count, loff_t * loff){    int ret;        /* 通过file指针,获取到用户要操作的设备对应的设备结构体 */    struct hello_device * dev = fp->private_data;        /* 将用户需要的数据从内核空间copy到用户空间(buf) */    printk("%s : %d\n", __func__, __LINE__);    if (count <=0 || count > 128)        count = 128;    if ((ret = copy_to_user(buf, dev->data, count)))    {        printk("copy_to_user err\n");        return -1;    }        return count;}ssize_t hello_write(struct file * fp, const char __user * buf, size_t count, loff_t * loff){    int ret;    struct hello_device * dev = fp->private_data;        /* 将用户需要的数据从内核空间copy到用户空间(buf) */    printk("%s : %d\n", __func__, __LINE__);    if (count <=0 || count > 128)        count = 128;    if ((ret = copy_from_user(dev->data, buf, count)))    {        printk("copy_from_user err\n");        return -1;    }        return count;}/* 2. 分配file_operations结构体 */struct file_operations hello_fops = {    .owner = THIS_MODULE,    .open  = hello_open,    .release = hello_close,    .read = hello_read,    .write = hello_write};struct cdev cdev;static int hello_init(void){    int i;    printk("%s : %d\n", __func__, __LINE__);        /* 1. 生成并注册两个设备的设备号 */    /* 3. 分配、设置、注册两套cdev结构体 */    for (i = 0; i < NUM_OF_DEVICES; i++)    {        hello_dev[i].devno = MKDEV(major, i);        sprintf(hello_dev[i].name, "%s%d", DEVNAME, i);        register_chrdev_region(hello_dev[i].devno, 1, hello_dev[i].name);                hello_dev[i].cdev.owner = THIS_MODULE;        cdev_add(&hello_dev[i].cdev, hello_dev[i].devno, 1);        cdev_init(&hello_dev[i].cdev, &hello_fops);                /* 初始化两个设备各自的存储空间 */        sprintf(hello_dev[i].data, "Hi, I am hello device %d", i);    }        /* 在/sys/class目录下创建hello类,并在这个类下面创建hello_device0和hello_device1 */    hello_class = class_create(THIS_MODULE, DEVNAME);    for (i = 0; i < NUM_OF_DEVICES; i++)    {        device_create(hello_class, NULL, hello_dev[i].devno, NULL, "%s%d", DEVNAME, i);        printk("success!\n");    }    return 0;}static void hello_exit(void){    int i;    printk("%s : %d\n", __func__, __LINE__);        /* 释放资源 */    for (i = 0; i < NUM_OF_DEVICES; i++)    {        device_destroy(hello_class, hello_dev[i].devno);        cdev_del(&hello_dev[i].cdev);        unregister_chrdev_region(hello_dev[i].devno, 1);    }    class_destroy(hello_class);}MODULE_LICENSE("GPL");module_init(hello_init);module_exit(hello_exit);

解释:

container_of:/** * container_of - cast a member of a structure out to the containing structure * @ptr:        the pointer to the member. * @type:       the type of the container struct this is embedded in. * @member:     the name of the member within the struct. * */#define container_of(ptr, type, member)

功能:根据结构体中某个成员的地址,从而获取到整个结构体的首地址

@ptr: 已知结构体成员的地址

@type: 要获取的结构体的类型

@member: 已知结构体成员的名字

我们用到的实例解析:

struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);

文章最后的图解和上篇文章中我讲到file结构体和inode结构体的关系,其中inode结构体和文件系统下的文件是一一对应的关系,里面保存了这个字符设备对应的cdev结构体:

struct cdev * i_cdev,而这个cdev结构体又包含在设备结构体hello_device中,其中hello_dev[0]中包含的是设备0的cdev,hello_dev[1]中包含的是设备1的cdev,那么

container_of函数就可以根据这个cdev来判断用户打开的是hello_dev[0]还是hello_dev[1]并获取到地址。

编译安装驱动:

sudo insmod hello.ko

hanp@hanp:/dev$ ls hello_device*hello_device0  hello_device1
hanp@hanp:/dev$ cat /proc/devices | grep hello255 hello_device0255 hello_device1

可以看到在/proc/devices下注册了两个设备hello_device0和hello_device1,这两个设备的主设备一样都是255,但是次设备号不一样(cat /dev/hello_deviceX可以查看次设备号)。

4. 写应用程序进行测试 app.c

#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>int main(char argc, char * argv[]){    int fd;    int ret;    char buf[64];        if (argc != 2)    {        printf("Usage: %s <filename>\n", argv[0]);        return -1;    }        fd = open(argv[1], O_RDWR);    if (fd < 0)    {        perror("fail to open file");        return -1;    }        /* read data */    ret = read(fd, buf, sizeof(buf));    if (ret < 0)    {        printf("read err!");        return -1;    }    printf("buf = %s\n", buf);        /* write data */    strcpy(buf, "write data from app!");    ret = write(fd, buf, sizeof(buf));    if (ret < 0)    {        printf("write err!");        return -1;    }    read(fd, buf, sizeof(buf));    printf("buf = %s\n", buf);        close(fd);    return 0;}

测试:

$ gcc app.c$ sudo ./a.out /dev/hello_device0buf = Hi, I am hello device 0buf = write data from app!$ sudo ./a.out /dev/hello_device1buf = Hi, I am hello device 1buf = write data from app!

 

目录
相关文章
|
1月前
|
Shell Linux C语言
【Shell 命令集合 设备管理 】Linux 创建设备文件 MAKEDEV命令 使用指南
【Shell 命令集合 设备管理 】Linux 创建设备文件 MAKEDEV命令 使用指南
35 0
|
1月前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
29 0
|
1月前
|
监控 Linux Shell
【Shell 命令集合 网络通讯 】Linux管理终端设备的登录过程 getty命令 使用指南
【Shell 命令集合 网络通讯 】Linux管理终端设备的登录过程 getty命令 使用指南
34 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 磁盘管理 】Linux losetup命令使用教程 将一个文件或设备与一个回环设备(loop device)进行关联
【Shell 命令集合 磁盘管理 】Linux losetup命令使用教程 将一个文件或设备与一个回环设备(loop device)进行关联
44 0
|
1月前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
36 0
|
1月前
|
存储 Shell Linux
【Shell 命令集合 磁盘管理 】Linux 从远程磁带设备中删除文件或目录rmt命令使用教程
【Shell 命令集合 磁盘管理 】Linux 从远程磁带设备中删除文件或目录rmt命令使用教程
27 0
|
1月前
|
Shell Linux C语言
【Shell 命令集合 磁盘管理 】Linux 控制光驱或可移动媒体设备的弹出和关闭操作 eject命令使用教程
【Shell 命令集合 磁盘管理 】Linux 控制光驱或可移动媒体设备的弹出和关闭操作 eject命令使用教程
36 1
|
1月前
|
监控 Linux Shell
【Shell 命令集合 磁盘维护 】Linux 交换分区的特殊文件或设备 swapon命令使用指南
【Shell 命令集合 磁盘维护 】Linux 交换分区的特殊文件或设备 swapon命令使用指南
38 1
|
1月前
|
存储 Shell Linux
【Shell 命令集合 磁盘维护 】Linux 创建一个用作交换空间(swap space)的特殊文件或设备 mkswap命令使用教程
【Shell 命令集合 磁盘维护 】Linux 创建一个用作交换空间(swap space)的特殊文件或设备 mkswap命令使用教程
34 0
|
1月前
|
安全 Shell Linux
【Shell 命令集合 网络通讯 】Linux 打开终端设备 mingetty命令 使用指南
【Shell 命令集合 网络通讯 】Linux 打开终端设备 mingetty命令 使用指南
39 0