相关链接:https://www.cnblogs.com/lxq-247A3/p/16337398.html
1.字符设备和块设备
字符设备:能够像字节流一样被访问的设备,驱动程序至少实现open,close,read和write系统调用。字符终端、串口、鼠
标、键盘、摄像头、声卡和显卡等。
块设备:可以存取任意字节数,能够容纳文件系统。如:u盘,SD卡,磁盘等。
网络设备:可以是一个硬件设备,或者是软件设备,没有相应的read
write,面向流的一种特殊设备。
相同点:都是通过/dev目录下的文件系统节点来访问。
系统调用:read、open、close、write,注意fopen
2.驱动加载
静态加载:把驱动直接编译到内核中,这种加载方式比较麻烦,特别是我们需要修改驱动的时候,就需要每次都编译内核,编译内核是比较费时间的
动态加载:使用insmod命令添加模块(.ko),在不需要的时候用rmmod命令卸载模块,采用这种动态加载的方式便于驱动程序的调试,同时可以针对产品的功能需求,进行内核的裁剪,将不需要的驱动去除,大大减小了内核的存储容量。
3.查看模块相关信息
查看驱动模块中打印信息的命令:dmesg
查看字符设备信息可以用lsmod 和modprobe
lsmod可以查看模块的依赖关系
modprobe在加载模块时会加载其他依赖的模块
显示当前使用的中断号cat /proc/interrupt
4.主设备号和次设备号
创建chartest设备:mknod chartest c 4 64
主设备号:标识设备对应的驱动程序,Linux中为12位
次设备号:通过次设备号获得一个指向内核设备的直接指针,也可将此设备号当作设备本地数组的索引。占20位
chartest 由驱动程序4管理,该文件所指的设备是64号设备
5.内核和用户空间之间的拷贝
copy_to_user():完成内核空间到用户空间的复制
copy_from_user():完成用户空间到内核空间的复制
一般用于file_operations(文件操作结构体)里的read,write,ioctl等内存数据交换作用的函数。如果ioctl没有用到内存数据复制,那么就不会用到这两个函数。
6.引入模块机制的好处
该机制有助于缩短模块的开发周期。即:注册和卸载都很灵活方便。
7.字符设备基本框架
7.1 模块加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
```cpp #include <linux/init.h> #include <linux/module.h>
static int __init xxx_init(void) { return 0; }
static void __exit xxx_exit(void) { }
module_init(xxx_init); module_exit(xxx_exit) MODULE_LICENSE("GPL"); MODULE_AUTHOR("VICCZYQ"); ```
```shell sudo insmod hello.ko sudo rmmod hello ```
**注意在init函数中申请的资源在exit函数中要释放**
|
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备。卸载驱动模块的时也需要注销掉字符设备。
7.2 注册字符设备驱动
对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备。卸载驱动模块的时也需要注销掉字符设备。一般字符设备的注册在驱动模块的入口函数
xxx_init
中进行,字符设备的注销在驱动模块的出口函数
xxx_exit 中进行。
1 2 3 4 5 6 7
| static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) static inline void unregister_chrdev(unsigned int major, const char *name)
|
7.3 内存映射
在Linux中不能直接访问寄存器,要想要操作寄存器需要完成物理地址到虚拟空间的映射。物理内存和虚拟内存之间的转换,需要用到:
ioremap 和 iounmap两个函数
1 2 3
| #define addr (0X020E0068) static void __iomem* va; va=ioremap(addr, 4);
|
注意卸载时候要用iounmap
7.4 数据传递
1 2 3 4 5
| static inline long copy_from_user(void *to, const void __user * from, unsigned long n) static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
|
7.5 字符设备最基本框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| #define CHRDEVBASE_MAJOR 200 #define CHRDEVBASE_NAME "chrdevbase"
static char readbuf[100]; static char writebuf[100]; static char kerneldata[] = {"kernel data!"};
#define GPIO_TEST_BASE (0x01234567) static void __iomem *GPIO_TEST;
static int chrdevbase_open(struct inode *inode, struct file *filp) { return 0; }
static ssize_t chrdevbase_read(struct file *filp , char __user *buf , size_t cnt , loff_t *offt) { int retvalue = 0; unsigned char databuf[1];
#if 0 databuf[0] = readl(GPIO_TEST); retvalue = copy_to_user(buf , databuf, cnt);
#else memcpy(readbuf , kerneldata , sizeof(kerneldata)); retvalue = copy_to_user(buf , readbuf , cnt); #endif
if(retvalue == 0) printk("kernel senddate ok!\n"); else printk("kernel senddate failed!\n"); return 0; }
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt , loff_t *offt) { int retvalue = 0;
#if 0 writel(buf[0],GPIO_TEST);
#else retvalue = copy_from_user(writebuf , buf ,cnt); #endif if(retvalue == 0) printk("kernel recevdate : %s\n",writebuf); else printk("kernel recevdate failed!"); return 0; }
static int chrdevbase_release(struct inode *inode , struct file *filp) { return 0; }
static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .read = chrdevbase_read, .write = chrdevbase_write, .release = chrdevbase_release, };
static int __init chrdevbase_init(void) { int retvalue = 0; GPIO_TEST= ioremap(GPIO_TEST_BASE, 4); retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops); if(retvalue < 0) printk("chrdevbase driver register failed\n"); printk("chrdevbase_init()\r\n"); return 0; }
static void __exit chrdevbase_exit(void) { iounmap(GPIO_TEST); unregister_chrdev(CHRDEVBASE_MAJOR , CHRDEVBASE_NAME); printk("chrdevbase_exit()\r\n"); }
module_init(chrdevbase_init); module_exit(chrdevbase_exit);
MODULE_LICENSE("GPI"); MODULE_AUTHOR("songwei");
|
7.6 创建驱动节点文件
1
| mknod /dev/chrdevbase c 200 0
|
创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls
/dev/chrdevbase -l”命令查看
8.DMA和中断
DMA:一种无须CPU的参与就可以让外设与系统内存之间进行数据传输的硬件机制
中断:是指CPU在执行程序的过程中,出现了某些突发事件时CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回源程序被中断的位置并继续执行。
中断和DMA的区别就是DMA不需CPU参与而中断是需要CPU参与的。
9.驱动的Makefile框架
1 2 3 4 5 6
| BASE_KERNEL ?= 内核的源码目录 obj-m += 驱动源文件名.o all: make -C $(BASE_KERNEL) M=$(PWD) modules clean: make -C $(BASE_KERNEL) M=$(PWD) clean
|
eg.驱动源文件为abc.c和hello.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
BASE_KERNEL ?= /lib/modules/$(shell uname -r)/build
TARGET := hello
obj-m := abc.o hello.o
all: make -C $(BASE_KERNEL) M=$(PWD) modules
clean: make -C $(BASE_KERNEL) M=$(PWD) clean
|
10 启动过程
10.1 U-boot 启动流程
第一阶段:
- 硬件设备初始化
- 将第二阶段的代码加载到RAM
- 设置好栈空间
- 跳转到第二阶段代码入口处
第二阶段:
- 初始化本阶段的硬件设备
- 将Linux内核从Flash读入RAM
- 为Linux内核设置启动参数
- 调用Linux内核
10.2 Linux系统启动过程
- 内核引导
- 运行初始化,init
- 系统初始化
- 建立终端
- 用户登录系统