加入收藏 | 设为首页 | 会员中心 | 我要投稿 | RSSRSS-巴斯仪表网
您当前的位置:首页 > 电子发烧 > 单片机学习

uclinux下静态/动态加载驱动程序的方法

时间:2013-09-08  来源:123485.com  作者:9stone

说明:这是我最近给单位写的一篇文档,没有什么复杂的东东,对刚接触linuxdriver的朋友或许有点帮助。文档本来是针对我们自己的产品的,有些地方(路径、mknod、动态分配主设备号等)本来应该改改,因为懒惰也没去改。

  在LINUX下加载驱动程序可以采用动态和静态两种方式。静态加载就是把驱动程序直接编译到内核里,系统启动后可以直接调用。静态加载的缺点是调试起来比较麻烦,每次修改一个地方都要重新编译下载内核,效率较低。动态加载利用了LINUX的module特性,可以在系统启动后用insmod命令把驱动程序(.o文件)添加上去,在不需要的时候用rmmod命令来卸载。在台式机上一般采用动态加载的方式。在嵌入式产品里可以先用动态加载的方式来调试,调试完毕后再编译到内核里。下面以我们的nHD板卡为例讲述一下加载驱动程序的方法。

  假设我们需要添加一个名为mydrv的字符型设备驱动,主设备号为254,次设备号为0(只有一个从设备)。

静态加载的步骤如下:

1、编写自己的驱动程序源文件mydrv.c,并放在firmware/uClinux-Samsung-2500/linux-2.4.x/drivers/char下面。一个典型的字符型驱动的最后一般包括如下内容:
static int mydrv_init(void)
{
  int ret;
  ret = register_chrdev(mydrv_major, " mydrv ", &my_fops);
  if(ret == 0) printk("register_chrdev succeed!/n");
  else       printk("register_chrdev fail!/n");
    return 0;
}

static __exit void mydrv _cleanup(void)
{
  unregister_chrdev(mydrv _major, " mydrv ");
  printk("register_chrdev succeed!/n");
  return ;
}
module_init(mydrv _init);
module_exit (mydrv _cleanup);
函数mydrv_init的任务是注册设备,mydrv_cleanup的任务是取消注册。 Module_init和module_exit的作用后面会讲到。

2.在firmware/uClinux-Samsung-2500/vendors/Samsung/2500/Makefile中添加如下语句(以刚才的设备为例,实际添加时当然要根据你自己的设备名称和设备号来添加):
  mknod $(ROMFSDIR) /dev/mydrv c 254 0
  这句话的目的是在内核中创建一个与你的驱动程序对应的设备节点。

3.在firmware/uClinux-Samsung-2500/linux-2.4.x/drivers/char/Makefile
中添加如下语句:
  obj-$(CONFIG_CHAR_MYDRV) +=mydrv.o
  这句话的目的是根据编译选项$(CONFIG_CHAR_MYDRV)来决定是否要添加该设备驱动。

4.在firmware/uClinux-Samsung-2500/linux-2.4.x/drivers/char/config.in
中添加:
if [“$CONFIG_ARCH_SAMSUNG”=”y”]; then
tristate ' ,MYDRV driver module ' CONFIG_CHAR_MYDRV
这句话的目的是在运行make menuconfig时产生与你的设备对应的编译选项。

5.运行make menuconfgi,应该能看到你自己的设备的选项,选中就可以了。

6.编译内核,下载,运行自己的测试程序。

  如果你觉得上述步骤比较麻烦,可以把4、5两条都省去,把第3条中的
  obj-$(CONFIG_CHAR_MYDRV) +=mydrv.o
改为
  obj-y +=mydrv.o
  这样在menuconfig就没有与你的设备对应的选项了,编译内核时直接会把你的驱动编译进去。

  还有一个问题需要说明一下。在…/drivers/char下有一个mem.c文件,其中最后有一个int __init chr_dev_init(void)函数。大家可以看到,所有字符设备的初始化函数(IDE_INT_init之类)都要添加在这里,而我们刚才的驱动程序的初始化函数并没有添加到这里。这个问题涉及到系统启动时的do_initcall函数,详细讲述起来比较烦琐,大家有兴趣可以看一下《情景分析》下册P726~P729。这里简单介绍一下。如果对一个函数(通常都是一些初始化函数)作如下处理(仍以我们的mydrv_init函数为例):
  __initcall(mydrv_init)
  那么在编译内核时会生成一个指向mydrv_init的函数指针__initcall_mydrv_int,系统启动时,在运行do_initcall函数时,会依次执行这些初始化函数,并且会在初始化结束后把这些函数所占用的内存释放掉。

  回到mem.c文件,在最后有一行:
  __initcall(chr_dev_init)
  这句话的作用就显而易见了,在系统启动时自动执行chr_dev_init函数。所以我们完全可以不用在mem.c/chr_dev_init中添加我们自己的初始化函数,而是在我们自己的设备文件中(mydrv.c)添加如下一行:
  __initcall(mydrv_init).

  在我们前面说到的我们自己的设备文件mydrv.c中,最后有一句:
  module_init(mydrv _init);
  大家可以看一下module_init的定义,在…linux.-2.4.x/include/linux/init.h中。如果定义了宏MODULE时,module_init是作为模块初始化函数,如果没有定义MODULE,则
module_init(fn)就被定义为__initcall(fn)。静态编译时是不定义MODULE的,所以我们的驱动中的module_init就等于是:
  __initcall(mydrv_init).
  这样我们的初始化函数就会在启动时被执行了。

  至于究竟是在mem.c/chr_dev_init中添加你的设备初始化函数,还是在设备文件中通过module_init来完成,完全取决于你的喜好,没有任何差别。如果你的初始化函数只是注册一个设备(没有申请内存等操作),那即使你在两个地方都加上(等于初始化了两次)也没关系,不会出错(有兴趣可以看一下内核里注册设备的函数实现,
…linux-2.4.x/fs/devices.c/register_chrdev)。不过为了规范起见,还是不建议这样作。

  最后一个问题。在静态加载驱动的时候,我们那个mydrv_cleanup和module_exit函数永远不会被执行,所以去掉是完全可以的,不过为了程序看起来结构清晰,也为了与动态加载的程序兼容,还是建议保留着。

下面讲一下动态加载驱动的方法。
1、运行make menuconfgi,在内核配置中进入Loadable module support,选择Enable loadable module support和Kernel module loader(NEW)两个选项。在应用程序配置中进入busybox,选择insmod, rmmod, lsmod三个选项。
2、在…vendors/Samsung/2500/Makefile中添加相应的设备节点,方法与静态加载时完全一样。
3、编写自己的驱动程序文件,在文件开始处加一句:
#define MODULE
文件最后的
mydrv_init
mydrv_cleanup
module_init(mydrv _init)
module_exit (mydrv _cleanup)
这四项必须保留。
4、仿照如下的格式写自己的Makefile文件:
KERNELDIR= /home/hexf/hardware/nHD/Design/firmware/uClinux-Samsung-2500/linux-2.4.x
CFLAGS = -D__KERNEL__ -I$(KERNELDIR)/include       -Wall -Wstrict-prototypes -Wno-trigraphs -O2 -fno-strict-aliasing -fno-common -fno-common -pipe -fno-builtin -D__linux__ -DNO_MM -mapcs-32   -mshort-load-bytes -msoft-float
CC   =   arm-elf-gcc

all: mydrv.o
clean:
  rm -f *.o

5、编译自己的驱动程序文件。注意在动态加载时只编译不连接,所以得到的是.o文件。

6、把编译后的驱动程序的.o 文件,连同自己的测试程序(假设叫mytest,注意这个是可执行文件)一起放在编译服务器的/exports/自己的目录下。
测试程序就是一个普通的应用程序,其编写和编译的步骤这里就不讲了。

7、启动nHD板卡,用nfs的方法把编译服务器上/exports/自己的目录mount上来(假设mount 到 /mnt下)。Nfs的使用大家都很熟悉了,这里就不再说。

8、
cd /mnt
/bin/insmod mydrv.o
现在你的设备就已经被动态加载到系统里了。可以用lsmod命令查看当前已挂接的模块。
9、运行你的测试程序
10、调试完毕后用 rmmod mydrv把你的设备卸载掉。

  补充几点:
1、关于建立设备节点的问题,因为大家所使用的系统不太一样,所以不需要按照我说的方法。总之只要在你自己的系统的dev目录下建立了自己的驱动程序的设备节点就可以了。
2、没有考虑动态分配主设备号的问题。所以注册设备那个地方稍微有点不严密。
3、模块加载时要把自己的.c文件编译成.o文件,CFLAGS后面那一串编译选项有时可能有点烦人,如果你没搞定,最简单的办法就是重新编译一遍内核并重定向到一个文件中(别忘了先make clean一下):make > out。

  然后在out文件里随便找一个字符驱动程序的编译过程,把它的编译选项找出来,拷贝到你自己的Makefile里就可以了。我就是这么作的。

下面是一个最简单的字符设备驱动的例子。实际的驱动千差万别,但其实也就是“填充”自己的open,close,read,write,ioctl几个函数而已。

#ifndef __KERNEL__
#define __KERNEL__
#endif
#define MODULE
#define drvtest_major 254

#include <linux/config.h>
#include <linux/module.h>

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>     // printk()
#include <linux/slab.h>     // kmalloc()
#include <linux/errno.h>   // error codes
#include <linux/types.h>   // size_t
#include <linux/interrupt.h>   // mark_bh
#include <linux/i2c.h>     
#include <linux/skbuff.h>
#include <asm/segment.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <linux/wait.h>
#include <asm/arch/hardware.h>


static int mytest_open(struct inode *inode,struct file *filp)
{
  

  MOD_INC_USE_COUNT;
 
  printk("mytest open!/n");

  return 0;

}

static ssize_t mytest_read(struct file *flip,char * buff,size_t count,
        loff_t * f_pos)
{
      char buf[10] ={0x1,0x2,0x3,0x4,0x5};
    
      memcpy(buff,buf,5);
    
      return 5;
}

static int mytest_close(struct inode *inode,struct file *filp)

{   MOD_DEC_USE_COUNT;

  printk("mytest close!/n");
 
  return 0;
  
  
}

static struct file_operations my_fops = {
  read:   mytest_read,
// write:   mytest_write,
open:   mytest_open,
release: mytest_close,
// ioctl:   mytest_ioctl,

}; 

static int mytest_init(void)

{
  int ret;
  ret = register_chrdev(drvtest_major, "drvtest", &my_fops);

  if(ret == 0) printk("register_chrdev succeed!/n");
  else       printk("register_chrdev fail!/n");
    return 0;

}

static __exit void mytest_cleanup(void)
{

  unregister_chrdev(drvtest_major, "drvtest");
  printk("register_chrdev succeed!/n");
  printk("bye!/n");

    return ;
}
module_init(mytest_init);
module_exit(mytest_cleanup);


分享到:
来顶一下
返回首页
返回首页
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
栏目导航->单片机学习
  • 电子应用基础
  • 电源技术
  • 无线传输技术
  • 信号处理
  • PCB设计
  • EDA技术
  • 单片机学习
  • 电子工具设备
  • 技术文章
  • 精彩拆解欣赏
  • 推荐资讯
    使用普通运放的仪表放大器
    使用普通运放的仪表放
    3V与5V混合系统中逻辑器接口问题
    3V与5V混合系统中逻辑
    数字PID控制及其改进算法的应用
    数字PID控制及其改进
    恶劣环境下的高性价比AD信号处理数据采集系统
    恶劣环境下的高性价比
    栏目更新
    栏目热门