前言
想要对设备进行读写等操作,首先要求该设备在内核中已经添加了该设备的设备驱动。
设备驱动是如何被调用的
首先在应用层,用户调用api 比如说 open()
应用层 open();
| 产生了一个软中断,由应用层即用户态进入到内核态 中断号 0x80
------------------------------------------------------------------
内核 sys_call()
| 根据设备名找到主次设备号;再找到对应驱动的sys_open
VFS sys_open()
(虚拟文件系统)| 找到对应设备驱动链表内部的节点,节点所对应的open函数
open() 控制设备对寄存器进行操作
|
------------------------------------------------------------------
硬件 对应的设备
代码段例程
底层驱动
#include <linux/fs.h> //file_operations声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //__init __exit 宏定义声明
#include <linux/device.h> //class devise声明
#include <linux/uaccess.h> //copy_from_user 的头文件
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap的头文件
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //设备号
static int major =231; //主设备号
static int minor =0; //次设备号
static char *module_name="pin4"; //模块名
//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //内核的打印函数和printf类似
return 0;
}
//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
printk("pin4_write\n");
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
int __init pin4_drv_init(void) //2、真实驱动入口(创建驱动)
{
int ret;
devno = MKDEV(major,minor); //3、创建设备号
ret = register_chrdev(major, module_name,&pin4_fops); //4、注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码生成设备
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //5、创建设备文件
return 0;
}
void __exit pin4_drv_exit(void)
{
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin4_drv_init); //1、入口 这里是一个宏定义 当内核加载驱动的时候,该宏定义会被调用,进而会调用其内部的函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
以上的几十行代码主要就干了两件事;
printk(“pin4_open\n”);
printk(“pin4_write\n”);。
在基本框架中所调用的函数是查不到的,只能在已有的内核代码程序中查找,模仿着来调用。
另外基本框架是指此框架或者说编写格式已经是想要添加一个驱动所需要编写的最精简的代码段,缺少哪一部分也不行;没有任何的业务功能。
我们写底层开发比如说内核驱动不能像上层开发那样,从main开始往下写,
而是要遵循其规则来写,对于内核驱动;想要在驱动链表内添加一个驱动,
就要从它的基本框架内填充。
应用层
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
fd = open("/dev/pin4",0_RDWR);
fd = write(fd,'1',1);
return 0;
}
上层调用open ,导致底层pin4_open()被调用;
上层调用write ,导致底层pin4_write()被调用;
----------------------------------------------------------
为什么?
----------------------------------------------------------
那一定是open和write函数被注册进了驱动链表里;
在底层驱动的代码段中可以看到有一个结构体
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
};
//static是为了防止有其他文件的结构体名字也叫pin4_fops,
为了限定该结构体的作用范围只在改文件内
在内核程序中有1.5万个c文件,很容易造成命名上的冲突
小知识点
在linux中结构体的成员的单独定义;
#include <stdio.h>
typedef struct{
int a;
int b;
int c;
int d;
}Demo;
void main()
{
struct Demo d1 = {
.a = 1;
.c = 6;
};
printf("a = %d,c = %d",d1.a,d1.c);
}
只在linux的编译工具里可以这样写。
总结
在底层的开发中,编写框架是规定好的;不能更改,需要按照其格式来进行添加需要的工具;
对于驱动来说,上层需要调用哪些函数,对应的驱动也需要有相应的功能函数。这两者(上层与底层)之间是相互对应的。