1. 什么是驱动程序
驱动程序是操作系统和硬件中间的一个抽象层,Linux把所有的设备都抽象成文件,驱动程序屏蔽各个硬件设备差异,提供统一的接口,利于上层开发
驱动程序编写主要是通过查看外设芯片手册,根据手册完成设备的初始化、读写、控制等一系列函数的编写
2.编写驱动程序准备
驱动程序可以和内核一起编译,也可以用动态加载的方式
1.Linux环境的搭建(利用虚拟机,安装Linux一个环境),只要这一部分就可以了,下面2步,可以先跳过
2.查看Linux系统版本(uname -r)
3.熟悉一下驱动的Makefile
3.如何编写驱动程序
1.完成驱动程序的init函数
硬件初始化、中断函数、向内核注册驱动程序等。首先理解硬件结构,搞清楚其功能,接口寄存器以及CPU怎么访问控制这些寄存器等。设备驱动程序可以直接编译进内核,在系统启动的时候初始化,也可以在需要的时候以模块的方式动态加载到内核中去。每个字符设备或是块设备都是通过register_chrdev()函数注册,调用该函数后就可以向系统申请主设备号,操作成功,设备名就会出现在/proc/devices里。insmod会执行init函数,lsmod查看已经安装的模块
2.完成驱动设备的exit函数
删除设备文件的一些内存资源、unregister_chrdev() 卸载驱动程序
3.完成file_operations结构中的open/release/read/write函数
打开设备是由open()函数来完成,在大部分设备驱动中open完成如下工作: 计数器+1、检查特定设备的特殊情况 、初始化设备 、识别次设备号 ; 释放设备由release()函数来完成,计数器-1。当一个进程释放设备时,其它进程还能继续使用该设备,只是该进程暂时停止对该设备的的使用
读写设备的主要任务就是把内核空间的数据复制到用户空间,或者是从用户空间复制到内核空间,也就是将内核空间缓冲区里的数据复制到用户空间的缓冲区中或者相反。字符设备使用各自的read()函数和write()函数来进行数据读写
4.完成设备的ioctl函数
ioctl的cmd的取值及含义都与具体的设备有关,根据上层用户感兴趣的点设置cmd命令,除了ioctl(),设备驱动程序还可能有其他控制函数,比如llseek()等。当应用程序使用open、release等函数打开某个设备时,设备驱动程序的file_operations结构中的相应成员就会被调用
4.驱动程序实例
下面几个文件,主要讲一下过程和Makefile
1.首先make,生成char.ko
2.sudo insmod char.ko(会调用init函数)安装char模块 dmesg | tail查看内核打印
3.lsmod 或cat /proc/devices查看模块是否安装成功,利用dmesg | tail查看内核打印,得到主设备号和次设备号
4.创建设备节点mknod /dev/dev_name dev_type major minor(设备名字、设备类型、主设备号、次设备号)
6.利用cat /dev查看是否生成设备节点成功
5.利用test.c测试驱动程序
6.删除设备节点 rm -rf /dev/dev_name
7.rmmod char(会调用exit函数)删除char模块
(1)KERNELRELEASE在linux内核源代码中的顶层makefile中有定义
(2)shell pwd会取得当前工作路径
(3)shell uname -r会取得当前内核的版本号
(4)KDIR变量便是当前内核的源代码目录。
关于linux源码的目录有两个,分别为"/lib/modules/$(shell uname -r)/build"和"/usr/src/linux-header-$(shell uname -r)/",
但如果编译过内核就会知道,usr目录下那个源代码一般是我们自己下载后解压的,而lib目录下的则是在编译时自动copy过去的,
两者的文件结构完全一样,因此有时也将内核源码目录设置成/usr/src/linux-header-$(shell uname -r)/。关于内核源码目录
可以根据自己的存放位置进行修改。
(5)make -C $(KDIR) M=$(PWD) modules
这就是编译模块了:首先改变目录到-C选项指定的位置(即内核源代码目录),其中保存有内核的顶层makefile;
M=选项让该makefile在构造modules目标之前返回到模块源代码目录;然后,modueles目标指向obj-m变量中设定的模块;
在上面的例子中,我们将该变量设置成了hello.o
char.c
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<linux/cdev.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YXW");
MODULE_DESCRIPTION("this is a chr_dev module\n");
/*
struct cdev {
struct kobject kobj;
struct module *owner;//填充时,值要为 THIS_MODULE,表示模块
const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量
struct list_head list;
dev_t dev;//设备号,主设备号+次设备号
unsigned int count;//次设备号个数
};
*/
static struct cdev chr_dev;
static dev_t ndev; //设备号
static int chr_open(struct inode* nd, struct file* flip)
{
int major = 0;
int minor = 0;
major = MAJOR(nd->i_rdev);//主设备号关联设备驱动
minor = MINOR(nd->i_rdev);//次设备号指向哪个设备
printk("chr_open, major = %d, minor = %d\n", major, minor);
return 0;
}
static ssize_t chr_read(struct file* flip, char __user* u, size_t sz, loff_t* off)
{
printk("chr_read process!\n");
return 0;
}
struct file_operations chr_ops = {
.owner = THIS_MODULE,
.open = chr_open,
.read = chr_read
};
static int demo_init(void)
{
int ret = 0;
cdev_init(&chr_dev, &chr_ops);//将struct cdev类型的结构体变量和file_operations结构体进行绑定的,还有动态分配方式cdev_alloc
ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev");
/*
1:这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,
将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。
mknod /dev/xxx c 主设备号 次设备号(c表示生成一个字符设备)
2:第二个参数:次设备号的基准,从第几个次设备号开始分配。
3:第三个参数:次设备号的个数。
4:第四个参数:驱动的名字。
5:返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。
*/
if(ret < 0)
{
return ret;
}
printk("demo_init: major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev));
ret = cdev_add(&chr_dev, ndev, 1);//向内核里面添加一个驱动,注册驱动
if(ret < 0)
{
ret = 0;
}
return 0;
}
static void demo_exit(void)
{
printk("demo_exit process!\n");
cdev_del(&chr_dev);//从内核中注销掉一个驱动。注销驱动
unregister_chrdev_region(ndev, 1);//释放原先申请的设备号,参数2:设备个数
}
module_init(demo_init);
module_exit(demo_exit);
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := char.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif
test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define CHAR_DEV_NAME "/dev/chr_dev"
int main()
{
int ret;
int fd;
char buf[32];
fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY);
if(fd < 0)
{
printf("open failed!\n");
return -1;
}
read(fd, buf, 32);
close(fd);
return 0;
}
参考博文:
https://blog.csdn.net/zqixiao_09/article/details/50839042(推荐)
https://blog.csdn.net/xdw1985829/article/details/6801349(推荐)
https://www.cnblogs.com/Charles-Zhang-Blog/archive/2013/12/02/3454382.html(推荐)
https://www.cnblogs.com/zhaobinyouth/p/6227644.html
https://blog.csdn.net/zhoujiaxq/article/details/7646013