linux驱动学习记录(一)-字符设备框架
发布日期:2021-10-10 05:30:57 浏览次数:25 分类:技术文章

本文共 5625 字,大约阅读时间需要 18 分钟。

该系列是我在学习宋宝华老师的《Linux设备驱动开发详解》以及结合其他网上教程所做记录。

1.     Linux设备

 

在Linux操作系统下的设备通常分为三类:

字符设备、块设备和网络设备。

 

字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。

块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。

网络设备不同于字符设备和块设备,它是面向报文的而不是面向流的,它不支持随机访问,也没有请求缓冲区。在Linux里一个网络设备也可以叫做一个网络接口,如eth0,应用程序是通过Socket而不是设备节点来访问网络设备,在系统里根本就不存在网络设备节点。

一般说来,PCI卡属于字符设备。

 

 

2.     字符设备驱动框架

2.1   设备驱动模块

 

在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module()和cleanup_module(),而且至少要包含<linux/krernel.h>和<linux/module.h>两个头文件。

static int __init xxx_init_module (void){    ...}static void __exit xxx_cleanup_module (void){	...}/* 驱动模块加载函数 */module_init(xxx_init_module);/* 驱动模块卸载函数 */module_exit(xxx_cleanup_module);

2.2   字符设备驱动的两个重要结构体

 

cdev

在Linux内核中,使用cdev结构体描述一个字符设备,cdev结构体的定义如下

struct cdev {

struct kobject kobj; /* 内嵌的kobject对象 */

  struct module *owner; /* 所属模块*/

struct file_operations *ops; /* 文件操作结构体*/

struct list_head list;

dev_t dev; /* 设备号*/

unsigned int count;

};

cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号:

MAJOR(dev_t dev)

MINOR(dev_t dev)

而使用下列宏则可以通过主设备号和次设备号生成dev_t:

MKDEV(int major, int minor)

 

file_operations

cdev结构体的另一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。

file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

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 (*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 **);

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

 loff_t len);

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

};

 

 

2.3   字符设备驱动模型

以下为一个简单字符驱动框架示例代码,没有任何功能

 

#include        #include          #include            #include              #include                 //预定义主设备号 #define LS_MAJOR 150 static int ls_major = LS_MAJOR; module_param(ls_major, int, S_IRUGO); //自定义设备结构体 struct ls_dev { struct cdev cdev; //在Linux内核中,使用cdev结构体描述一个字符设备 }; struct ls_dev *ls_devp; /* 字符设备file_operations open 成员函数 */ static int xxx_open(struct inode *inode, struct file *filp) { return 0; } /* 字符设备file_operations ioctl 成员函数 */ static long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { return 0; } /* 字符设备file_operations read 成员函数 */ static ssize_t xxx_read(struct file *filp, char __user * buf, size_t size, loff_t * ppos) { return 0; } /* 字符设备file_operations write成员函数 */ static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos) { return 0; } /* 字符设备file_operations release成员函数 */ static int xxx_release(struct inode *inode, struct file *filp) { return 0; } /* 设备文件操作接口 */ static const struct file_operations xxx_fops = { .owner = THIS_MODULE, /* xxx_fops 所属的设备模块 */ .read = xxx_read, /* 读设备操作*/ .write = xxx_write, /* 写设备操作*/ .unlocked_ioctl = xxx_ioctl, /* 控制设备操作*/ .open = xxx_open, /* 打开设备操作*/ .release = xxx_release, /* 释放设备操作*/ }; static int __init xxx_init_module (void) { //申请字符设备号 dev_t xxx_dev_no = MKDEV(ls_major, 0); register_chrdev_region(xxx_dev_no, 1, "driver_test"); //为自定义设备结构体申请内存 ls_devp = kzalloc(sizeof(struct ls_dev), GFP_KERNEL); //将自定义设备结构体内的cdev成员与file_operations设备文件操作接口绑定 cdev_init(&ls_devp->cdev,&xxx_fops); //拥有该结构的模块的指针,一般为THIS_MODULES ls_devp->cdev.owner = THIS_MODULE; //注册设备 cdev_add(&ls_devp->cdev, xxx_dev_no, 1); return 0; } static void __exit xxx_cleanup_module (void) { /* 注销字符设备 */ cdev_del(&ls_devp->cdev); /* 释放占用的设备号 */ unregister_chrdev_region(MKDEV(ls_major, 0), 1); kfree(ls_devp); } /* 驱动模块加载函数 */ module_init(xxx_init_module); /* 驱动模块卸载函数 */ module_exit(xxx_cleanup_module); MODULE_AUTHOR("LuoSheng"); MODULE_LICENSE("GPL v2");

3.      驱动加载

 

可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( )。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module()。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。 

所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:

#insmod pcitest.ko //加载驱动

#mknod /dev/pcitest c 150 0 //创建设备文件,主设备号150,次设备号0

#rmmod pcitest  //卸载驱动

 

将建立一个主设备号为150,次设备号为0的字符设备文件/dev/ pcitest。

当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。 

转载地址:https://blog.csdn.net/qq_22042587/article/details/78175014 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:linux驱动学习记录(二)-PCI驱动框架
下一篇:新建scala工程并导出jar运行

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年03月06日 16时24分18秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章