Linux驱动开发笔记总结(一)

来源:互联网 发布:基本款 外套 知乎 编辑:程序博客网 时间:2024/06/06 00:34
基于Cortex A8的Linux驱动编程初级基础知识总结
Linux操作系统中的驱动程序分3种:
1.字符设备 顺序读写,以字节为单位,不带缓冲区的设备
2.块设备 随机读写,以数据块为单位,带有缓冲区的设备
3.网络设备 不是基于文件系统访问的设备,而是通过一个特殊的网络接口(struct net_device)实现的。
Linux模块编程中需要用到的宏:
module_init:内核中定义的一个宏,用来修饰模块加载函数,可以将模块加载函数链接到.init.text段上。
module_exit:内核中定义的一个宏,用来修饰模块卸载函数,可以将模块卸载函数链接到.exit.text段
MODULE_LICENSE("GPL"):添加许可声明,必须加,否则会出现内核污染
如何在内核中添加一个组件?
共有两种方法,静态编译和动态加载模块。在Linux驱动编程中,一般使用动态加载模块来对内核进行添加组件的操作。如果使用静态编译一方面可能会导致内核过大,另一方面在添加和卸载组件时都需要重新编译内核,这样会十分麻烦。使用动态加载的方法,并不会将模块编译进内核,而是采用模块化的方式,在需要的时候可以动态的安装或卸载模块
Linux下字符设备驱动编程基本流程:
1.通过datasheet和原理图,了解设备的操作方法
2.定义模块的加载和卸载函数,在加载和卸载函数中实现相应的初始化和卸载时所需要执行的任务(需使用module_init和module_exit修饰)
3.实现对设备的相应操作函数,如open(),close(),ioctl(),write(),read()函数
4.如果设备需要用到中断的实现,则需编写中断处理函数
5.编译成模块,使用insmod命令进行模块的安装
在进行Linux下驱动模块编程的时候,通常会使用Makefile文件来对模块文件进行编译
Makefile文件内容如下:
obj_m += xxx.o//xxx表示模块文件名称,表示将整个文件编译成模块
all:
make -C /home/driver/cw210_kernel_2.6.35.7 M=$(PWD) modules//其中-C表示进入后面的内核原码路径,-M表示当前模块所在路径,modueles目标指向obj-m变量中设定的模块
clean:
rm -rf *.o *.ko *.order *.sy* *.mod*
Linux模块编程常用命令:
insmod 安装模块 insmod xxx.ko
rmmod 删除模块rmmod xxx.ko
lsmod 查看模块信息
modinfo 查看单个模块信息modinfo xxx.ko
内核模块编程的特点:
1、不能使用C库函数 也不能访问标准C头文件
2、不能使用浮点数
3、没有内存保护机制
4、必须使用GNU C进行编程
5、内核为每个进程分配了一个8KB的定长堆栈
6、内核支持中断,抢占和SMP,时刻注意同步和并发
7、要考虑可移植的重要性
导出符号:
导出符号的本质就是在内核态a.c中的函数或者变量要被b.c使用,如何处理?
在a.c文件中用EXPORT_SYMBOL或EXPORT_SYMBOL_GPL宏对将要被使用的函数或变量进行修饰,并编写一个头文件,在其中声明函数或变量是在外部定义的。然后直接在b.c文件中包含这个头文件,并使用函数或变量即可
a.c : func
EXPORT_SYMBOL(func);EXPORT_SYMBOL_GPL(func);

a.h:

extern int func(void);
b.c:
#include "a.h"func();
模块参数:
模块参数本质就是用来解决安装模块时传递参数的问题。在实际开发中,可以用来更改在模块中被使用的参数的值以便于调试。
对要做为参数的变量采用module_param修饰,对于数组则使用module_param_array来修饰
module_param(name,type,perm)
name 变量名称
type 数据类型(bool,int,short,long,charp)
perm 指定该变量的访问权限 0664
module_param_array(name,type,nump,perm)
name 变量名称
type 数据类型(bool,int,short,long,charp)
nmup 有效数组元素个数的指针
perm 指定该变量的访问权限 0664
具体代码示例如下:
#include <linux/init.h>#include <linux/module.h>//全局变量,模块参数static int irq;static char *pstr;static int fish[10];static int nr_fish;//用来记录有效的数组元素个数//模块参数的声明module_param(irq, int, 0664);module_param(pstr, charp, 0);module_param_array(fish, int, &nr_fish, 0640);static int moduleparam_init(void){    int i = 0;printk("%s: irq=%d, pstr=%s\n",__func__, irq, pstr);for(; i<nr_fish; i++){printk("%s: fish[%d] = %d\n",__func__, i, fish[i]);}return 0;}static void moduleparam_exit(void){    int i = 0;printk("%s: irq=%d, pstr=%s\n",__func__, irq, pstr);for(; i<nr_fish; i++){printk("%s: fish[%d] = %d\n",__func__, i, fish[i]);}}module_init(moduleparam_init);module_exit(moduleparam_exit);MODULE_LICENSE("GPL");
在安装模块时声明参数的值即可,如:insmod moduleparam.ko irq=5 pstr=Phoenix fish=1,2,3,4,5
【注意】在模块中声明权限不为0的模块参数,会在/sys/module/xxx/parameters/目录下产生一个与参数同名的文件,在实际应用中可以通过改变这个文件中的值来动态的改变模块参数的值
printk优先级
printk是在在内核中运行的向控制台输出显示的函数,一共有8个优先级。
    #define KERN_EMERG 0/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/    #define KERN_ALERT 1/*报告消息,表示必须立即采取措施*/    #define KERN_CRIT 2/*临界条件,通常涉及严重的硬件或软件操作失败*/    #define KERN_ERR 3/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/    #define KERN_WARNING 4/*警告条件,对可能出现问题的情况进行警告*/    #define KERN_NOTICE 5/*正常但又重要的条件,用于提醒*/    #define KERN_INFO 6/*提示信息,如驱动程序启动时,打印硬件信息*/    #define KERN_DEBUG 7/*调试级别的消息*/

一般在/proc/sys/kernel/printk文件中可以查看printk优先级的相关信息

cat /proc/sys/kernel/printk

7      4     1 7
第一个值:可以打印到终端的最大值
printk中使用的优先级要小于该值才能显示出来
第二个值:默认优先级
printk("hello\n");//默认优先级是4

使用方法:

    printk(KERN_ERR "helloworld\n");    printk(<3> "helloworld\n");

改变printk优先级输出的方法
1、可以通过命令echo 8>/proc/sys/kernel/printk来改变printk的显示级别
2、第一种方法对于内核启动时的信息是无法改变printk的优先级输出的,但是可以通过设置开发板参数来实现
setenv bootargs root=/dev/nfs ...... debug/quiet/loglevel=数字
在实际的开发中,当内核启动时出现问题,可以通过修改bootargs变量来设置printk的输出优先级,这样可以大大的减少内核输出中的一些不重要的信息,减少找出错误的难度。
小实验:通过/proc/sys/kernel/printk文件修改printk输出优先级的方式动态改变模块的打印信息
代码如下:
#include <linux/init.h>#include <linux/module.h>static int prink_init(void){printk(KERN_EMERG "0");printk(KERN_ALERT "1");printk(KERN_CRIT "2");printk(KERN_ERR "3");printk(KERN_WARNING "4");printk(KERN_NOTICE "5");printk(KERN_INFO "6");printk(KERN_DEBUG "7");return 0;}static void printk_exit(void){printk(<0> "0");printk(<1> "1");printk(<2> "2");printk(<3> "3");printk(<4> "4");printk(<5> "5");printk(<6> "6");printk(<7> "7");}module_init(printk_init);module_exit(printk_exit);MODULE_LICENSE("GPL");
实验步骤:
insmod prinkall.ko
echo 5>/proc/sys/kernel/printk
rmmod printk.ko
系统调用
系统调用是用户空间的程序调用内核空间程序的一种方式
实现机制:
1、进程先将系统调用号填充寄存器;
2、调用一个特殊的指令;
3、让用户进程跳转到内核事先定义好的一个位置;
4、内核的位置是entry(vector_swi) //entry-common.s
5、检查系统调用号,通过系统调用号告诉内核所请求的服务
6、检查系统调用表(sys_call_table)找到所调用的内核函数入口地址
7、调用该函数,执行完毕后返回用户进程
如何添加一个自己定义的系统调用
1)在内核中的sys_arm.c文件中添加一个新的函数

vi arch/arm/kernel/sys_arm.c

asmlinkage int sys_add(int x,int y){    printk("enter %s\n",__func__);    return x+y;}

PS:内核编程时不能使用浮点数(float,double)
2)更新头文件unistd.h

vi arch/arm/include/asm/unistd.h

    #define __NR_add  (__NR_SYSCALL_BASE+366)

3)更新系统调用表call.S

vi arch/arm/kernel/calls.S

    CALL(sys_add)

4)重新编译内核,让开发板重新加载新的内核
make
cp arch/arm/boot/zImage /tftpboot
重启开发板
5)编写测试程序
0 0