驱动第二天

驱动第二天
[1] 划分模块(模块化的编程思想)
    把大的任务划分成功能比较独立的模块,模块一般都包含三大部分:
    1. 初始化(函数)
    2. 功能
    3. 退出(函数)
   
[2] 使用面向对象的编程思想实现模块
    一般一个模块就是实现一类对象,面向对象的编程思想,需要:
    1. 描述对象
       1. 静态特征描述(类/结构体和共用体)
       2. 动态特征描述(函数/方法)
       
    2. 使用描述的对象(类)
       1. 实例化对象/定义结构体或共用体变量(分配内存)
       2. 使用对象
[3] 模块管理
    1. 代码和Makefile
       见工程
       
    2. 加载命令
       insmod module_demo.ko
       
    3. 查看模块加载状态
       lsmod | grep module
       原理:
       cat /proc/modules
       
       ls /sys/module/模块名字/
       
    4. 卸载命令
       rmmod module_demo.ko
       
    5. 查看打印信息
       dmesg | tail
       
    6. 查看模块信息
       modinfo module_demo.ko
       
    7. 符号表
       定义:符号表是指变量名、函数名和他们地址的对应关系
       导出符号是指,将模块定义的变量或函数导出给别的模块用,导出方法见《module》代码
       
       使用别的模块导出的符号的步骤如下:
       1. 拷贝别的模块的符号表
          cp ../module/Module.symvers .
         
       2. 在模块代码中声明,并且使用
          例:
          extern int myint;
          printk("myint=%d\n", myint);
         
       3. 编译
          make
         
       注意:必须先加载导出符号的模块,再加载使用符号的模块
       
       *.ko 是编译出来的模块的可执行代码

 [4] 通过 vi+ctags 阅读大型工程源码
    1. windows下用source insight可以建立源码工程,同步之后就可以看相关源码和跳转了
    2. linux下可以用 vi+ctags 的组合查看相应的源码:
       a. linux系统中必须安装vi和tags。
       b. 将源码放到linux中的目录中。
       c. 进入源码的顶层目录,用vi打开任意源码文件(注意,只能在顶层目录打开,可以打开任意源码,如include中的linux/fs.h)可以使用一下命令:
       在顶层目录中执行:vi include/linux/fs.h
       d. 然后按F9,下面会自动产生:“Generate tags ...”,第一次使用的话,等待的时间有点长。
       e. 等完成之后,我们就可以利用vi -t xxx查看自己想要的函数、宏定义、结构体等等(注意:还是一样,必须在源码包的顶层目录中执行vi -t xxx,因为我们是在顶层目录中生成的tags文件)
 
【符号导出】

**********************************************************

文件夹:module_test(module_demo.c)

/************************* 必须有的部分 ********************/

// 所有模块都要用到的头文件

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

/*

 * @brief 模块的初始化函数

 * @return 0 模块加载成功

 * !0 模块加载失败,返回错误码

 * @notes __init 修饰模块加载函数,模块加载完成后,会释放模块加载函数占用的内存

 */

int __init module_demo_init(void)

{

 extern int myint;

 printk("module demo myint = %d\n", myint);

 

 return 0;

}

/*

 * @brief 模块的卸载函数

 * @notes __exit 修饰模块卸载函数,静态加载模块,会释放模块卸载函数占用的内存

 */

void __exit module_demo_exit(void)

{

}

// 告诉内核模块的加载函数

module_init(module_demo_init);

// 告诉内核模块的卸载函数

module_exit(module_demo_exit);

// 告诉内核模块的许可

MODULE_LICENSE("GPL");

对应Makefile

# KERNELRELEASE 变量装载的是内核版本,变量在内核顶层目录下的Makefile赋值

ifeq ($(KERNELRELEASE),)

内核源代码目录(模块将运行的内核的)

KERNELDIR ?= /usr/src/linux-headers-3.5.0-17-generic #(uname -r查看)

PWD := $(shell pwd)

调用内核顶层目录下的Makefile,是它调用本Makefile编译本模块

# -C 在做任何事情之前,进入指定目录

# M= 告诉内核顶层目录的Makefile,模块的位置

# modules 告诉内核顶层目录的Makefile 仅仅编译模块

all:

 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

清除编译出来的文件

clean:

 rm -rf *.*~ core .depend .*.cmd *.ko *.mod..tmp_versions Module* modules*

else

被内核顶层目录的Makefile调用,来编译模块

obj-+= module_test.o #模块重命名

module_test-objs := module_demo.o

endif

******************************************************************

文件夹module(module_demo.c)

/********************************** 必须有的部分 ****************************/

// 所有模块都要用到的头文件

#include <linux/module.h>

#include <linux/moduleparam.h>

#include <linux/kernel.h>

/*

 * @brief 模块的初始化函数

 * @return 0 模块加载成功

 * !0 模块加载失败,返回错误码

 * @notes __init 修饰模块加载函数,模块加载完成后,会释放模块加载函数占用的内存

 */

int __init module_demo_init(void)

{

 int i = 0;

 

 extern short myshort;

 printk("myshort = %d\n", myshort);

 

 extern char *string;

 printk("string = %s\n", string);

 

 extern int array[10];

 extern int num;

 for (= 0; i < num; i++) {

  printk("array[%d]= %d\n", i, array[i]);

 }

 

 return 0;

}

/*

 * @brief 模块的卸载函数

 * @notes __exit 修饰模块卸载函数,静态加载模块,会释放模块卸载函数占用的内存

 */

void __exit module_demo_exit(void)

{

}

// 告诉内核模块的加载函数

module_init(module_demo_init);

// 告诉内核模块的卸载函数

module_exit(module_demo_exit);

// 告诉内核模块的许可

MODULE_LICENSE("GPL");

/********************************** 可选的部分 ****************************/

// 模块参数

// sys虚拟文件中查看参数值:cat /sys/module/module_demo/parameters/myint

// 加载模块命令

// insmod module_example.ko myshort=5 myint=6

short myshort = 0;

module_param(myshort, short, 0400);

MODULE_PARM_DESC(myshort, "myshort demo");

int myint = 0;

module_param(myint, int, 0400);

MODULE_PARM_DESC(myint, "myint demo");

long mylong = 0;

module_param(mylong, long, 0400);

MODULE_PARM_DESC(mylong, "mylong demo");

// 加载模块命令

// insmod module_example.ko string="hello"

char *string = 0;

module_param(string, charp, 0400);

MODULE_PARM_DESC(string, "string demo");

// 加载模块命令

// insmod module_example.ko array=1,2,3

int array[10] = {1, 2, 3};

int num;

module_param_array(array, int, &num, 0400);

// 导出符号到内核符号表

EXPORT_SYMBOL_GPL(myint);

EXPORT_SYMBOL(myshort);

// 作者

MODULE_AUTHOR("demo");

// 描述

MODULE_DESCRIPTION("A module demo");

// 重命名

MODULE_ALIAS("demo hello");

// 全大写的宏说明此宏跟编译运行无关,仅仅说明信息

******************************************************************

【学习总结】

/*

基础知识:

1:一个设备驱动可以只包含 <linux/sched.h> 并且引用当前进程.例如下面的语句打印了当前进程的进程 ID 和命令名称通过存取结构task_struct 中的某些字段.

printk(KERN_INFO "The process is \"%s\" (pid %i)\n", current->comm,current->pid);

存储于 current->comm 的命令名称是由当前进程执行的程序文件的基本名称截短到 15 个字符如果需要 ).

2lsmod 通过读取 /proc/modules 虚拟文件工作当前加载的模块的信息也可在位于 /sys/module  sysfs 虚拟文件系统找到.

/**************************Makefile******************************/

KERN_DIR = /work/system/linux-2.6.30.4

all:

    make -C $(KERN_DIR) M=`pwd` modules

            # -C 指定进入指定的目录即KERN_DIR,是内核源代码目录,调用该顶层目录下的Makefile

            # M=选项让该makefile在构造modules目标之前返回到模块源代码目录

clean:                              #然后modules目标指向obj-m变量中设定的模块

    make -C $(KERN_DIR) M=`pwd` modules clean

    rm -rf modules.order

obj-+= xxx.o

xxx-objs :=xxx1.o xxx2...... #模块是由多个C文件构成

/***************************end*******************************/

/************************相关头文件*****************************/

#include <asm/irq.h>                    //IRQ中断相关头文件

#include <linux/interrupt.h>

#include <linux/sched.h>

#include <mach/regs-gpio.h>            //GPIO操作相关头文件

#include <asm/io.h>

#include <mach/hardware.h>             //硬件平台相关头文件

#include <linux/kernel.h>              //内核相关的头文件

#include <linux/module.h> 

//所有模块都需要的头文件,此头文件包含了可装载模块需要的大量的符号和函数定义

#include <linux/init.h> //设备驱动初始化相关的头文件,包含该函数的目的是指定初始化和清除函数

#include <linux/mm.h> //内存管理相关的头文件

#include <linux/fs.h>

    //文件系统相关的头文件,它是编写设备驱动程序必需的头文件,其中声明了许多重要的函数和数据结构

#include <linux/types.h> //设备类型定义

#include <linux/delay.h> //延时处理相关的头文件

#include <linux/moduleparam.h> 

//大部分模块都包含的头文件,这样我们就可以在装载模块时向模块传递参数

#include <linux/slab.h>

#include <linux/errno.h> //错误处理

#include <linux/ioctl.h> //IO操作

#include <linux/cdev.h> //字符设备相关的头文件

#include <linux/string.h>

#include <linux/list.h>

#include <linux/pci.h>

#include <asm/uaccess.h> //包含它,可以用它定义的用户空间和内核空间的数据传递

#include <asm/atomic.h>

#include <asm/unistd.h>

#include <linux/version.h> //版本相关

#include <linux/semaphore.h>

#include <asm/system.h>

#include <linux/device.h>

/***************************end*********************************/

/*******************模块相关的说明与许可证**************************/

MODULE_AUTHOR("xxxx"); //描述模块作者

MODULE_LICENSE("Dual BSD/GPL");//指定代码使用双重许可证

MODULE_VERSION("v1.0"); //模块版本

MODULE_DESCRIPTION("xxxx"); //说明模块用途

MODULE_ALIAS("xxx"); //模块别名

MODULE_DEVICE_TABLE(); //用来告诉内核空间模块所支持的设备

/***************************end*********************************/

/**********************模块加载与卸载******************************/

static int __init xxx_init(void) //初始化函数

{

                                  //注意错误的处理,goto就比较有效

   return 0;

}

static void __exit xxx_exit(void) //清除函数

{

}

 

module_init(xxx_init);//设备加载函数

module_exit(xxx_exit);//设备卸载函数

 

/***************************************************************/

当加载程序完成之后,__init标识符修饰的函数就会无效,所占用的内存就会被释放

__exit标识符也是一样(动态加载)

/***************************************************************/

/*

在注册设备时注册可能失败即便最简单的动作常常需要内存分配分配的内存可能不可用.

因此模块代码必须一直检查返回值,并且确认要求的操作实际上已经成功.

当模块的初始化出现错误后,模块必须自行撤销已注册的设施,错误恢复处理有时goto语句比较有效

/****************************end**********************************/

注:以上的学习总结选自韦东山群答疑助手:沈朝平的《Linux驱动程序学习笔记》!非常感谢!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值