Linux设备驱动第二天(数组参数传递,模块相互调用、printk、内核GPIO函数、系统调用)

数据的模块参数声明:

/**
* 宏的作用:对数组进行模块参数声明,将来可以进行传递一些参数信息进来
* 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系统调用为例:

  1. 当调用open函数时,首先调用C的open函数实现;
  2. C库的open函数实现会做两件事:一是将open的系统调用号(__NR_open)保存在R7寄存器,另一个是调用SVC触发软中断异常,至此任务由用户空间陷入内核空间
  3. 一旦触发软中断异常,CPU跳转到内核准备好的异常向量表的对应位置(软中断的处理入口)
  4. 跳转到软中断的处理入口以后,也会做两件事:一个是R7中取出open的系统调用号,另一个是以系统调用号为索引再内核准备好的系统调用表(数组)中,找到对应的内核函数sys_open
  5. 找到以后,执行些内核函数,执行完毕,原路返回到用户究竟
  6. 用户应用程序的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);

添加系统调用参考这里写链接内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值