1、概述
1.1 驱动分类
linux下的驱动分为三类:
字符驱动设备:只能一个字节一个字节的读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序进行。字符设备是面向流的设备,常见的字符设备如鼠标、键盘、串口、控制台、LED等。
块设备:是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘、U盘和SD卡等存储设备。
网络设备:网络设备比较特殊,不在是对文件进行操作,而是由专门的网络接口来实现。应用程序不能直接访问网络设备驱动程序。在/dev目录下也没有文件来表示网络设备。
1.2 主从设备号
主设备号:用于标识驱动程序,主设备号一样的设备文件将使用同一类驱动程序。(1~254)
从设备号:用于标识使用同一驱动程序的不同设备(0~255)
设备文件存放于/dev目录下,使用ls -l可以查看
c开头标识字符设备
b开头表示块驱动设备
10表示主设备号,45/50表示从设备号
还可以使用cat /proc/devices查看主设备号的使用情况
2、模块的基本框架
//hello.c
#include <linux/module.h>
#include <linux/init.h>
//模块加载函数格式为xxx_init
static int __init hello_init(void){
printk("hello init\n");
return 0;
}
//模块卸载函数格式为xxx_exit
static int __exit hello_exit(void) {
printk("hello exit\n");
return 0;
}
//声明模块加载函数宏
module_init(hello_init);
//声明模块卸载函数宏
module_exit(hello_exit);
//模块许可证明,描述内核模块的许可权限
MODULE_LICENSE("GPL");
驱动的编译有两种方式:
放入Linux内核源码中编译
采用独立的方法编译模块
独立编译需要编写makefile文件
ifneq ($(KERNELRELEASE),)
obj-m := hello.o #模块名字,与C文件同名
else
KERNELDIR ?= /home/ms/FirePrime/Source/Android5.1/kernel #内核路径
PWD := $(shell pwd) #当前路径
default: #编译过程
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules #-C 找到内核的makefile
clean:
rm -rf *.o *~ .depend .*.cmd *.ko .tmp_versions
endif
2.2 内核模块命令
lsmod 列举当前系统中的所有模块
insmod xxx.ko 加载指定模块到内核
rmmod xxx 卸载指定模块(不需要.ko后缀)
3、字符驱动框架
字符设备驱动靠file_operations连接
struct file_operations {
struct module *owner; //防止模块还在被使用的时候被卸载
//方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t (*llseek) ();
ssize_t (*read) ();
ssize_t (*write) ();
unsigned int (*poll) ();//这是一个设备驱动中的轮询函数
int (*ioctl) ();//系统调用提供了发出设备特定命令的方法
int (*mmap) ();//用来请求将设备内存映射到进程的地址空间
int (*open) ();
int (*flush) ();
int (*release) ();//在文件结构被释放时引用这个操作.
};
3.2 字符设备驱动的小练习:
具体步骤:
module_init(drvdemo_init);
//注册file_operations
register_chrdev(0, "demo_chrdev", &fops);
//创建设备类
demo_class = class_create(THIS_MODULE, "demo_class");
//创建设备节点
device_create(demo_class,NULL, MKDEV(major, i), NULL,"demo%d",i);
//驱动完成 可以使用
module_exit(drvdemo_exit);
//字符设备注销函数
unregister_chrdev(major, "demo_chrdev");
//删除设备节点
device_destroy(demo_class, MKDEV(major, i));
//销毁创建好的设备类
class_destroy(demo_class);
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
/*结构体file_operations定义的头文件*/
#include <linux/fs.h>
/*声明copy_to/from_user函数的头文件*/
#include <linux/uaccess.h>
/*声明class_create 和device_create相关信息*/
#include <linux/device.h>
#define DEVICE_COUNT 2
/*记录当前驱动所占用的主设备号*/
static int major = 0;
static int demo_open (struct inode *pnode, struct file *filp)
{
printk("==> %s major: %d minor: %d\n",
__FUNCTION__, imajor(pnode), iminor(pnode));
return 0;
}
static ssize_t demo_read (struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
unsigned char ary[100] = "read successfully\n";
unsigned long len = min(count, sizeof(ary)); //min是个宏,用来获取两个数中较小的值
int retval;
printk("==>%s major: %d minor: %d\n",
__FUNCTION__, imajor(filp->f_dentry->d_inode),
iminor(filp->f_dentry->d_inode));
if(copy_to_user(buf, ary, len) != 0){
retval = -EFAULT;
goto cp_err;
}
return len; //成功返回实际传输的字节数
cp_err:
return retval;
}
static ssize_t demo_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp)
{
unsigned char ary[100] = "";
unsigned long len = min(count, sizeof(ary)); //min是个宏,用来获取两个数中较小的值
int retval;
printk("==>%s major: %d minor: %d\n",
__FUNCTION__, imajor(filp->f_dentry->d_inode),
iminor(filp->f_dentry->d_inode));
if(copy_from_user(ary, buf, len) != 0){
retval = -EFAULT;
goto cp_err;
}
printk("writing context==> %s\n",ary);
return len; //成功返回实际传输的字节数
cp_err:
return retval;
}
static int demo_release (struct inode *pnode, struct file *filp)
{
printk("==>%s major: %d minor: %d\n",
__FUNCTION__, imajor(pnode), iminor(pnode));
return 0;
}
/*定义file_operations结构体变量*/
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = demo_read,
.write = demo_write,
.open = demo_open,
.release = demo_release,
};
static struct class *demo_class;
static int __init drvdemo_init(void)
{
struct device *demo_device;
int i;
int retval;
printk("==>drvdemo_init\n");
//注册字符驱动函数
//成功返回动态分配好的主设备号,失败返回错误码(负值)
//register_chrdev(主设备号 0为自动分配,设备名称 在/proc/devices/下,file_operations)
major = register_chrdev(0, "demo_chrdev", &fops);
if(major < 0){
retval = major;
goto chrdev_err;
}
/*创建设备类*/
demo_class = class_create(THIS_MODULE, "demo_class");
if(IS_ERR(demo_class)){
retval = PTR_ERR(demo_class);
goto class_err;
}
for(i=0; i<DEVICE_COUNT; i++){ //最多可创建255个设备节点(register_chrdev函数会申请0-254范围的从设备号)
//根据类创建设备节点,通知用户在“/dev/”目录下创件名字为demoX的设备文件
demo_device = device_create(demo_class,NULL, MKDEV(major, i), NULL,"demo%d",i);
if(IS_ERR(demo_device)){
retval = PTR_ERR(demo_device);
goto device_err;
}
}
return 0;
device_err:
while(i--) //设备节点创建的回滚操作
device_destroy(demo_class,MKDEV(major, i));
class_destroy(demo_class); //删除设备类
class_err:
unregister_chrdev(major, "demo_chrdev");
chrdev_err:
return retval;
}
static void __exit drvdemo_exit(void)
{
int i;
printk("==>drvdemo_exit\n");
/*注销字符驱动函数,无返回值,major为已分配的主设备号*/
unregister_chrdev(major, "demo_chrdev");
/*删除设备节点和设备类*/
for(i=0; i<DEVICE_COUNT; i++)
device_destroy(demo_class, MKDEV(major, i));
class_destroy(demo_class);
}
module_init(drvdemo_init);
module_exit(drvdemo_exit);
MODULE_LICENSE("GPL");
Makefile:
#如果已定义KERNELRELEASE,说明是由内核构造系统调用的
#可以利用内建语句
ifneq ($(KERNELRELEASE),)
obj-m +=demo_chrdev.o
#此时由内核构造系统调用
else
#定义并记录内核源码路径
KERNELDIR = /home/ms/FirePrime/Source/Android5.1/kernel
#记录当前工程目录
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
@rm -rf *.o .t* .m* .*.cmd *.mod.c *.order *.symvers
endif
clean:
rm -rf *.ko *.o .t* .m* .*.cmd *.mod.c *.order *.symvers
3.3 struct inode结构体记录了文件系统中文件的信息,如文件大小、创建者等。每个文件都有与之对应的inode节点。
inode包含了主从设备号,可以通过宏获取
unsigned int imajor(struct inode *inode);//主设备
unsigned int iminor(struct inode *inode);//从设备
dev_t MKDEV(int imajor, int iminor);//将主从设备号合成dev_t结构体
3.4 内核空间与用户空间的数据拷贝:
//从内核空间拷贝n字节数据到用户空间
copy_to_user(user_buffer, kernel_buffer, n)
//从用户空间拷贝n字节数据到内核空间
copy_from_user(kernel_buffer, user_buffer, n)
//从内核空间拷贝一数据(任意类型)变量到用户空间
put_user(kernel_value, user_buffer)
//从用户空间拷贝一数据(任意类型)变量到内核空间
get_user(kernel_value, user_buffer)