什么是模块?什么是驱动?
模块 驱动的雏形, 你要能操控硬件才叫驱动
在空的模块的基础上,安装驱动
5.2.6.1、系统整体工作原理
(1)应用层->API->设备驱动->硬件
(2)API:open、read、write、close等
(3)驱动源码中提供真正的open、read、write、close等函数实体
5.2.6.2、file_operations结构体
(1)元素主要是函数指针,用来挂接实体函数地址
(2)每个设备驱动都需要一个该结构体类型的变量
(3)设备驱动向内核注册时提供该结构体类型的变量
注册就是 file_iperations 结构体
/*
* NOTE:
* read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
* can be called without the big kernel lock held in all filesystems.
*/
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 (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 *, 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 **);
};
5.2.6.3、注册字符设备驱动
(1)为何要注册驱动
需要内核 知道!!!
(2)谁去负责注册
驱动自己注册
(3)向谁注册
向内核 注册
(4)注册函数从哪里来
register_chrdev
(5)注册前怎样?注册后怎样?注册产生什么结果?
注册前 内核查不到, 注册后 不是黑户了,
5.2.7.字符设备驱动工作原理2
5.2.7.1、register_chrdev详解(#include <linux/fs.h>)
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
static : 为什么 加 static ,为了不和别的 文件 冲突
inline : 函数不能定义在头文件,如果你这个头文件被2个或2个以上的头文件包含时,准确的说 在链接时,会 报 重复定义 ! 怎样避免重复定义 就是 加 inline ,
如果注册成功 返回值 0
如果注册失败 返回 负整数
unsigned int major : major 大的, 这里是主设备 编号 1-255,类似于人的 身份证号
const char *name :输入型参数,字符串指针 , 设备驱动的 名字,主要为了 我们 好识别 ,人的 名字
const struct file_operations *fops :输入型参数,结构体 指针 变量,我们注册的 关键
把我们 file_operations 结构体 挂钩
4.6.4.2、内联函数和inline关键字
(1)内联函数通过在函数定义前加inline关键字实现。
(2)内联函数本质上是函数,所以有函数的优点(内联函数是编译器负责处理的,编译器可以帮我们做参数的静态类型检查);
但是他同时也有带参宏的优点(不用调用开销,而是原地展开)。所以几乎可以这样认为:内联函数就是带了参数静态类型检查的宏。
(3)当我们的函数内函数体很短(譬如只有一两句话)的时候,我们又希望利用编译器的参数类型检查来排错,我还希望没有调用开销时,最适合使用内联函数。
(1)作用,驱动向内核注册自己的file_operations
(2)参数
(3)inline和static
5.2.7.2、内核如何管理字符设备驱动
(1)内核中有一个数组用来存储注册的字符设备驱动
(2)register_chrdev内部将我们要注册的驱动的信息(主要是 )存储在数组中相应的位置
(3)cat /proc/devices查看内核中已经注册过的字符设备驱动(和块设备驱动)
(4)好好理解主设备号(major)的概念
5.2.7.3、回顾和展望
(1)回顾:inline、static等关键字
(2)回顾:/proc文件系统的作用
(3)展望:将来深入学习驱动时可以去跟register_chrdev到内部看,验证我们上面讲的原理
5.2.8.字符设备驱动代码实践1
5.2.8.1、思路和框架
(1)目的:给空模块添加驱动壳子
(2)核心工作量:file_operations及其元素填充、注册驱动
5.2.8.2、如何动手写驱动代码
(1)脑海里先有框架,知道自己要干嘛
(2)细节代码不需要一个字一个字敲,可以到内核中去寻找参考代码复制过来改
(3)写下的所有代码必须心里清楚明白,不能似懂非懂
5.2.8.3、开始动手
(1)先定义file_operations结构体变量
(2)open和close函数原型确定、内容填充
module_test.c 文件
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h> /* 驱动头文件 */
#define MYMAJOR 200 /* 定义 register_chrdev 注册设备的 主设备号 */
#define MYNAME "test_char" /* 定义 register_chrdev 注册设备的 设备名字 */
/* NOTE 自己定义函数指针 test_chrdev_open */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
printk(KERN_DEBUG "test_chrdev_open\n");
return 0;
} /* test_chrdev_open() */
/* NOTE 自己定义函数指针 test_chrdev_release , release对应的就是 close */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_DEBUG "test_chrdev_release\n");
return 0;
}
//自定义 file_operations 结构体 及其元素填充
/* NOTE 定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
.owner = THIS_MODULE, /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
.open = test_chrdev_open, /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open 函数指针*/
.release = test_chrdev_release, /* release对应的就是 close 函数指针 */
};
// 模块安装函数
static int __init chrdev_init(void)
{
int ret = -1; /* 定义 register_chrdev 的返回值 */
printk(KERN_DEBUG "chrdev_init helloworld init\n");
// 在 module_init 宏 调用函数中去注册字符串 设备驱动
ret = register_chrdev(MYMAJOR, "test_char", &test_fops);
if(ret)
{
printk(KERN_ERR "registe_chrdev fail \n");
return -EINVAL; /* 返回一个错误码 需要加 ’-‘负号*/
}
printk(KERN_INFO "register_chrdev success....\n");
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// 在 module_exit宏 调用函数中去注销 字符串 设备驱动
unregister_chrdev(MYMAJOR, "test_char"); /* 这里不判断返回值 了,一般不会出错 */
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
/***********************************************************
如果 KERN_DEBUG 打印不出来,更改打印级别
printk(KERN_DEBUG "chrdev_init helloworld init\n");
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
7 4 1 7
[root@liang_x210 driver_test]# echo 8 > /proc/sys/kernel/printk
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
8 4 1 7
************************************************************/
Makefiel 文件 和 上一节一样,无需更改
# 开发板的linux内核的源码树目录
KERN_DIR = /root/driver/kernel
obj-m += module_test.o
all:
make -C $(KERN_DIR) M=`pwd` modules
cp:
cp *.ko /root/rootfs/rootfs/driver_test
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
运行结果:
1.查看 字符设备驱动 号 ,找一个没有被占用的,我们在 代码中 已经选好 为 200
cat /proc/devices
2.
5.2.9.3、让内核自动分配主设备号
(1)为什么要让内核自动分配
(2)如何实现?
(3)测试
代码:
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#define MYMAJOR 200 /* 定义 register_chrdev 注册设备的 主设备号 */
#define MYNAME "test_char" /* 定义 register_chrdev 注册设备的 设备名字 */
int mymajor; /* 定义 register_chrdev 注册设备号*/
/* NOTE 自己定义函数指针 test_chrdev_open */
static int test_chrdev_open(struct inode *inode, struct file *file)
{
/* 这个函数中真正应该 放置 打开这个硬件设备的 操作代码 ,我们先 printk 代替一下 */
printk(KERN_DEBUG "test_chrdev_open\n");
return 0;
} /* test_chrdev_open() */
/* NOTE 自己定义函数指针 test_chrdev_release , release对应的就是 close */
static int test_chrdev_release(struct inode *inode, struct file *file)
{
printk(KERN_DEBUG "test_chrdev_release\n");
return 0;
}
//自定义 file_operations 结构体 及其元素填充
/* NOTE 定义 register_chrdev 注册设备的 设备结构体 test_fops */
static const struct file_operations test_fops = {
.owner = THIS_MODULE, /* 所有的驱动 代码这一行不需要动,所有的都是这样,不是函数指针, 惯例直接写即可 */
.open = test_chrdev_open, /* 将来应用 open 打开这个设备时实际 调用的就是这个 .open 函数指针*/
.release = test_chrdev_release, /* release对应的就是 close 函数指针 */
};
// 模块安装函数
static int __init chrdev_init(void)
{
int ret = -1; /* 定义 register_chrdev 的返回值 */
printk(KERN_DEBUG "chrdev_init helloworld init\n");
// 在 module_init 宏 调用函数中去注册字符串 设备驱动
mymajor = register_chrdev(0, "test_char", &test_fops); /* major设成0,内核帮我们自动分配空白的设备号,分配的值会 做返回值 ,负数还是返回失败 */
if(mymajor < 0)
{
printk(KERN_ERR "registe_chrdev fail \n");
return -EINVAL; /* 返回一个错误码 需要加 ’-‘负号*/
}
printk(KERN_INFO "自动分配 register_chrdev success....mymajor = %d \n",mymajor);
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
// 在 module_exit宏 调用函数中去注销 字符串 设备驱动
unregister_chrdev(mymajor, "test_char"); /* 这里不判断返回值 了,一般不会出错 */
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
/***********************************************************
如果 KERN_DEBUG 打印不出来,更改打印级别
printk(KERN_DEBUG "chrdev_init helloworld init\n");
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
7 4 1 7
[root@liang_x210 driver_test]# echo 8 > /proc/sys/kernel/printk
[root@liang_x210 driver_test]# cat /proc/sys/kernel/printk
8 4 1 7
************************************************************/