目录
用户态
- 用户态由用户程序(C代码和调用的C函数库)和函数库(提供应用程序支配内核干活的接口)组成。
- 通过函数库来访问系统调用接口来支配内核干活,进程间通信、进程调度、内存管理、文件操作、创建子进程等等。
内核态
系统调用接口
系统调用接口里有vfs、异常处理等功能体
- vfs使得不管是什么文件类型,不管是磁盘文件还是设备,都能看作是文件,使得其只用open、write、read进行统一操作。
- 异常处理根据发生异常的原因来调用不同的处理函数,如open()产生的异常就调用sys_open()、write()产生的异常就调用sys_write()、read()产生的异常就调用sys_read()
设备驱动程序
驱动一般分为字符设备驱动、块设备驱动、网络设备驱动。
如何为设备找到对应的驱动程序
用户态的应用程序调用open()传入进来的设备名(文件名)在内核态找到主设备号,从而找到驱动链表的相应驱动。
- 设备号:主设备号(手机种类)与次设备号(手机型号)
- 一个字符设备或者块设备都有一个主设备号和次设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备,即可以使用同一个主设备号,不同的次设备号
驱动链表
驱动链表管理着所有设备的驱动,驱动插入驱动链表的顺序由设备号检索,驱动链表主要有两个操作
- 添加:编写完驱动程序,加载到内核,需要设备名(文件名)、设备号(确认该驱动在驱动链表的位置)、设备驱动函数(该函数操作寄存器来操作I/O口)
- 查找:调用驱动程序,用户态调用open()
字符设备驱动框架
- 必要的头文件
- file_operations类型结构体,该结构体在内核加载驱动时会加入到驱动链表中,当用户态的应用程序调用open()时,就是根据传进来的设备名(文件名)来找到该接头体,让用户态应用程序的open、write、read函数与驱动程序的open、write、read产生联系
- open、write、read函数(该驱动框架的功能)
- 一些必要的宏
- 驱动的入口和出口函数
字符设备驱动框架
//驱动步骤1,必要的头文件
#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 *first_drv_class; //创建的类
static struct class_device *first_drv_class_dev; //创建的设备
//驱动步骤3,驱动框架的功能
// open函数
static int first_drv_open(struct inode *inode, struct file *file)
{
printk("first_drv_open\n"); //内核的打印函数和printf类似
return 0;
}
// write函数
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
printk("first_drv_write\n"); //内核的打印函数和printf类似
return 0;
}
//驱动步骤2,file_operations结构体
//将上面的函数赋值给一个结构体中,方便下面加载到到驱动链表中去
static struct file_operations first_drv = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
static int major;
//驱动步骤5,驱动的入口和出口函数
static int first_drv_init(void) //真实的驱动入口
{
//register_chrdev(主设备号,设备名,file_operations结构体地址),当主设备号为0时,系统会自动分配一个主设备号,并作为该函数的返回值
major = register_chrdev(0, "haozige", &first_drv); //注册驱动,驱动名为haozige,告诉内核,把这个驱动加入到内核驱动的链表中
first_drv_class = class_create(THIS_MODULE, "haoizge_class"); //创建一个类,类名:haozige_class,该类存在/sys/class底下
//创建设备文件,在类的下面创建一个设备,设备名:hzg,设备在/sys/class/haozige_class底下,同时mdev机制会自助创建设备节点,即在/dev底下创建这个设备,即/dev/hzg
first_drv_class_dev = class_device_create(first_drv_class, NULL, MKDEV(major, 0), NULL, "hzg"); //MKDEV(主设备号,次设备号)
return 0;
}
static void first_drv_exit(void)
{
class_device_unregister(first_drv_class_dev); //销毁设备
class_destroy(first_drv_class); //销毁类
unregister_chrdev(major,"haozige"); //卸载驱动
}
//驱动步骤4,一些必要的宏
module_init(first_drv_init); //入口,内核加载驱动的时候,这个宏会被调用,告诉内核我的驱动入口函数是这个
module_exit(first_drv_exit); //出口,内核卸载驱动的时候,这个宏会被调用,告诉内核我的驱动出口函数是这个
MODULE_LICENSE("GPL");
字符设备驱动框架(pin21_drive.c)
#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 *pin21_class; //创建的类
static struct device *pin21_class_dev; //创建的设备
static dev_t devno; //设备号,devno是用来接收创建设备号函数的返回值,销毁的时候需要传这个参数
static int major = 130; //主设备号
static int minor = 4; //次设备号
static char *module_name = "pin21"; //设备名
// open函数
static int pin21_open(struct inode *inode, struct file *file)
{
printk("pin21_open\n"); //内核的打印函数和printf类似
return 0;
}
// write函数
static ssize_t pin21_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
printk("pin21_write\n"); //内核的打印函数和printf类似
return 0;
}
// read函数
static ssize_t pin21_read(struct file *file1, char __user *buf1, size_t count1, loff_t *ppos1)
{
printk("pin21_read\n"); //内核的打印函数和printf类似
return 0;
}
//将上面的函数赋值给一个结构体中,方便下面加载到到驱动链表中去
static struct file_operations pin21_fops = {
// static 限定某些结构体或函数或变量仅在当前文件有效
.owner = THIS_MODULE,
.open = pin21_open,
.write = pin21_write,
.read = pin21_read,
};
int __init pin21_drv_init(void) //真实的驱动入口
{
int ret;
devno = MKDEV(major, minor); // 2. 创建设备号
ret = register_chrdev(major, module_name, &pin21_fops); // 3. 注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中
pin21_class = class_create(THIS_MODULE, "haozige_pin21"); //在/dev下自动生成设备,创建一个类
pin21_class_dev = device_create(pin21_class, NULL, devno, NULL, module_name); //创建设备文件,在类的下面创建一个设备。
return 0;
}
void __exit pin21_drv_exit(void)
{
device_destroy(pin21_class, devno); //销毁设备
class_destroy(pin21_class); //销毁类
unregister_chrdev(major, module_name); //卸载驱动
}
module_init(pin21_drv_init); //入口,内核加载驱动的时候,这个宏会被调用,告诉内核我的驱动入口函数是这个
module_exit(pin21_drv_exit); //出口,内核卸载驱动的时候,这个宏会被调用,告诉内核我的驱动出口函数是这个
MODULE_LICENSE("GPL v2");
驱动的编译(往期博文Linux内核的编译)
- 驱动的编译需要一个编译好的内核,如该驱动是树莓派的驱动就需要一个编译好的树莓派Linux内核
- 对于目标平台的Linux内核的编译,一般选择交叉编译的方式
字符设备驱动的编译
- 使用tree命令查看Linux内核源码目录结构
- 将字符驱动程序放到Linux内核源码目录下的drivers/char底下
- 修改Makefile文件,将内核驱动程序编译成模块或者编译进内核,将字符驱动程序(pin21_driver.c)编译成模块 obj-m +=pin21_driver.o
- 在Linux内核源码目录底下进行驱动编译,产生.ko文件,ARCH=armCROSS_COMPILE=arm-linux-gnueabinf- make modules
ARCH:指定架构CROSS_COMPILE:指定编译器 - 将驱动程序加载到目标平台(树莓派)中,命令sudo insmod pin21_driver.ko
- 为该设备添加权限,命令sudo chmod 666 /dev/pin21
- 显示载入树莓派的驱动,命令lsmod
- 卸载驱动,命令sudo rmmod pin4 driver
用户态应用程序如何访问内核态框架程序
- 当用户态(上层应用)调用open(“/dev/xxx”,O_RDWR,0666)时,会执行一条汇编指令(swi xxx),该指令会发生一次异常,类似于中断,中断号为0x80,0x80代表发生了一次系统调用,进入了内核态,然后内核态会调用syscall(),该函数会根据open()提供的设备名(文件名)找到主设备号从而在驱动链表找到相应的驱动(即找到结构体file_operations),然后系统调用接口的异常处理函数调用sys_open来调用驱动的kernel_open(),write(),read()同理,syscall()是由汇编实现的。
- 系统调用接口的异常处理函数就会处理该条异常,根据发生异常的原因来调用不同的处理函数,如open()产生的异常就调用sys_open()、write()产生的异常就调用sys_write()、read()产生的异常就调用sys_read()
硬件级
设备:鼠标、键盘、led、屏幕、flash、内存、网卡等等