0%

Linux设备驱动程序基础

Linux设备驱动程序示例

源代码:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/vmalloc.h>

static int num = 1;                                                                                                                 
static char *mode = "show";

static int __init hello_module_init(void)
{
    printk("Hello,world\n");
}

static void __exit hello_module_exit(void)
{
    printk("Good Bye\n");
}

module_init(hello_module_init);
module_exit(hello_module_exit);
module_param(mode, charp, S_IRUGO);
module_param(num, int, S_IRUGO);

MODULE_DESCRIPTION("driver for the Hello World.");
MODULE_AUTHOR("Hello");
MODULE_LICENSE("GPL");

Makefile:

obj-m += hello.o    #设置模块名
hello-objs := bsp_hello.o hello_mod_linux.o

all:
    $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_PATH) SUBDIRS=`pwd`

clean:
    rm -rf *.ko *.o *.mod.c .tmp_versions Module.symvers modules.order .tmp_versions
    find ../ -name "*.cmd" -delete

install:
    cp $(HELLO_TARGET) $(OUTDIR) -f

Tips:obj-m += (module name).o

相关宏定义

Linux设备驱动编译时,如果MODULE未定义,表明设备驱动是build-in模式。
相应的module_init/module_exit宏定义展开不同。

linux对只需要初始化运行一次的函数都加上init属性,
__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,
module_exit的参数卸载时同
init类似,如果驱动被编译进内核,
exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,
**显然
init和__exit对动态加载的模块是无效的,只支持完全编译进内核**。

在kernel初始化后期,释放所有这些函数代码所占的内存空间。
连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。
当函数初始化完成后这个区域可以被清除掉以节约系统内存。
Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。

start_kernel->rest_init->kernel_init->init_post->free_initmem()来释放初始化代码和数据。

特殊宏定义

  • MODULE_LICENSE(license) 代码使用的许可证
  • MODULE_AUTHOR(author) 描述模块作者
  • MODULE_DESCRIPTION(description) 说明模块用途的简短描述
  • MODULE_VERSION(version_string) 代码修订号
  • MODULE_DEVICE_TABLE(table_info) 告诉用户空间模块所支持的设备
  • MODULE_ALIAS(alternate_name) 模块的别名

加载函数宏定义module_init()

module_init定义如下:

include/linux/init.h
#ifndef MODULE
/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)  __initcall(x);
#else /* MODULE */
/* Each module must use one module_init(). */
#define module_init(initfn)                     \
    static inline initcall_t __inittest(void)   \
    { return initfn;  }                         \                                                                                           
    int init_module(void) __attribute__((alias(#initfn)));
#endif

build-in分支

#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)     __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \                                                                                            
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn

因此使用module_init修饰的hello_module_init最终展开为:

static initcall_t  __initcall_hello_module_init6 __used __attribute__((__section__(".initcall6.init"))) = hello_module_init;

就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量
__initcall_hello_module_init6并将hello_module_init赋值与它。

其中attribute((section(“.initcall6.init”)))表明变量__initcall_hello_module_init6放入section .initcall6.init中。

在文件vmlinux.lds中有以下定义:

.initcall.init : AT(ADDR(.initcall.init) - 0) {                                                                                    
 __initcall_start = .;
 *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) 
                                                *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) 
                                                *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) 
                                                *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init)
                                                *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) 
                                                *(.initcall7.init) *(.initcall7s.init)
 __initcall_end = .;
}

启动顺序

init/main.c
start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()

static void __init do_initcalls(void)
{ 
    initcall_t *call;

    for (call = __early_initcall_end; call < __initcall_end; call++)
        do_one_initcall(*call);                                                                                                     

    /* Make sure there is no pending stuff from the initcall sequence */
    flush_scheduled_work();
} 

do_initcalls()将按顺序从由early_initcall_end开始,
initcall_end结束的section中以函数指针的形式取出这些编译到内核的驱动模块中初始化函数起始地址
来依次完成相应的初始化。

内核初始化函数do_basic_setup(): do_initcalls() 将从.initcall.init 中,
也就是这几个section中依次取出所有的函数指针,并调用这些函数指针所指向的函数,来完成内核的一些相关的初始化。

内核的加载的时候,会搜索”.initcall”中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。
其它模块优先级列出如下:值越小,越先加载。

Module分支

/* Each module must use one module_init(). */
#define module_init(initfn)                     \
    static inline initcall_t __inittest(void)   \
    { return initfn;  }                         \                                                                                           
    int init_module(void) __attribute__((alias(#initfn)));

typedef int (*initcall_t)(void)

其中

typedef int (*initcall_t)(void)
#define module_init(initfn)                     \
    static inline initcall_t __inittest(void)   \
    { return initfn;  }                         \                                                                                           

用于对传入的initfn进行类型检测,类型必须为 int (*initcall_t)(void) 的函数指针,然后通过alias将initfn变名为init_module:

/* type newname __attribute__((alias("oldname"))); */
int init_module(void) __attribute__((alias(#initfn)));

当调用insmod和rmmod时,只与init_module和cleanup_module有关,insmod和rmmod中调用这两个函数。

busybox/modutils/insmod.c:insmod_main()
rc = bb_init_module(filename, parse_cmdline_module_options(argv, /*quote_spaces:*/ 0));
.
busybox/modutils/modutils.c:bb_init_module()
init_module(image, image_size, options);
.
# define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)

insmod最终会调用到 syscall __NR_init_module

在Kernel中,各个arch下的文件unistd.h有如下定义:

#define __NR_init_module        128
#define __NR_delete_module      129
#define __NR_get_kernel_syms    130
#define __NR_quotactl           131
#define __NR_getpgid            132

然后,文件entry.S下有定义:

.data
ALIGN
sys_call_table:
    .
    .
    .long sys_mprotect      /* 125 */
    .long sys_sigprocmask
    .long sys_ni_syscall    /* old "create_module" */
    .long sys_init_module
    .long sys_delete_module
    .long sys_ni_syscall    /* 130 - old "get_kernel_syms" */
    .long sys_quotactl
    .
    .

当insmod调用到系统调用号128时,sys_call_table中取出128地址的函数指针进行执行,及sys_init_module。

现在来看系统调用sys_init_module

kernel/module.c
SYSCALL_DEFINE3(init_module, void __user *, umod, unsigned long, len, const char __user *, uargs)
.
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, name, ...)                   \                                                                           
    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

宏定义__SC_DECL##x作用是将参数之间的‘,’去掉,定义如下:

#define __SC_DECL1(t1, a1)  t1 a1
#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)
#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)                                                                      
#define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)
#define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)
#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)

宏定义中的‘##’为(token-pasting)符号连接操作符,全部展开之后为:

asmlinkage long sys_init_module(void __user * umod, unsigned long len, const char __user * uargs)

在文件include/linux/syscalls.h中可以看到所有的展开的系统调用,例如:

asmlinkage long sys_init_module(void __user *umod, unsigned long len,                                                               
                const char __user *uargs);
asmlinkage long sys_delete_module(const char __user *name_user,
                unsigned int flags);

asmlinkage long sys_rt_sigprocmask(int how, sigset_t __user *set,
                sigset_t __user *oset, size_t sigsetsize);
asmlinkage long sys_rt_sigpending(sigset_t __user *set, size_t sigsetsize);
asmlinkage long sys_rt_sigtimedwait(const sigset_t __user *uthese,
                siginfo_t __user *uinfo,
                const struct timespec __user *uts,
                size_t sigsetsize);

在系统调用sys_init_module中:

.
mod = load_module(umod, len, uargs);  //主要过程都在这儿
.
.
/* Start the module */
if (mod->init != NULL)
    ret = do_one_initcall(mod->init);

load_module做了绝大部分的工作,将驱动拷贝到内核,重定位等等,do_one_initcall调用module注册的init函数。

卸载函数宏定义module_exit()

#ifndef MODULE
/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 * 
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x)  __exitcall(x);
#else /* MODULE */
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)                 \
    static inline exitcall_t __exittest(void)       \
    { return exitfn;  }                  \
    void cleanup_module(void) __attribute__((alias(#exitfn)));
#endif

build-in

无效,没有意义

module

busybox:

# define delete_module(mod, flags) syscall(__NR_delete_module, mod, flags)

kernel:

.long sys_delete_module
#define __NR_delete_module      129

SYSCALL_DEFINE2(delete_module, const char __user *, name_user, unsigned int, flags)
{
    .
    if (mod->exit != NULL)
        mod->exit();
    .
}

asmlinkage long sys_delete_module(const char __user *name_user, unsigned int flags);

模块传参宏定义module_param()

include/linux/moduleparam.h
#define module_param(name, type, perm)              \                                                                               
    module_param_named(name, name, type, perm)

用于向模块传递参数,其允许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,
在驱动程序里,参数的用法如同全局变量。

  • name既是用户看到的参数名,又是模块内接受参数的变量;
  • type表示参数的数据类型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool;
  • perm指定了在sysfs中相应文件的访问权限。
    访问权限与linux文件爱你访问权限相同的方式管理,如0644,或使用stat.h中的宏如S_IRUGO表示。0表示完全关闭在sysfs中相对应的项。

这些宏不会声明变量,因此在使用宏之前,必须声明变量,典型地用法如下:

static unsigned int int_var = 0;
module_param(int_var, uint, S_IRUGO);

Tips

  • mod->exit()怎么调用到 alias 为 cleanup_module的函数
  • mod->init()怎么调用到 alias 为 init_module的函数

接下来分析module具体组成