一.字符驱动框架
1.应用层每个文件在内核层对应一个INODE号,存在在内核的struct iNode结构体(成员i_ino).
2.struct iNode结构体中存在一个struct *i_cdev结构指针,该指针指向字符设备驱动结构。
3.每个字符设备在内核中都对应一个cdev结构体,cdev通过device_id设备号来互相区分(i_rdev);
4.每个cdev对应不同的硬件设备,譬如led、串口等等.
5.inode号-->struct inode--->cdev指针--->设备号--->硬件
6.写1个字符驱动需要做的几个步骤:
A.分配一个cdev对象
B.填充cdev对象里的内容(提供fops业务层操作对象以及操作硬件实现函数,open/read/write等)
C.分配设备号
D.设备号和cdev对象关联(注册设备号)
7.应用层调用open等操作是如何关联到内核的fps->open的,关联系统调用号,使用swi 系统调用号进行系统调用.fps中每个函数都要添加系统调用号关联起来:
A.../kernal/calls.S中按照顺序添加系统调用号,譬如CALL(sys_my_open);CALL(sys_my_read);系统调用号默认排序递增。
B.../kernal/sys.c中添加函数SYSCALL_DEFINE1/2/3(my_open,xx,yy,cc),1/2/3表示系统调用参数的个数,my_open是函数名称,即SYSCALL_DEFINE后函数变成sys_my_oepn;
C.测试:应用层程序调用syscall(系统调用号,参数1...);就能调用到内核.
D.open函数系统调用到sys_open后,执行do_sys_open分配file文件得到fd,从inode结构体中拿到fops操作集,并使用fd_install(fd,f)。
8.内核驱动执行流程
驱动执行的两种方法:内核启动初始化和insmod xx.ko初始化
A.内核启动初始化
module_init(my_test_init),将my_test_init函数编译进指定段(.initcall1/2/3/4/6.init段,123456为初始化优先级),每个段区分不同的初始化等级device_initcall(fn)。上电时从指定段中读出这些函数指针并初始化.
系统启动时解析.initcall段并调用my_test_init函数。
链接脚本vmlinux.lds,Image、zImage、uImage的区别,Image是原型,zImage是Image基础上做的压缩,uImage是在zImage的基础上加的64字节头.
所以vmlinux.lds也对应三个.64字节头包括版本信息、文件大小、时间、load地址、入口地址、CRC信息.
系统启动在./init/main.c中开始执行start_kernal
B.insmode xx.ko初始化
最终也是调用module_init(my_test_init)
sudo strace insmod xxx.ko能看到调用过程.
二.编写字符驱动测试程序
1.编写mychardev.c
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>//file_operations结构体
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#define BUFFER_MAX (10)
#define OK (0)
#define ERROR (-1)
struct cdev *gDev;//代表字符设备的数据结构
struct file_operations *gFile;
//struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带.该驱动程序的核心。它给出了对文件操作函数的定义。当然,具体的实现函数是留给驱动程序编写的
dev_t devNum;//设备文件的设备号
unsigned int subDevNum = 1;
int reg_major = 232;
int reg_minor = 0;
char *buffer;
int flag = 0;
struct class *my_class;
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_EMERG"hello_open\r\n");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_write\r\n");
return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_read\r\n");
return 0;
}
int hello_init(void)//三件事情注册驱动,申请资源,节点创建
{
devNum = MKDEV(reg_major, reg_minor);//将主设备号和次设备号转换成dev_t类型
if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){
printk(KERN_EMERG"register_chrdev_region ok \n");
}else {
printk(KERN_EMERG"register_chrdev_region error n");
return ERROR;
}
printk(KERN_EMERG" hello driver init \n");
gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
gFile->open = hello_open;
gFile->read = hello_read;
gFile->write = hello_write;
gFile->owner = THIS_MODULE;
cdev_init(gDev, gFile);//初始化,建立cdev和file_operation 之间的连接
cdev_add(gDev, devNum, 3);//把它添加到系统中去,注册设备,通常发生在驱动模块的加载函数中
/* create your own class under /sysfs */
my_class = class_create(THIS_MODULE, "hello");
if(IS_ERR(my_class))
{
printk("Err: failed in creating class.\n");
return -1;
}
/* register your own device in sysfs, and this will cause udev to create corresponding device node */
device_create(my_class, NULL, devNum, NULL,"hello");
return 0;
}
void __exit hello_exit(void)
{
cdev_del(gDev);
device_destroy(my_class, devNum); //delete device node under /dev
class_destroy(my_class); //delete class created by us
unregister_chrdev_region(devNum, subDevNum);
kfree(gDev);
kfree(gFile);
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
编写Makefile:
obj-m += mychardev.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
编译:
编写app_test.c测试程序:
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd;
char buf[1024];
fd = open("/dev/hello", O_RDWR);
if (fd == -1) {
printf("无法打开设备文件\n");
return -1;
}
if (read(fd, buf, sizeof(buf)) == -1) {
printf("读取数据失败\n");
close(fd);
return -1;
}
printf("从设备中读取的数据:%s\n", buf);
char write_data[] = "Hello, driver!";
if (write(fd, write_data, sizeof(write_data)) == -1) {
printf("写入数据失败\n");
close(fd);
return -1;
}
close(fd);
return 0;
}
dmesg:
测试结束,具体涉及硬件驱动时需要把ops里面的几个操作函数根据寄存器手册来实现.