回顾:
1.linux系统分为用户空间和内核空间
用户空间
内核空间
2.linux内核编程的规范
2.1入口函数:module_init
intdriver_entry(void);
2.2出口函数:module_exit
voiddriver_exit(void);
注意:
1.不能使用标准的C库头文件和C库
2.不能处理浮点数,一般把浮点数的运算放在用户空间来
2.3内核代码编译
obj-m += xxxx.o
make -C/opt/kernel SUBDIRS=/opt/driver/day02/1.0 modules
2.4内核模块操作
insmod
rmmod
lsmod
modinfo
modprobe
注意:建议搭建在配置busybox时,使用完整版的模块操作命令
模块加载命令insmod和modprobe对比:
insmod仅仅只是加载模块,不会去检查模块的依赖关系,如果使用insmod加载模块,必须人为事先直到模块之间的依赖。并且insmod在加载模块时,必须指定模块所在的路径!
insmod /home/helloworld.ko
modprobe不仅仅用于加载或者卸载模块,还能够根据modues.dep来检查模块之间的依赖,根据依赖关系,决定先加载哪个模块。并且modprobe加载模块时,默认的模块在/opt/rootfs/lib/module/2.6.35.7../extra,不会到当前目录去找模块!
2.5modules.dep依赖文件从何而来?
1.进入内核源码只执行一次make modules即可(module.order)
2.进入自己的驱动模块所在的路径,进行安装模块
make modules
make install
Makefile:
install:
make -C /opt/kernel SUBDIRS=$(PWD)modules_install INSTALL_MOD_PATH=/opt/
结果:在/opt/下生成一个目标目录lib目录,将lib下的内容全部拷贝到根文件系统的lib目录中即可
2.6给内核模块添加相关信息
MODULE_LICENSE("GPL");//必须添加
MODULE_AUTHOR("youcw<youcw@tarena.com.cn>"); //作者信息
MODULE_VERSION("1.0.0");//busybox-1.19.4的modinfo命令有问题,需要使用新版的busybox才能获取版本信息。
2.7内核模块参数声明
module_param(name,type,perm)
module_param_array(name,type,nump,perm)
注意:perm:0和非0
2.8内核符号导出
EXPORT_SYMBOL
EXPORT_SYMBOL_GPL
2.9printk内核打印函数
指定了8个输出级别0~7
#defineKERN_WARNING "<4>"
默认打印输出级别:限制哪些信息打印输出,哪些不做输出
默认打印输出级别的设置方法:
1./proc/sys/kernel/printk
2.uboot的bootargs(quiet,debug,loglevel=数字)
**************************************************************************************
Day3:
一:linux内核提供了GPIO操作的库函数:
内核为了方便驱动操作GPIO,内核封装了一套GPIO操作的库函数,这些函数使用的前提是GPIO为输入或者输出。并且这些库函数的底层硬件寄存器的操作都由芯片公司在平台代码中帮你写好,驱动只需调用库函数操作即可!
GPIO的软件编号:
芯片手册对于每一个硬件GPIO,都给定一个符号“GPC1_3”或者"GPC1_3",内核对于每一个GPIO也给出了对应的软件标号(本质上就是一个数字),当然每一个GPIO都有唯一的编号!
硬件标识 软件编号
GPC1_3 S5PV210_GPC1(3)
内核提供的GPIO库函数就是通过软件编号来对硬件GPIO进行操作!
(1)库函数:
gpio_request(软件编号,标签(字符串))
GPIO在操作之前,必须向内核去申请资源!一旦申请成功,别的模块就不能再访问这个资源了。
gpio_direction_output(软件编号,高/低电平)//配置GPIO为输出口,并且输出给定电平,GPIO的控制权由CPU掌管
gpio_direction_input(软件编号);//配置GPIO为输入口,GPIO控制权由外设来掌管
gpio_set_value(软件编号,电平状态); //函数使用的前提是GPIO为输出
gpio_get_value(软件编号); //获取GPIO的状态,GPIO输入输出无要求
gpio_free(软件编号); //如果GPIO资源不再使用,一定要归还给操作系统,供其他模块使用。
gpio的控制权,或者gpio连接的总线的控制权归属问题:
cpu设置gpio为输出,则控制权归cpu,cpu设置gpio为输入,则控制权归外设!谁设置输出,谁掌握控制权!
头文件:
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
案例:gpio编程
在加载驱动模块时,点亮灯
在卸载驱动模块时,关闭灯
案例:多文件编译
将开关灯操作封装在一个函数中int led_config(int gpio, int value),并且让这个函数在一个C中实现,另外一个C文件来调用这个函数进行对灯操作!
提示:
1.led.h
/*
函数功能:配置操作GPIO
参数:gpio:软件编号,value:gpio电平状态
*/
extern voidled_config(int gpio, int value);
2.led.c
/*函数实现*/
voidled_config(int gpio, int value)
{
gpio_direction_output
gpio_set_value
}
EXPORT_SYMBOL_GPL(...);
MODULE_LICENSE...
3.led_drv.c
for(...) {
led_config... //调用
}
4.不允许使用insmod,rmmod,必须使用modprobe
******************************************************
二:linux系统调用过程(原理):
1.linux系统给每一个系统调用函数都指定了一个号,这个号称之为系统调用号,例如:exit函数的系统调用号为1,write的系统调用号为4等
2.每当应用程序调用系统调用函数时(例如write函数),首先跑到C库的write函数实现;
3.C库的write函数首先把write函数对应的系统调用号保存在R7寄存器中,然后调用swi或者svc触发一个软中断异常,CPU要处理这个异常,需要跳转到异常向量表的位置去处理。
4.linux系统的异常向量表是在内核启动初始化时会帮你建立好异常向量表(向量表的地址为0xFFFF0000),软中断对应的入口为vector_swi,应用程序触发软中断,那么CPU就会到这个入口去处理软中断;
5.进入软中断vector_swi入口以后,从R7寄存器中取出之前保存的系统调用号,然后在内核已经准备好的系统调用表中,以系统调用号为索引(下标)在系统调用表中找到自己对应的函数(sys_write),然后执行此函数(存在于内核空间)。
6.执行完这个函数以后,再原路返回到用户空间!
总结:用户空间到内核空间通过软中断来实现!
7如何添加一个新的系统调用呢?
内核空间需要完成的工作:
1.在arch/arm/include/asm/unistd.h中添加一个新的系统调用号。
#define__NR_OABI_SYSCALL_BASE 0x900000
..........
#define __NR_buzzer_drv (__NR_SYSCALL_BASE+366)
2.在arch/arm/kernel/calls.S在这个系统调用表中添加一个新项。
CALL(sys_buzzer_drv)
3.在内核源码arc/arm/kernel/sys_arm.c中添加一个内核函数的实现,类似sys_write
用户空间要完成的工作:
1.linux系统提供了一个系统调用函数syscall来帮你完成保存系统调用号到R7寄存器,然后调用svc触发软中断
int syscall(int number, ...);
返回值:就是系统调用号对应的内核函数的返回值,如果出错一律返回-1
number:系统调用号
...:系统调用号对应函数的参数
案例:利用syscall实现打印和程序退出
编译方法1:
arm-linux-gcc -otest test.c
编译方法2:
汇编:
arm-linux-gcc -Stest.c //test.s
vim test.s //不想在调用syscall,我想直接保存系统调用号,然后调用svc触发软中断
修改如下:
.file "test.c" ->.file "test.s"
mov r0, #4 -> mov r7,#4
mov r1, #1 -> move r0, #1
movw r2, #:lower16:.LC0 ->movw r1, #:lower16:.LC0
movt r2, #:upper16:.LC0 ->movt r1, #:upper16:.LC0
mov r3, #14 -> mov r2, #14
bl syscall -> svc 0
mov r0, #1 -》mov r7,#1
mov r1, #0 -> move r0,#0
bl syscall -> svc 0
保存退出
arm-linux-gcc -otest test.s
编译方法3:
由于main函数在执行前,必须先执行_start
arm-linux-gcc -Stest.c //test.s 目的纯汇编代码
vim test.s
.file "test.c" ->.file "test.s"
删除一下信息:
编译器信息处理:
2 .eabi_attribute 27, 3
3 .fpu neon
4 .eabi_attribute 20, 1
5 .eabi_attribute 21, 1
6 .eabi_attribute 23, 3
7 .eabi_attribute 24, 1
8 .eabi_attribute 25, 1
9 .eabi_attribute 26, 2
10 .eabi_attribute 30, 6
11 .eabi_attribute 18, 4
栈的处理:
22 @ args = 0, pretend = 0, frame = 8
23 @ frame_needed = 1, uses_anonymous_args = 0
24 stmfd sp!, {fp, lr}
25 add fp, sp, #4
26 sub sp, sp, #8
27 str r0, [fp, #-8]
28 str r1, [fp, #-12]
栈的处理:
38 sub sp, fp, #4
39 ldmfd sp!, {fp, pc}
然后再修改一下内容:
.file "test.c" ->.file "test.s"
mov r0, #4 -> mov r7,#4
mov r1, #1 -> move r0, #1
movw r2, #:lower16:.LC0 ->movw r1, #:lower16:.LC0
movt r2, #:upper16:.LC0 ->movt r1, #:upper16:.LC0
mov r3, #14 -> mov r2, #14
bl syscall -> svc 0
mov r0, #1 -》mov r7,#1
mov r1, #0 -> move r0,#0
bl syscall -> svc 0
arm-linux-gcc -ctest.s //编译成目标文件test.o
arm-linux-ld -otest test.o //连接成可执行程序test
反汇编:
arm-linux-objdump -d test >test.dis
查看"Hello,world"只读数据段的信息
arm-linux-objdump -j .rodata -stest //查看只读数据段的
信息
总结:用户空间转向内核空间通过软中断!
案例:利用syscall实现文件的读写
实验步骤:
gcc file.c
touch a.txt
./a.out
cat a.txt
案例:在内核中添加新的系统调用,实现两个数加法运算
案例:在内核中添加新的系统调用,实现开关灯
实验文档:ftp/drivers/syscall_add.doc
sys_arm.c
//index:表示哪个灯,1或者2
//cmd:开还是关,1表示开,0表示关
voidsys_ledconfig(int index, int cmd)
{
if (index == 1) //第一个灯
//申请资源
else if (index == 2) //第二个灯
//申请资源
配置输出口,输出1
//释放资源
}
问题:重新编译内核以后,重启系统,内核跑到“USB HUB”地方停住。
解决方法:
cd /opt/kernel
cpconfig_CW210_linux_V1.0 .config
make zImage
asmlinkage:强制要求传递参数通过栈,而不是通过寄存器,默认采用寄存器(r0~r3)
调试内核代码使用printk打印信息!
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
总结:
1.gpio资源的申请、释放以及操作库函数
gpio是一种软件资源,需要时需申请,其他模块不可再使用,不用时,即释放,其他模块可以使用。
gpio必须配置为输出或者输入
(1)gpio_request(gpio软件编号,标签(字符串))
(2)gpio_free(gpio软件编号)
(3)gpio_direction_output(gpio软件编号,int value)
(4)gpio_direction_input(软件编号)
(5)gpio_set_value(gpio软件编号,int value) gpio为输出
(6)gpio_get_value(gpio软件编号) gpio为输出、输入都可
2.linux系统调用原理