10.6假期作业 字符设备驱动框架

一、字符设备驱动程序框架
  字符设备驱动是一种Linux驱动,用于支持以字符为单位进行I/O操作的设备,如串口、终端等。字符设备驱动的设计原理主要包括定义一个结构体,该结构体内部定义了一些设备的打开、关闭、读、写、控制函数;在结构体外分别实现这些函数;并向内核中注册或删除驱动模块。
二、基本原理
1. 设备号的申请与归还
  Linux中提供了两种字符定义方式:第一种方式,就是我们常见的变量定义;第二种方式,是内核提供的动态分配方式,调用该函数之 后,会返回一个struct cdev类型的指针,用于描述字符设备。

第一种方式 static struct cdev chrdev;

第二种方式 struct cdev *cdev_alloc(void);

register_chrdev_region函数用于静态地为一个字符设备申请一个或多个设备编号。
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数:
        from:dev_t类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败。
        count:指定要申请的设备号个数,count的值不可以太大,否则会与下一个主设备号重叠。
        name:用于指定该设备的名称,我们可以在/proc/devices中看到该设备。
返回值: 返回0表示申请成功,失败则返回错误码
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数:
        dev:指向dev_t类型数据的指针变量,用于存放分配到的设备编号的起始值;
        baseminor:次设备号的起始值,通常情况下,设置为0;
        count、name:同register_chrdev_region类型,用于指定需要分配的设备编号的个数以及设备的名称。
返回值: 返回0表示申请成功,失败则返回错误码
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{
   return __register_chrdev(major, 0, 256, name, fops);
} 
参数:
        major:用于指定要申请的字符设备的主设备号,等价于register_chrdev_region函数,当设置为0时,内核会自动分配一个未使用的主设备号。
        name:用于指定字符设备的名称
        fops:用于操作该设备的函数接口指针。
返回值: 主设备号
static inline void unregister_chrdev(unsigned int major, const char *name) { __unregister_chrdev(major, 0, 256, name); } 
 参数:
        major:指定需要释放的字符设备的主设备号,一般使用register_chrdev函数的返回值作为实参。
        name:执行需要释放的字符设备的名称。
返回值: 无
void unregister_chrdev_region(dev_t from, unsigned count)
参数:
        from:指定需要注销的字符设备的设备编号起始值,我们一般将定义的dev_t变量作为实参。
        count:指定需要注销的字符设备编号的个数,该值应与申请函数的count值相等,通常采用宏定义进行管理。
返回值: 无

2.保存file_operations接口

  在完成第一步的设备号申请之后,我们要开始着手考虑如何利用file_operations这个结构体中来编写读写函数。那如何将该结构体与我们的字符设备结构体相关联呢?内核提供了cdev_init函数,来实现这个过程

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    //将cdev中的所有值清零
    memset(cdev, 0, sizeof *cdev);
    //用于初始化这个 list_head 成员,使其成为一个空的链表头。
    INIT_LIST_HEAD(&cdev->list);
    //用于初始化一个已经分配好的 struct kobject 结构体,并将其与指定的类型、父对象以及名称关联起来。
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    cdev->ops = fops;
}
参数:
        cdev:struct cdev类型的指针变量,指向需要关联的字符设备结构体;
        fops:file_operations类型的结构体指针变量,一般将实现操作该设备的结构体file_operations结构体作为实参。
返回值: 无
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    int error;
    //赋值字符设备的设备号
    p->dev = dev;
    //赋值设备驱动程序控制的实际同类设备的数量
    p->count = count;
    error = kobj_map(cdev_map, dev, count, NULL,
             exact_match, exact_lock, p);
    if (error)
        return error;
    kobject_get(p->kobj.parent);
    return 0;
}
参数:
        p:struct cdev类型的指针,用于指定需要添加的字符设备;
        dev:dev_t类型变量,用于指定设备的起始编号;
        count:指定注册多少个设备。
返回值: 错误码
void cdev_del(struct cdev *p)
参数(p): struct cdev类型的指针,用于指定需要删除的字符设备
返回值: 无

 

3. 设备节点的创建和销毁

  device_create是 Linux 内核中的一个函数,用于在内核中创建一个新的设备对象。这个函数是设备驱动开发中非常重要的一部分,它允许你将设备和其对应的类关联起来,并为设备提供一组属性和操作。

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
参数:
        class: 指向一个 class 结构体的指针,表示这个设备所属的类
        parent: 指向父设备的指针,如果设备没有父设备,可以设置为 NULL
        dev_t devt: 设备的设备号
        drvdata: 要进行回调的数据
        fmt: 指定设备的名称
返回值:
        成功:新创建的 device 结构体的指针;
        失败:ERR_PTR( )
void device_destroy(struct class *class, dev_t devt)
参数
        class:指向注册此设备的struct类的指针;
        devt:以前注册的设备的开发;
返回值: 无

 

4. 创建文件设备

mknod [- 选项(可不选)] [文件名称] [类型] [主设备号] [次设备号]
选项
        Z:设置安全的上下文。
        m:设置权限信息。
        help:显示帮助信息。
        version:显示版本信息。
参数包括:
        文件名:要创建的设备文件名。
        类型:指定要创建的设备文件类型
        c、u代表(无缓冲区)字符设备文件
        b代表(有缓冲区)块设备文件
        p代表FIFO型特殊文件
主设备号:指定设备文件的主设备号,用于区分不同种类的设备。
次设备号:指定设备文件的次设备号,用于区分同一类型的多个设备。

 

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
    inode->i_mode = mode;
    //是否是字符类型
    if (S_ISCHR(mode)) {
        inode->i_fop = &def_chr_fops;
        inode->i_rdev = rdev;
    } 
    //是否是块类型
    else if (S_ISBLK(mode)) {
        inode->i_fop = &def_blk_fops;
        inode->i_rdev = rdev;
    } 
    //是否是FIFO类型
    else if (S_ISFIFO(mode))
        inode->i_fop = &pipefifo_fops;
    else if (S_ISSOCK(mode))
        ;    /* leave it no_open_fops */
    else
        printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for inode %s:%lu\n", mode, inode->i_sb->s_id,inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);

5.查找file_operation接口

  用户空间使用open()系统调用函数打开一个字符设备时(int fd = open(“dev/xxx”, O_RDWR))

在虚拟文件系统VFS中的查找对应与字符设备对应 struct inode节点
遍历散列表cdev_map,根据inod节点中的 cdev_t设备号找到cdev对象
创建struct file对象(系统采用一个数组来管理一个进程中的多个被打开的设备,每个文件秒速符作为数组下标标识了一个设备对象)
初始化struct file对象,将 struct file对象中的 file_operations成员指向 struct cdev对象中的 file_operations成员(file->fops = cdev->fops)
回调file->fops->open函数

三、程序编写

具体流程如下:

        内核模块入口获得相关寄存器并初始化

        构造file_operations接口并注册到内核

        创建设备文件,绑定file_operations接口

        应用程序获得文件句柄后,使用库函数提供的write或ioctl函数发出控制命令。

1. 模块初始化及关闭

#define DEV_NAME "EmbedCharDev"
#define DEV_CNT (1)
#define BUFF_SIZE 128
//定义字符设备的设备号
static dev_t devno;
//定义字符设备结构体chr_dev
static struct cdev chr_dev;
static int __init chrdev_init(void)
{
   int ret = 0;
   printk("chrdev init\n");
   //第一步
   //采用动态分配的方式,获取设备编号,次设备号为0,
   //设备名称为EmbedCharDev,可通过命令cat /proc/devices查看
   //DEV_CNT为1,当前只申请一个设备编号
   ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
   if (ret < 0) {
   printk("fail to alloc devno\n");
   //当获取失败时,直接返回对应的错误码
   goto alloc_err;
 }
 //第二步
 //关联字符设备结构体cdev与文件操作结构体file_operations
 cdev_init(&chr_dev, &chr_dev_fops);
 //第三步
 //添加设备至cdev_map散列表中
 ret = cdev_add(&chr_dev, devno, DEV_CNT);
 if (ret < 0) {
   printk("fail to add cdev\n");
   //当添加设备失败的话,需要将申请的设备号注销掉
   goto add_err;
 }
 return 0;

 add_err:
 //添加设备失败时,需要注销设备号
 unregister_chrdev_region(devno, DEV_CNT);
 alloc_err:
 return ret;
 }
 
 static void __exit chrdev_exit(void)
{
   printk("chrdev exit\n");
   unregister_chrdev_region(devno, DEV_CNT);
   cdev_del(&chr_dev);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

2. 文件操作方式的实现

static int chr_dev_open(struct inode *inode, struct file *filp)
{
    printk("\nopen\n");
    return 0;
}

static int chr_dev_release(struct inode *inode, struct file *filp)
{
    printk("\nrelease\n");
    return 0;
}
static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
   unsigned long p = *ppos;
   int ret;
   int tmp = count ;
   if (p > BUFF_SIZE)
      return 0;
   if (tmp > BUFF_SIZE - p)
      tmp = BUFF_SIZE - p;
   ret = copy_from_user(vbuf, buf, tmp);
   *ppos += tmp;
   return tmp;
}

static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{
   unsigned long p = *ppos;
   int ret;
   int tmp = count ;
   if (p >= BUFF_SIZE)
      return 0;
   if (tmp > BUFF_SIZE - p)
      tmp = BUFF_SIZE - p;
   ret = copy_to_user(buf, vbuf+p, tmp);
   *ppos +=tmp;
   return tmp;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值