驱动学习-日常笔记day2

【1】复习
1.驱动的种类
字符设备驱动:按照字节流来访的,并且顺序访问。
90%设备都对应字符设备驱动。
帧缓存设备驱动:LCD
块设备驱动:按照block来访问,访问的单位512字节
它可以顺序访问,也可以无序访问。
网卡设备驱动:和网络通讯相关的设备

2.内核模块
	入口
	static int __init demo_init(void)
	{
		//在安装驱动的时候执行
		//资源分配
		return 0;
	}
	module_init(demo_init);
	
	出口
	static void __exit demo_exit(void)
	{
		//在卸载的时候执行
		//资源释放	
	}
	
	module_exit(demo_exit);
	许可证
	MODULE_LICENSE("GPL");
	
3.编译
	内部编译:在内核源码树中进行编译
		Kconfig .config Makefile
	外部编译:在内核源码树外进行编译
	Makefile:
		KERNELDIR:=/lib/modules/$(shell uname -r)/build/
		#KERNELDIR:=/home/linux/kernel/kernel-3.4.39/
		PWD:=$(shell pwd)
		
		modules:
			make -C $(KERNELDIR) M=$(PWD) modules
	
		clean:
			make -C $(KERNELDIR) M=$(PWD) clean
		obj-m:=demo.o
	
	静态编译:
		将驱动编译到uImage中,它是内部模块
	动态编译:
		编译生成外部模块xxx.ko,在运行的时候必须依赖uImage
		
	
4.打印
	printk(打印级别 "想打印的内容");
	printk("想打印的内容");  //使用默认打印级别
	
	cat /proc/sys/kernel/printk
	4  4   1   7
	su root
	echo  4  3 1 7 > /proc/sys/kernel/printk
	
	dmesg 主动查看内核的打印信息
	sudo dmesg -C
	sudo dmesg -c
5.模块传参
	module_param(变量名,变量类型,权限);
	MODULE_PARM_DESC(变量名,"描述字段");
	
6.作业:
1.通过命令行传参的形式传递字符类型
	//只能传递整数,不能传递字符
2.通过命令行传参的形式传递字符串
	//在命令行中不允许传递空格
3.通过命令行传参的形式传递数组
module_param_array(name, type, nump, perm) 
功能:接收整型的数组
参数:
	@name :数组名
	@type :数组的类型
	@nump :命令行传递的参数的个数
	@perm :权限

sudo insmod demo.ko light=50 tt=65
	p="www.hqyj.com" ww=11,22,33

【2】内核模块中导出符号表
导出符号表可以让一个内核模块,调用另外一个
内核模块中的函数,它可以让linux更简约,防止
代码冗余的现象。还可以让驱动工程师写否写复杂
驱动的时候更简答。

EXPORT_SYMBOL_GPL(sym)  
功能:将函数或者变量的符号表导出
参数:
	@sym:函数名或者变量名


编译:
	1.先编译提供者模块,编译完之后会产生一个
	Module.symvers,这个文件中记录的就是函
	数的名字及地址
	
	2.在编译调用者前需要将Module.symvers拷贝到
	调用者目录下,然后执行make,如果不拷贝会提示
	add undefined
安装:
	先安装提供者,在安装调用者
卸载:
	先卸载调用者,在卸载提供者

【3】字符设备驱动
用户空间:

open    read   write    close

	/dev/myled(c字符设备文件)

-----------------|----------------------------
内核空间: |
字符设备驱动
------------ struct file_operations{
| led_open | int (*open)();
| led_write| int (*read)();
mycdev.ko | led_read | int (*write)();
| led_close| int (*close)()
------------ };
---------------|------------------------------
硬件层: LED

vi -t register_chrdev

int register_chrdev(unsigned int major,
const char *name,
const struct file_operations *fops)
功能:注册一个字符设备驱动
参数:
@major :主设备号
major>0 系统认为这个major就是主设备号
major=0 系统自动分配一个主设备号

设备号(32位)=主设备号(高12)+次设备号(低20)
主设备号:它是哪一类设备 LED UART BEEP WDT
次设备号:同类中的第几个设备

@name :给你的字符设备驱动起的名字
	通过 cat /proc/devices 可以查看
	Character devices:
	  1 		mem
	  4 		/dev/vc/0
	  4 		tty
	  4 		ttyS
	  5 		/dev/tty
	  5 		/dev/console
	  5 		/dev/ptmx
	  5 		ttyprintk
	  6 		lp
	  7 		vcs
	  |          |
	  主设备号  名字

@fops:操作方法结构体
	vi  -t file_operations
	struct file_operations {
		ssize_t (*read) (struct file *,
			char __user *, size_t, loff_t *);
			
		ssize_t (*write) (struct file *, 
			const char __user *, size_t, loff_t *);
		int (*open) (struct inode *, struct file *);
		int (*release) (struct inode *, struct file *);
	}

返回值:
major>0 成功返回0,失败返回错误码(负数)
major=0 成功返回主设备号,失败返回错误码


vi -t unregister_chrdev
void unregister_chrdev(unsigned int major, const char *name)
功能:注销一个字符设备驱动
参数:
@major:主设备号
@name : 名字
返回值:无

指定驱动文件的tags就是内核顶层的tags
:set tags=/home/linux/kernel/kernel-3.4.39/tags

【4】驱动的测试流程
1.安装驱动
sudo insmod mycdev.ko

2.查看字符设备驱动是否创建成功
	cat /proc/devices 
	250       mycdev
	|          |
	主设备号  名字
3.创建设备文件(mknod)
	sudo mknod 路径(任意)/设备文件的名字  c/b  主设备号   次设备号
	sudo mknod /dev/mycdev c 250 0
	
	
4.编写应用程序
	在应用程序中调用open  read  write  close函数
	
	
5.执行应用程序
	sudo ./a.out
	或者
	sudo chmod 777 /dev/mycdev
	./a.out
6.查看
	dmesg
	如果看到驱动中的open read  write close都打印了
	说明,应用程序调用驱动就成功了。
	
练习:
	在给大家10分钟的时间练习字符设备驱动

【5】用户空间和内核空间数据传递
在内核中有asm/uaccess.h或者asm-generic/uaccess.h
里面写的都是同样类型的函数,如果包含头文件的时候
写上其中一种,可能在当前的内核版本中编译能通过,
换一个内核版本编译就通过不了了,把前面的asm换成
linux/uaccess.h,这是一个链接文件,它总能链接到正确
的头文件的位置。

#include <linux/uaccess.h>

unsigned long copy_from_user(void *to, 
	const void __user *from, unsigned long n)
功能:将数据从用户空间拷贝到内核空间
参数:
	@to  :内核空间的首地址
	@from:用户空间的首地址
	@n   :拷贝的字节的个数
返回值:
	成功返回0,失败返回未拷贝的字节的个数
	
内核中错误码问题:
	vi -t EIO;
	
unsigned long copy_to_user(void __user *to, 
	const void *from, unsigned long n)

功能:将数据从内核空间拷贝到用户空间
参数:
	@to  :用户空间的首地址
	@from:内核空间的首地址
	@n   :拷贝的字节的个数
返回值:
	成功返回0,失败返回未拷贝的字节的个数

【6】驱动和硬件的交互过程
由于驱动运行在3-4G的虚拟地址中,LED灯的
控制寄存器它是物理地址。所以如果想在内核空间
操作物理地址,需要将物理地址映射成虚拟地址。

void *ioremap(phys_addr_t offset, unsigned long size)   
功能:将物理地址映射成虚拟地址
参数:
	@offset:物理地址
	@size  :映射的长度(字节)
返回值:成功返回虚拟地址,失败返回NULL


void iounmap(void *addr)                                          
功能:取消映射
参数:
	@addr:虚拟地址
返回值:无

【7】source insight创建索引的过程
1.解压内核
将系统移植课上的内核的源码在windows上解压
(不能和ubuntu通过共享目录共享这份内核代码)

2.创建索引的工程
	project->new project->填写工程的名字,选择内核源码
	的路径->ok ->ok-->选中内核源码目录-->add all-->
	会看到有3万多个文件--->close
3.创建索引
	project->sychronize files->把能选的对号都选上->ok 
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页