6ull Linux驱动开发笔记(二)字符设备

1、简介

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比

应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write 和 read 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。



2、字符设备注册与注销

字符设备的注册和注销函数原型

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

    
    
static inline void unregister_chrdev(unsigned int major, const char *name)

register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:

major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两

部分,关于设备号后面会详细讲解。

name:设备名字,指向一串字符串。

fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。

unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:

major:要注销的设备对应的主设备号。

name:要注销的设备对应的设备名。

unregister_chrdev函数用于注销字符设备



file_operations结构体

struct file_operations {

  struct module *owner;

  loff_t (*llseek) (struct file *, loff_t, int);

  ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

  ssize_t (*write) (struct file *, const char __user *, size_t,loff_t *);

  ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);

  ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);

  int (*iterate) (struct file *, struct dir_context *);

  unsigned int (*poll) (struct file *, struct poll_table_struct *);

  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

  long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

  int (*mmap) (struct file *, struct vm_area_struct *);

  int (*mremap)(struct file *, struct vm_area_struct *);

  int (*open) (struct inode *, struct file *);

  int (*flush) (struct file *, fl_owner_t id);

  int (*release) (struct inode *, struct file *);

  int (*fsync) (struct file *, loff_t, loff_t, int datasync);

  int (*aio_fsync) (struct kiocb *, int datasync);

  int (*fasync) (int, struct file *, int);

  int (*lock) (struct file *, int, struct file_lock *);

  ssize_t (*sendpage) (struct file *, struct page *, int,size_t,loff_t *, int);

  unsigned long (*get_unmapped_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);

  int (*check_flags)(int);

  int (*flock) (struct file *, int, struct file_lock *);

  ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,loff_t *, size_t, unsigned int);

  ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

  int (*setlease)(struct file *, long, struct file_lock **, void **);

  long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);

  void (*show_fdinfo)(struct seq_file *m, struct file *f);

  #ifndef CONFIG_MMU

  unsigned (*mmap_capabilities)(struct file *);

  #endif

};

owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。

llseek 函数用于修改文件当前的读写位置。

read 函数用于读取设备文件。

write 函数用于向设备文件写入(发送)数据。

poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。

unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。

compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。

mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。

open 函数用于打开设备文件。

release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。

fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。

aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。





3、新字符设备驱动

新字符设备驱动框架是为了解决驱动加载后还需要手动使用mknod命令挂载/dev/xxx文件的问题。

大概初始化流程:

一、创建设备号

二、初始化 cdev

三、添加一个 cdev

四、创建类

五、创建设备



1)创建设备号

动态设备号的申请函数如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:

dev:保存申请到的设备号。

baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。

count:要申请的设备号数量。

name:设备名字。



2)cdev

在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中的定义.

cdev初始化,cdev_init 函数原型如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

例子:

struct cdev testcdev;

/* 设备操作函数 */
static struct file_operations test_fops = {
	.owner = THIS_MODULE,
	/* 其他具体的初始项 */
};

testcdev.owner = THIS_MODULE;
cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */

cdev_init 初始化后。cdev_add 函数向 Linux 系统添加这个字符设备。

cdev_add 函数原型如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,参数 count 是要添加的设备数量。

cdev_del卸载cdev。

void cdev_del(struct cdev *p)



3)创建类

struct class *class_create (struct module *owner, const char *name)

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。

返回值是个指向结构体 class 的指针,也就是创建的类。

卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:

void class_destroy(struct class *cls);



4)创建设备

使用 device_create 函数在类下面创建设备,device_create 函数原型如下:

struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)

device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。

卸载驱动的时候需要删除掉创建的设备。

void device_destroy(struct class *class, dev_t devt) 



5)例程

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>

//设备名
#define DEV_NAME        "new_fs"
//申请设备数
#define DEV_NUM         1

struct My_Config_typedef{

    struct cdev cdev;           //cdev句柄
    struct class *class;        //class句柄
    dev_t dev;                  //设备号
    struct device *device;      //设备句柄

    int MAJOY;                  //主设备号
    int MINOR;                  //次设备号

};
static  struct My_Config_typedef Config_t;



int dev_open(struct inode *inode, struct file *filep)
{
        printk("dev open!\r\n");
        return 0;
}

ssize_t dev_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
    printk("dev_read!\r\n");
    return cnt;
}


ssize_t dev_write(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
    printk("dev_write!\r\n");
    return cnt;
}



int dev_release(struct inode *inode, struct file *filep)
{
    printk("dev_release!\r\n");
    return 0;
}

/*
* 设备操作函数结构体
*/
static struct file_operations dev_fops = {
    .owner = THIS_MODULE, 
    .open = dev_open,
    .read = dev_read,
    .write = dev_write,
    .release = dev_release,
};






static int __init module_my_init(void){

    printk("module_init !\r\n");

    //动态申请设备号
    alloc_chrdev_region(&Config_t.dev,0,DEV_NUM,DEV_NAME);
    Config_t.MAJOY = MAJOR(Config_t.dev); /* 获取主设备号 */
    Config_t.MINOR = MINOR(Config_t.dev); /* 获取次设备号 */

    printk("MAJOY:%d\tMINOR:%d\r\n",Config_t.MAJOY,Config_t.MINOR);

    //cdev初始化
    Config_t.cdev.owner = THIS_MODULE;
    cdev_init(&Config_t.cdev, &dev_fops);


    //添加字符设备
    cdev_add(&Config_t.cdev,Config_t.dev,DEV_NUM);


    //创建类
    Config_t.class = class_create(THIS_MODULE,DEV_NAME);
    if (IS_ERR(Config_t.class)) {
        return PTR_ERR(Config_t.class);
    }

    //创建设备
    Config_t.device = device_create(Config_t.class,NULL,Config_t.dev,NULL,DEV_NAME);
    if(IS_ERR(Config_t.device)){
        return PTR_ERR(Config_t.device);
    }


    return 0;
}


static void __exit module_my_exit(void){

    printk("module_exit !\r\n");

    //注销设备号
    unregister_chrdev(Config_t.MAJOY,DEV_NAME);

    //删除cdev
    cdev_del(&Config_t.cdev);

    //撤销设备
    device_destroy(Config_t.class,Config_t.dev);

    //撤销类
    class_destroy(Config_t.class);
}



module_init(module_my_init); //注册模块加载函数
module_exit(module_my_exit); //注册模块卸载函数



MODULE_LICENSE("GPL"); //添加模块 LICENSE 信息
MODULE_AUTHOR("PGLaoZu");//添加模块作者信息





  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值