Linux 内核驱动开发基础

1.裸板驱动和linux驱动的异同点

	裸板驱动:
		uart驱动程序:
			uart_init
			uart_puts
			uart_gets
		i2c控制器驱动:
			i2c_start
			i2c_stop
			i2c_tx
			i2c_rx
		g-sensor驱动
			mma8653_read_id
			mma8653_read_acc
			
	linux下驱动:英语的完型填空
		需要的知识:
		1) 硬件的知识
			读懂电路原理图
			阅读芯片的数据手册
			熟悉计算机中常用的接口
			UART
			I2C
			SPI
			CAN
			USB
			...	
		2) 驱动程序属于内核态的一部分 运行于内核态
			内核态编程的规则
				用户态和内核态的数据交互
				内核模块编程的基本框架
				解决竞态
				...
		3) 驱动编程框架
			字符设备驱动程序框架
				读写以字节为单位 读写顺序固定
				例如:键盘
				
			块设备驱动程序框架
				读写以块 (多字节) 为单位 读写顺序不固定
				例如: USB 硬盘 ......
				
			网络设备驱动程序框架
				读写以帧(多字节)为单位  读写顺序固定
				例如: Gamc...

2.内核态驱动编程的学习方法

	内核态编程最好的老师就是内核源码
		1) 看看内核中其他模块如何使用该函数
		2) 研究该函数的源码分析及使用功能
	经典书籍:
		内核: Linux内核的设计与实现
		驱动: LDD3
			 精通Linux设备驱动程序开发

3.搭建linux驱动开发环境

	3.1 安装交叉编译工具
	
	3.2 编译linux内核
		  cd /home/liuyang/driver
		  mkdir kernel 编译通过的内核源码
		  cp /mnt/hgfs/driver/env/kernel.tar.bz2 driver/
		  tar xf kernel.tar.bz2 
		  cd kernel
		  cp arch/arm/configs/x6818_config .config
		  vi Makefile
		  	ARCH
		  	CROSS_COMPILE
		  make uImage
		  让开发板使用新编译的内核
		  cp uImage /tftpboot
		  tftp 48000000 uImage
		  mmc write 48000000 800 3000

	3.3 让开发板上的内核挂载/home/liuyang/driver/rootfs根文件系统
	  		mkdir rootfs 根文件系统
	  		cp porting/busybox-1.23.2/_install/  driver/rootfs -a 
	  		//注意是将_intsall 换成了名字为rootfs的目录,并不是将_install拷贝到rootfs目录下
	  		vi /etc/exports
	  		+ /home/liuyang/driver/rootfs *(rw,sync,no_root_squash)	  		
	  		/etc/init.d/nfs-kernel-server restart		  		
	  		setenv bootcmd mmc read 48000000 800 3000 \; bootm 48000000
	  		setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/liuyang/driver/rootfs 
	  					ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 maxcpus=1 
	  					init=/linuxrc 	console=ttySAC0 lcd=wy070ml tp=gslx680
	  		saveenv 

4.内核态编程的特点

4.1 内核代码的特点
	1)内核中用到的所有函数都是自身实现的
	2) 内核源码使用的是GNU C
			GNU C 是标准C的扩展版
	3) 更多的关注代码的执行效率 以及代码的可移植性
	
4.2 内核编程时需要注意的问题
	1)内核编程时使用的虚地址为3G~4G
	2)内核中不允许做浮点运算
	3) 每个线程对应独立的栈空间
		每个线程有两个栈,用户态的栈 和内核态的栈	

5.编写独立的ko文件

vi hello.c
	#include  //在指定的内核源码目录下找该头文件
	#include 

	int __init hello_world_init(void)
	{
  		printk("helloworld\n");
  		return 0;
	}

	void __exit hello_world_exit(void)
	{
  		printk("bye bye!\n");
	}

	module_init(helloworld_init);
	module_exit(helloworld_exit);
	
vi Makefile
	//编译hello.c生成内核模块文件hello.ko
	obj-m += hello.o
	
	KERNEL_PATH=/home/liuyang/driver/kernel/

	all:
			make -C $(KERNEL_PATH) M=$(PWD) modules
			//-C:指定进入哪个目录进行编译
			//指定内核源码目录 并且该源码是编译通过的
			//M=$(PWD),指定当前目录
			//modules,编译内核模块的固定写法

	clean:
			make -C $(KERNEL_PATH) M=$(PWD) clean
	make 
	cp hello.ko /home/liuyang/driver/rootfs
	insmod hello.ko
	rmmod hello
	
	int __init hello_world_init(void)
	module_init(helloworld_init);
	//如果模块在安装时,希望某个函数被调用,以上是标准写法
	//安装模块时被调用的函数,其形式必须为:
	//int xxxx(void)
	//__init --->本身是一个宏,位于linux/init.h
	//规定hello_world_init对应的代码放入 ".init.text" 段
	//放入".init.text"段的代码执行一次 其占用的空间就释放
	//被module_init 修饰的函数在模块被安装的时候被调用

	void __exit hello_world_exit(void)			
	module_exit(helloworld_exit);
	//如果模块卸载时,希望某个函数被调用 以上是标准写法
	//被module_init修饰的函数在卸载模块时被调用
	//__exit被其修饰的函数,链接时被放入".exit.text"段
	//放入该段的代码执行一次,占据的存储空间就释放
	
	在insmod hello.ko的时候,报hello: module license 'unspecified' taints kernel
	污染了内核.
	所以应该也声明为开源的
	MODULE_LICENSE("GPL");
	MODULE_DESCRIPTION("EFI Test Driver");
	MODULE_AUTHOR("yang Liu ");

6 导出符号

它的作用是解决模块与模块之间的函数调用问题
应用编程
	a.c  
	int add (int x, int y)
	{
		return x+y;
	}
	
	a.h
	extern int add (int, int)

	b.c 
	#include "a.h"
	res = add (a,b);

内核编程
	a.c  
	int add (int x, int y)
	{
		return x+y;
	}
	EXPORT_SYMBOL(add);/EXPORT_SYMBOL_GPL(add);
	
	a.h
	extern int add (int, int)

	b.c 
	#include "a.h"
	res = add (a,b);
	
	vi export.c 
	#include 
	#include 
	MODULE_LICENSE("GPL");
	int add (int x, int y)
	{
  		return x + y;
	}
	EXPORT_SYMBOL(add);
	vi export.h
	#ifndef __EXPORT_H_
	#define __EXPORT_H_
	extern int add(int , int);
	#endif
	vi import.c 
	#include 
	#include 
	#include "export.h"

	MODULE_LICENSE("GPL");

	int __init import_init(void) 
	{
  		int res = add (10,20);
  		printk ("res = %d\n", res);
  		return 0;
	}

	void __exit import_exit(void)
	{
  		printk("bye bye\n");
	}

	module_init(import_init);
	module_exit(import_exit);
	vi Makefile
	obj-m += import.o export.o
	KERNEL_PATH=/home/liuyang/driver/kernel/

	all:
			make -C $(KERNEL_PATH) M=$(PWD) modules
			cp *.ko ../../rootfs

	clean:
			make -C $(KERNEL_PATH) M=$(PWD) clean
注意安装和卸载的顺序:
	insmod export.ko
	lsmod
	insmod import.ko
	lsmod
	rmmod import
	lsmod
	rmmod export
	lsmod
	
	EXPORT_SYMBOL 和 EXPORT_SYMBOL_GPL的区别:
	EXPORT_SYMBOL导出的符号所有的模块都可以使用
	EXPORT_SYMBOL_GPL导出的符号  只有遵循"GPL"的模块才能够使用

7.关于printk

	printk 的标准使用方法
		printk("<0/1/2/3../7>" "res = %d\n", res);

	特殊情况
		pritnk ("res = %d\n", res); //使用的是默认优先级

	linux内核将打印优先级设置为8级
	值越小优先级越高

	printk 输出的信息先到内核维护的缓冲区
	缓冲区的信息能不能输出到控制台是有限制的
	printk调用时使用的优先级 > 优先级阈值的 能够输出到终端
	可以通过dmesg 输出缓冲区中的所有打印信息
	
	实际项目中有debug 和release版本
	
	proc,基于内存的虚拟文件系统
		板子上的proc和pc上的proc目录不会做同步
		用于向用户空间输出内核的执行状态
		cat /proc/进程ID/map
			cat /proc/cpuinfo
		cat /proc/meminfo 
		
	cat /proc/sys/kernel/printk
		7		4		1		7
		第一个值,优先级阈值开关
			优先级高于该值的printk信息可以输出到终端
		第二个值, 默认优先级
			printk ("res = %d\n",res);
			
	vi printkall.c				
	#include 
	#include 

	MODULE_LICENSE("GPL");

	int __init printkall_init(void)
	{
  	  printk("<0>"  "level 0\n");
	  printk("<1>"  "level 1\n");
	  printk("<2>"  "level 2\n");
	  printk("<3>"  "level 3\n");
	  printk("<4>"  "level 4\n");
	  printk("<5>"  "level 5\n");
	  printk("<6>"  "level 6\n");
	  printk("<7>"  "level 7\n");
	  return 0;
	}

	void __exit printkall_exit(void)
	{
 	  printk("<0>"  "level 0\n");
	  printk("<1>"  "level 1\n");
	  printk("<2>"  "level 2\n");
	  printk("<3>"  "level 3\n");
	  printk("<4>"  "level 4\n");
	  printk("<5>"  "level 5\n");
	  printk("<6>"  "level 6\n");
	  printk("<7>"  "level 7\n");
	}

	module_init(printkall_init);
	module_exit(printkall_exit);
	vi Makefile
	obj-m += printkall.o
	KERNEL_PATH=/home/liuyang/driver/kernel/
	all:
			make -C $(KERNEL_PATH) M=$(PWD) modules
			cp *.ko ../../rootfs
	clean:
			make -C $(KERNEL_PATH) M=$(PWD) clean
	insmod
	修改打印优先级阈值的方式一:
		echo 4 >/proc/sys/kernel/printk
		rmmod printkall
		dmesg
		重启之后无效,是因为/proc下是基于内存的文件系统
	修改打印优先级阈值的方式二:
		setenv bootargs ...... loglevel=数字 (优先级阈值)

8.模块参数

	用户态: 
			./a.out  xxx yyy zzz
			int main (int argc, char **argv)
	内核态:
		安装内核模块时给其传递参数

  	内核中模块参数的使用步骤:
  		1) 定义全局变量
  		2) 通过特定的宏将其声明为模块参数
			module_param (name, type, perm);
				name, 要声明为模块参数的变量名称
				type, 变量的类型
						int long short charp (char *)
				perm,权限
			module_param_array(name, type, nump, perm);
				name, 数组的名称
				type, 数组元素的数据类型
				nump, 数组元素个数指针
				perm, 权限
		3) 使用模块参数
			insmod moduleparam.ko
			insmod moduleparam.ko irq=10 str="loongson" fish=1,2,3,4,5,6
		#include 
		#include 
		MODULE_LICENSE ("GPL");

		int irq = 0;
		char *str = "liuyang";
		int fish[10] = 0;
		int num = 10;

		module_param(irq, int, 0644);
		module_param(str, charp, 0);
		module_param_array(fish, int, &num, 0600);

		int __init moduleparam_init (void)
		{
			int i = 0;
			printk ("irq = %d\n", irq);
		    printk ("str = %s\n", str);
		    for (; i < num; i++) 
  			{
			    printk ("fish[%d] = %d\n", i, fish[i]);
  			}
		  	return 0;
		}
		void __exit moduleparam_exit (void)
		{
		  int i = 0;
		  printk ("irq = %d\n", irq);
		  printk ("str = %s\n", str);
		  for (; i < num; i++) 
		  {
		    printk ("fish[%d] = %d\n", i, fish[i]);
  		  }
		}

		module_init (moduleparam_init);
		module_exit (moduleparam_exit);
			ls /sys/module/moduleparam/parameters/
			ls fish -l 
			ls irq -l 
			因为所有的权限都是0,所以没有str文件
			cat 	/sys/module/moduleparam/parameters/irq
			echo 100 > 	/sys/module/moduleparam/parameters/irq 
			echo 11,22,33,44,55 >/sys/module/moduleparam/parameters/irq/fish
			看其在内核模块中的值发生了改变没?
			rmmod moduleparam
			发现发生了改变,神奇吧?!!!! 我们在用户空间改变了内核空间的值耶! 我就说神奇吧!哈哈
			
		4) 模块参数的使用场景: 
			驱动代码调试
			VAR声明为模块参数
			REG=VAR 

9 系统调用

谈谈对系统调用的理解?	
	9.1 系统调用的意义:
		是用户空间调用内核空间函数的一种方式
		由用户态进入内核态的一种方式
		注: 由用户态进入内核态的另一种方式是中断
		
		它保证了内核的安全
			允许应用程序调用内核中的函数
			以安全方式调用内核中的函数
			
	9.2 系统调用是如何实现的?
		应用程序首先使用适当的值填充寄存器
		然后调用特殊的指令
		该指令的执行 导致跳转到内核的某个位置去执行
		在该位置根据填充到寄存器中的值
		来找到内核中对应的函数 并调用该函数
		函数执行完毕后 原路返回到用户空间
		
			open ("a.txt", O_RDWR);
			适当的值: arch/arm/include/asm/unistd.h
				#define __NR_open 5
				如果是open系统调用  5
			寄存器 : r7
			特殊的指令:
				软中断指令		ARM swi
										X86	 int
				固定的位置 : 软中断异常产生后	跳转到异常向量表
						   再跳转到软中断异常处理函数中
						   arch/arm/kernel/entry-common.S
						   ENTRY(vector_swi)
						   Get the system call number.//获取系统调用号
						   adr tbl, sys_call_table  
						   @ load syscall table pointer 
						   sys_call_table [r7]
						   sys_call_table 存在于calls.S
				sys_call_table:                                                                                                                                   
  					.long sys_restart_syscall /* 0  -  old "setup()" system call  */
					.long sys_exit
					.long sys_fork
					.long sys_read
					.long sys_write
			 		.long sys_open      /* 5 */
						   ...
			
			总结:
			open (.....)  
			5
			swi
			------------------------
			sys_call_table[5]
			等价于
			sys_open(...)
			{
				.......
			}
			
	9.3 增加一个新的系统调用 	演示用户空间如何调用内核空间的函数
		cd /home/liuyang/driver/kernel
		1) 在内核下增加一个新函数
		vi arch/arm/kernel/sys_arm.c
		asmlinkage int sys_add(int x, int y)
		{
  			printk ("enter %s\n", __func__);
		    return x + y;
		}
	  2) 增加新的系统调用号
	 	vi arch/arm/include/asm/unistd.h
	 	#define __NR_add (__NR_SYSCALL_BASE + 378)
	  3) 更新系统调用表
	 	vi arch/arm/kernel/calls.S
	 	CALL(sys_add)
	  4) 编译内核 让开发板使用新内核
	 	 make uImage
	 	 cp arch/arm/boot/uImage /tftpboot
	 	 tftp 48000000 uImage
	 	 bootm 48000000 
	  5) 编写应用程序 在用户空间调用sys_add
		#include 
		int main (void)
		{
  		//open --> 5 --> r7 ---> swi
  		//open ("a.txt", O_RDWR);
  		//syscall (5, "a.txt", O_RDWR); //syscall是间接的系统调用
                                  		//这种写法和上面的效果一样
  		syscall (378, 10, 20);
  		pritnf ("res = %d\n", res);
  		return 0;
		}  
	  	arm-cortex_a9-linux-gnueabi-gcc test.c -o test
	  	cp  test ../../rootfs/
	  	./test 报没有libgcc_s.so.1 这个库,去交叉编译工具的安装包里找
	  	find-name /opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/ -name libgcc_s.so.1
	  	cp /opt/arm-cortex_a9-eabi-4.7-eglibc-2.18/arm-cortex_a9-linux-guneabi/sysroot/lib/libgcc_s.so.*  
	  	/home/liuyang/driver/rootfs/lib/ -a
	  	./test

你可能感兴趣的:(linux内核,嵌入式,驱动开发,linux,arm)