序言
代码结构简单linux命令vi,致力用最简单的原理理解最主要的框架逻辑,细节须要自行延展。-----------------学习的基础底层逻辑
基础步骤
因为硬件设备各色各样,有了设备驱动程序,应用程序就可以不用在乎设备的具体细节,而便捷地与外部设备进行通讯。从外部设备读取数据,或是将数据写入外部设备,即对设备进行控制。
设备驱动程序框架
module_init是linuxkernel绝大多数模块的起始点。
我们所熟悉的应用程序都是从一个main()函数开始运行的,而与应用程序不同,内核模块的起始就是module_init()标记的函数。
module_init是一个宏linux驱动原理linux驱动原理,它的参数就是模块自行定义的“起始函数”。这个函数使用module_init标记后,才会在内核初始化阶段,“自动”运行。
无论模块是编译进内核镜像,还是以ko的方式加载,都是从这儿开始运行。
有开始就有结束,与module_init对应的就是module_exit。module_exit负责进行一些和init反向的活动。
设备的种类繁杂linux vi 命令,所以设备的驱动程序也是各色各样的,SVR4是UNIX操作系统的一种内核标准。
规范有以下部份:
接出来使用mknod命令创建设备文件结点,之后用chmod命令更改权限为777。此时设备就可以使用了。
/proc/devices与/dev的不同之处下层应用怎么调用底层驱动
应用层的程序open(“/dev/xxx”,mode,flags)打开设备文件,步入内核中,即虚拟文件系统中。
VFS层的设备文件有对应的structinode,其中包含该设备对应的设备号,设备类型,返回的设备的结构体。
在驱动层中,按照设备类型和设备号就可以找到对应的设备驱动的结构体,用i_cdev保存。该结构体中有很重要的一个操作函数插口file_operations。
在打开设备文件时,会分配一个structfile,将操作函数插口的地址保存在该结构体中。
VFS层向应用层返回一个fd,fd是和structfile相对应,这样,应用层可以通过fd调用操作函数,即通过驱动层调用硬件设备了。
代码
起始函数
module_init(hello_init);
下边的函数的主要工作是:
int hello_init(void)//三件事情
{
devNum = MKDEV(reg_major, reg_minor);
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_add(gDev, devNum, 3);//把它添加到系统中去
return 0;
}
驱动注册
将驱动信息注册完毕后,假如有匹配的设备接入,本驱动的probe都会被调用。
原生代码里有好多进一步对module_init封装的宏,虽然做的也就是注册驱动这件事。
申请资源&创建节点
钩子函数的几个实现:没有实现任何功能,只是为了让框架更显著
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_EMERG"hello_openrn");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_writern");
return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_readrn");
return 0;
}
几个重要的数据结构:
内核中每位字符设备都对应一个cdev结构的变量:
structcdev:
struct cdev {
struct kobject kobj; // 每个cdev 都是一个 kobject
struct module *owner; // 指向实现驱动的模块
const struct file_operations *ops; // 操纵这个字符设备文件的方法
struct list_head list; // 与cdev 对应的字符设备文件的 inode->i_devices 的链表头
dev_t dev; // 起始设备编号
unsigned int count; // 设备范围号大小
};
structfile_operations:
struct file_operations {
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
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);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
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 *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
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 **);
};
完整代码
驱动代码:
#include
#include
#include
#include //file_operations结构体
#include
#include
#include
#include
#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;
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_EMERG"hello_openrn");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_writern");
return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_readrn");
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);//把它添加到系统中去,注册设备,通常发生在驱动模块的加载函数中
return 0;
}
void __exit hello_exit(void)
{
cdev_del(gDev);
unregister_chrdev_region(devNum, subDevNum);
kfree(gDev);
kfree(gFile);
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
编译驱动的Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := helloDev.o
else
PWD := $(shell pwd)
#KDIR:= /lib/modules/4.4.0-31-generic/build
KDIR := /lib/modules/`uname -r`/build
all:
make -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif
使用make命令编译结果:
ls-l如下:
加载进内核:
想要控制驱动就须要添加设备节点:mknod/dev/helloc2320
应用层代码:
#include
#include
#include
#include
#define DATA_NUM (64)
int main(int argc, char *argv[])
{
int fd, i;
int r_len, w_len;
fd_set fdset;
char buf[DATA_NUM]="hello world";
memset(buf,0,DATA_NUM);
fd = open("/dev/hello", O_RDWR);
printf("%drn",fd);
if(-1 == fd) {
perror("open file errorrn");
return -1;
}
else {
printf("open successern");
}
w_len = write(fd,buf, DATA_NUM);
r_len = read(fd, buf, DATA_NUM);
printf("%d %drn", w_len, r_len);
printf("%srn",buf);
return 0;
}
编译:
gcc test.c -o test.o
结果:
执行test测试驱动:
再度执行dmesg查看驱动输出,发觉驱动里的hell_open,hello_write,hello_read被依次调用了:
文章评论