一. 概念介绍
从上到下,一个软件系统可以分为:应用程序、库、内核、驱动程序。
(1)应用程序使用库提供的open函数打开代表LED的设备文件。
(2)库根据open函数传入的参数执行“swi”指令,这条指令会引发CPU异常,进入内核。
(3)内核的异常处理函数根据这些参数找到相应的驱动程序,返回一个文件句柄给库,进而返回给应用程序。
(4)应用程序得到文件句柄后,使用库提供的write或ioclt函数发出控制命令。
(5)库根据write或ioclt函数传入的参数执行“swi”指令,这条指令会引起CPU异常,进入内核。
(6)内核的异常处理函数根据这些参数调用驱动程序的相关函数,点亮LED。
open ,write 这些函数不是我们实现的,是C库实现的, C库也属于应用层。
当我们的应用程序调用open,write这些“系统调用”的时候,C库会进入到内核,内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。
C库怎么进入内核呢?
这些open ,write的实现,实际上是执行一条swi汇编指令,然后后面加入某个值,这条汇编指令就会引发一个异常,相当于中断一样,当发生异常的时候,就会进入内核的异常处理函数里面。
内核这一层“syatem lan interface 系统调用接口”:在异常处理函数里面根据发生中断的原因,调用不同的处理函数。比如我们用open的时候,传进来的值是val1,用write的时候,传进来的值是val2,内核里面的系统调用接口就根据传进来不同的值,去调用我们的sys_open,sys_write。
例如:我们有一个函数,
int main()
{
int fd1, fd2;
int val=1;
fd1 = open(“/dev/led”, O_RDWR); // fd1是打开led的设备文件,O_RDWR可读可写
write(fd1, &val, 4); //将&val的值写入设备文件”/dev/led”,应用程序里的write函数将参数传给驱动程序里的first_drv_write函数
fd2 = open(“hello.txt”, O_RDWR); //fd2是打开一个文本文件
write(fd2, &val, 4); //将&val所指向内存地址的值写入文本文件
}
函数里两个open函数(或两个write函数),引发了不一样的行为,第一个是操控硬件,第二个是读写文件。 谁来实现不一样的行为呢?内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。sys_open,sys_write最终会根据打开不同的文件找到不同的底层驱动程序,调用不同驱动程序里的open函数、write函数来实现这些功能。
二、字符设备驱动程序的框架
实现步骤:
1. 在Source Insight新建一个文件,这是我们的第一个驱动程序,命名first_drv.c,然后写出first_drv_open, first_drv_write函数
2. 定义file_operations结构体, 把驱动函数first_drv_open, first_drv_write填充到里面
3. 把这个结构告诉内核, 通过这个注册函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来实现
4. 谁来调用注册函数 ? 驱动的入口函数first_drv_init 来调用这个注册函数
5. 修饰一下这个入口函数module_init(first_drv_init)
第一个驱动的入口函数是first_drv_init,那第二个呢?有不同的入口函数,函数名称各有不同,
内核怎么知道去调用你这个函数?怎么知道你的入口函数是这一个?那得修饰一下入口函数。
怎么修饰? 是用这么一个宏来定义的 module_init(first_drv_init);
module_init定义一个结构体,结构体里面有一个函数指针,指向first_drv_init 这个入口函数。
当我们去加载或安装一个驱动程序的时候,内核就会自动去找到这么一个结构体module_init,然后调用里面的
函数指针,它就指向我们的入口函数first_drv_init。 我们的入口函数first_drv_init就把这个结构file_operations告诉内核。
first_drv.c
//第一步:驱动功能实现
static int first_drv_open(struct inode *inode, struct file *file) //应用程序打开设备文件的时候就会进入到内核里面,
{
printk("first_drv_open\n"); //调用驱动程序里面的first_drv_open,然后打印"first_drv_open"
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("first_drv_write\n");
return 0;
}
//第二步:定义结构体,并把驱动函数填充进去
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = first_drv_open,
.write = first_drv_write,
};
//第四步:实现驱动入口函数
int first_drv_init(void) //入口函数调用注册函数
{
//第三步:把结构体告诉内核
register_chrdev(111, "first_drv", &first_drv_fops); //注册,告诉内核,"first_drv"名字可随便写
return 0;
}
void first_drv_exit(void)
{
unregister_chrdev(111, "first_drv"); //卸载
return 0;
}
//第五步:修饰入口函数及出口函数
module_init(first_drv_init);
module_exit(first_drv_exit);
firstdrvtest.c
#include <sys/types.h> /*应用程序调用open, write函数,这两个函数是C库实现的*/
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xxx", O_RDWR); //open一个设备, xxx名字不重要
if(fd<0) //如果不能打开文件,就打印"can't open!"
printf("can't open!\n");
write(fd, &val, 4);
return 0;
}
三. 设置主设备号及创建设备节点
1、设置主设备号
我们设备驱动程序里面可以自动分配主设备号, 也可以由我们自己手工指定。
// 设置为 0 时是系统自动分配主设备号,通过cat /proc/device看一下系统为我们的first_drv分配的主设备号是多少。
major = register_chrdev(0, "first_drv", &first_drv_fops);
// 手动分配主设备号111给first_drv
register_chrdev(111, "first_drv", &first_drv_fops);
2、创建设备节点
当应用程序执行 open(“/dev/xyz”) 操作时,这个/dev/xyz是怎么来的 ?
2.1 手工建立
// 创建设备节点(在串口执行命令)
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号
//查看设备信息
ls -l /dev/xyz
2.2 自动创建
mdev – 根据系统信息创建设备节点
static struct class *firstdrv_class; //一个类
static struct class_device *firstdrv_class_dev; //类下面再建立一个设备
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); //主设备号可写为0,让系统自动给我们分配
//创建设备信息,执行后会出现 /sys/class/firstdrv
firstdrv_class = class_create(THIS_MODULE, "firstdrv"); //创建一个类,类下面再创建一个设备
//创建设备节点,就是根据上面的设备信息来的
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); //卸载
//删除节点及信息
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
对应的测试程序firstdrvtest.c也要修改:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xyz", O_RDWR); //无论主设备号怎么变,/dev/xyz都是系统帮我们创建的
if(fd<0)
printf("can't open!\n");
write(fd, &val, 4);
return 0;
}