数据的模块参数声明:
/**
* 宏的作用:对数组进行模块参数声明,将来可以进行传递一些参数信息进来
* name:数组名
* type:数组元素的数据类型
* nump:有效数组元素个数的指针
* 比如定义一个数组的时候,可能只想操作数据中的某些数据
* perm:访问权限 跟之前的一样
**/
module_param_array(name,type,nump,perm);
例如:
static int fish[10];
static int nr_fish;
module_param_array(fish,int,&nr_fish,0664);//声明
使用:
insmod helloworld.ko fish = 1,2,3 # nr_fish = 3
echo 10,30,40,50,60 -> /sys/..../fish # nr_fish = 6
注意:传递参数时,数组元素个数要小于等于10(fish[10]);
#include <linux/init.h>
#include <linux/module.h>
static int fish[10];
static int nr_fish;
module_param_array(fish,int,&nr_fish,0664);//声明参数
static int helloworld_init(void)
{
int i;
for(int i=0;i<nr_fish;i++){//nr_fish 注意是有效数据个数
printk(" fish[%d] = %d \n",i,fish[i]);
}
return 0;
}
static void helloworld_exit(void)
{
for(int i=0;i<nr_fish;i++){//nr_fish 注意是有效数据个数
printk(" fish[%d] = %d \n",i,fish[i]);
}
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL");
:wq 保存退出。
新建一个makefile:
obj -m += helloworld.o
all:
make -C /opt/kernel SUBDIRS = $(PWD) modules
clean:
make -C /opt/kernel SUBDIRS = $(PWD) clean
编译
cp .ko 到开发板
测试:
insmod helloworld.ko
rmmod helloworld
insmod helloworld.ko fish = 1,2,3,4,5,6
cat /sys/module/helloworld/parameters/fish #读文件
echo 10,20,30,40 > /sys/module/helloworld/parameters/fish
cat /sys/module/helloworld/parameters/fish
rmmod helloworld
Linux内核符号导出:
回忆:应用程序一个文件中的函数供别的软件使用
test.h
#ifndef __TEST_H
#define __TEST_H
extern void test(void);
#endif
test.c
#include "test.h"
void test(void){
printf("hello test \n");
}
main.c
#include "test.h"
void main(void){
test();
return 0;
}
编译:
方法一:gcc -o main main.c test.c test.h
方法一:
gcc -shard -fpic -o libtest.so test.c test.c
gcc -o main main.c -L. -ltest //-L.指定动态库的位置,.表示在当前目录;-ltest中的l表libary
内核的符号导出:
本质目的:导出的函数或者变量能够给其他模块进行使用;
导出方法:
EXPORT_SYMBOL(函数名或变量名)
EXPORT_SYMBOL_GPL (函数名或变量名)
前者导出的函数名或者变量名,其他任何模块都可以进行调用使用
前者导出的函数名或者变量名,只能给那些遵循GPL协议的模块使用,如果要想调用,必须添加MODULE_LICENSE(“GPL”)
实现过程:
1,编写头文件进行函数的声明
2,编写源码文件进行函数的实现
3,在函数的原型后面添加
EXPORT_SYMBOL_GPL或者EXPORT_SYMBOL进行导出
4,在其他模块添加头文件,进行调用即可;
5,编译,只需要将各个.c分别编译成对应的ko文件即可
6,注意模块ko的加载顺序
案例:helloworld.ko 调用test_module.ko中的test_module函数
test_module中的test.h源文件:
#ifndef __TEST_H
#define __TEST_H
extern void test(void);//函数声明
#endif
test_module中的test.c源文件:
#include <linux/init.h>
#include <linux/module.h>
void test(void){
printk("hello test");
}
//导出函数供其他模块使用,只给那些遵循GPL协议的模块调用
EXPORT_SYMBOL_GPL(test);
MODULE_LICENSE("GPL");
注意这个模块可以不用出口、入口函数
hellworld.c的文件:
#ifndef __TEST_H
#define __TEST_H
extern void test(void);//函数声明
#endif
test_module中的test.c源文件:
#include <linux/init.h>
#include <linux/module.h>
#include "test.h" //添加头文件
static int helloworld_init(void)
{
test();//调用其他模块的函数
return 0;
}
static void helloworld_exit(void)
{
test();
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL");
makefile 文件(注意要编译两个库文件)
obj -m += helloworld.o test.o #分别编译两个ko
all:
make -C /opt/kernel SUBDIRS = $(PWD) modules
clean:
make -C /opt/kernel SUBDIRS = $(PWD) clean
编译
把两个.ko分别考到开发板
测试步骤:
insmod test.ko
insmod helloworld.ko
rmmod helloworld
rmmod test
linux内核打印函数printk
应用程序打印printf
对比:
相同点:都是用于打印输出信息,只要CPU执行打印输出函数 ,都会消耗CPU的资源!
不同点:printf函数定义在C库,运行在用户空间;
printk运行在内核空间,并且printk打印函数能够指定输出的级别!
printk的打印级别
#define KERN_EMERG "<0>" /* system is unusable 通常是系统崩溃前的消息*/
#define KERN_ALERT "<1>" /* 需要立即处理*/
#define KERN_CRIT "<2>" /*严重情况 */
#define KERN_ERR "<3>" /*错误情况*/
#define KERN_WARNING "<4>" /* 有问题情况 */
#define KERN_NOTICE "<5>" /* 正常情况*/
#define KERN_INFO "<6>" /* 消息型 */
#define KERN_DEBUG "<7>" /* 调试消息 */
printk特点:
1,能够指定级别(0-7)
2,数字 越大级别越小
3,使用:
printk(“<3>”,”hello…”);
4,系统有一个默认的输出级别(默认的级别为7),可以进行设置
5,如果软件在使用打印时,指定的输出级别小于默认的输出级别,信息一律打印输出,否则不进行打印输出
6,如何设置默认的输出级别?
有两种方法:
方法一:系统启动完毕,通过修改配置文件/proc/sys/kernel/printk来修改默认的级别。注意:这种方法前题 是系统启动完毕,无法设置内核启动时的打印输出级别。
如:
cat /proc/sys/kernel/printk #查看
echo 8 > /proc/sys/kernel/printk #修改
方法二:通过设置uboot的bootargs来指定打印输出级别
setenv bootargs roots = /dev/nfs nfsroot = ... debug
setenv bootargs roots = /dev/nfs nfsroot = ... quite
setenv bootargs roots = /dev/nfs nfsroot = ... loglevel = 数字
通过这种方法可以稍微的减少内核启动时间。
案例:
#include <linux/init.h>
#include <linux/module.h>
static int helloworld_init(void)
{
printk("<1>","level 1");
printk("<2>","level 2");
printk("<3>","level 3");
printk("<4>","level 4");
printk("<5>","level 5");
printk("<6>","level 6");
printk("<7>","level 7");
return 0;
}
static void helloworld_exit(void)
{
printk("<1>","level 1");
printk("<2>","level 2");
printk("<3>","level 3");
printk("<4>","level 4");
printk("<5>","level 5");
printk("<6>","level 6");
printk("<7>","level 7");
}
module_init(helloworld_init);
module_exit(helloworld_exit);
MODULE_LICENSE("GPL");
实验步骤:
1,insmod helloworld.ko
2, rmmod helloworld.ko
3,cat /proc/sys/kernel/printk
4,echo 8 > /proc/sys/kernel/printk
5,insmod helloworld.ko
6, rmmod helloworld.ko
7,进入uboot,设置bootargs,指定debug,quiet,loglevel
8,insmod helloworld.ko
9, rmmod helloworld.ko
10,cat /proc/sys/kernel/printk
内核GPIO操作库函数
内核专门提供了一些库函数进行输入、输出操作
Linux内核提供的GPIO操作库函数:
明确一点:
Linux内核对于CPU上的每一个GPIO,在内核初始化时就已经将各个硬件IO作为一种资源供驱动使用!
对于每一个硬件IO在内核中都有唯一的软件编号(类似工号或学号),例如:GPCI_3->S5PV210_GPC1(3)
一旦GPIO资源形成以后,内核提供了相关的操作方法;
相关函数:
/**
*功能:向内核申请GPIO资源(仅限于输入或输出),一旦申请成功,
* 别人不能再次申请,直到释放
*gpio:GPIO软件编号
*name:标识
*返回:查询内核相关功能的使用(source insight -> 工具栏R )
**/
int gpio_request(unsigned gpio, const char *name) ;
/**
*功能:用来设置gpio为输出功能,同时设置gpio输出的值
*gpio:GPIO软件编号
*value:脚本状态 1为高 0为低
**/
gpio_direction_output(unsigned gpio, int value);
/**
*功能:用来设置gpio为输入功能
*gpio:GPIO软件编号
**/
gpio_direction_output(unsigned gpio);
/**
*功能:用来设置gpio为的状态为value(必须配置为输出)
*gpio:GPIO软件编号
*value:脚本状态
**/
gpio_set_value(unsigned gpio, int value);
/**
*功能:用来获取gpio的状态(既可以为输出也可以为输入)
*gpio:GPIO软件编号
**/
gpio_get_value(unsigned gpio);
/**
*功能:用来释放gpio口
*gpio:GPIO软件编号
**/
gpio_free(unsigned gpio);
使用记得参考内核代码copy头文件(不带双引号的头文件),然后申请、使用、释放;
案例:加载模块点亮灯,卸载模块关闭所有灯。
但凡以Linux开头的文件,一般与硬件无关;
但凡不以Linux开头的文件,与硬件无关密切相关;
led_drv.c驱动代码文件
#include<linux/init.h>
#include<linux/module.h>
//相关头文件
#include<asm/gpio.h>
#include<plat/gpio-cfg.h>
static int led_init(void){
//申请GPIO资源
gpio_request(S5PV210_GPC1(3),"LED1");
gpio_request(S5PV210_GPC1(4),"LED2");//ctrl+n
//配置GPIO为输出口,输出高电平
gpio_direction_output(S5PV210_GPC1(3),1);
gpio_direction_output(S5PV210_GPC1(4),1);//ctrl+n
printk("led on! \n");
}
static void led_exit(void){
//设置GPIO的状态为低电平
gpio_set_value(S5PV210_GPC1(3),0);
gpio_set_value(S5PV210_GPC1(4),0);
//释放GPIO资源
gpio_free(S5PV210_GPC1(3));
gpio_free(S5PV210_GPC1(4));
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
Makefile文件:
obj -m += led_drv.o
KDIR = /opt/kernel #指定内核源码路径
all :
make -C $(KDIR) SUBDIRS = $(PWD) modules
clean:
make -C $(KDIR) SUBDIRS = $(PWD) clean
编译,将.ko文件拷贝到开发板,然后安装模块;
如果有很多led的时候,led_drv中将会有很多重复代码,采用结构体优化后的代码:
#include<linux/init.h>
#include<linux/module.h>
//相关头文件
#include<asm/gpio.h>
#include<plat/gpio-cfg.h>
//声明描述LED硬件资源的结构体
struct led_resource{
int gpio;//gpio软件编号
char *name;//GPIO的标签
}
//分配初始化2个LED的硬件资源信息
static struce led_resource led_info[] = {
[0] = {
.gpio = S5PV210_GPC1(3),
.name = "LED1"
},
[1] = {
.gpio = S5PV210_GPC1(4),
.name = "LED2"
}
};//采用了结构体标记初始化
static int led_init(void){
int i ;
for(i = 0;i<ARRAY_SIZE(led_info);i++){//ARRAY_SIZE求数组中元素个数
//申请GPIO资源
gpio_request(led_info[i].gpio,led_info[i].name);
//配置GPIO为输出口,输出高电平
gpio_direction_output(led_info[i].gpio,1);
}
printk("led on! \n");
}
static void led_exit(void){
for(i = 0;i<ARRAY_SIZE(led_info);i++){//ARRAY_SIZE求数组中元素个数
gpio_set_value(led_info[i].gpio,0);
gpio_free(led_info[i].gpio);
}
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
结构体标记初始化的特点:
1,不用全部初始化
2,不需要按顺序初始化
内核提供一个宏:ARRAY_SIZE(X) 用来获取数组元素的个数
案例:加载模块打开蜂鸣器,卸载模块关闭蜂鸣器;
系统调用工作机制(系统调用面试必考)
系统调用接口SCI
内核调用原理:
这里以open系统调用为例:
- 当调用open函数时,首先调用C的open函数实现;
- C库的open函数实现会做两件事:一是将open的系统调用号(__NR_open)保存在R7寄存器,另一个是调用SVC触发软中断异常,至此任务由用户空间陷入内核空间
- 一旦触发软中断异常,CPU跳转到内核准备好的异常向量表的对应位置(软中断的处理入口)
- 跳转到软中断的处理入口以后,也会做两件事:一个是R7中取出open的系统调用号,另一个是以系统调用号为索引再内核准备好的系统调用表(数组)中,找到对应的内核函数sys_open
- 找到以后,执行些内核函数,执行完毕,原路返回到用户究竟
用户应用程序的open函数返回
总结:用户你空间切换到内核空间靠软中断!
如何添加自己新的系统调用呢?
答:实施步骤:
一、内核空间要完成的任务:
1,在内核源码中的arch/arm/inculde/asm/unistd.h中添加新的系统调用函数的系统调用号。#define __NR_add 366
2,在内核源码的arch/arm/kernel/call.s中的系统调用表中添加系统调用内核实现函数的入口。CALL(sys_add)
3,在内核源码arch/arm/kernel/sys_arm.c文件中添加sys_add函数的实现
二、用户空间要完成的任务:
Linux系统为用户提供了syscall函数来实现以上两个步骤
in syscall(int number,…);
函数功能:
1,保存add 系统调用号366到R7中
2,调用SVC触发软中断异常
参数:
number:系统调用号
….:系统调用函数的形参,例如,
write(1,"hello,world \n",12);
syscall(4,1,"hello,world \n",12);
添加系统调用参考这里写链接内容: