字符设备驱动编程①

简介: 字符设备驱动编程①

前言

前面有学习过模块化编程,及其字符设备的编写,现在我们深入学习一下字符设备驱动编程,了解字符设备编程所用函数之间的调用关系。


静态加载与动态加载概念

静态加载:将驱动代码直接编译进内核,内核在启动过程中就会自动加载内核;

动态加载:将驱动代码单独编译成.ko格式的文件,再用insmod命令在需要的时候加载内核,在不需要驱动的时候用rmmod命令卸载驱动。静态加载一般用于基础功能的驱动,反正都是迟早是要加载的,编译进内核效率更高;动态加载一般用于扩展功能的驱动,这个设备可能

在加载驱动的时候我们使用:

module_init();

在卸载驱动的时候我们使用:

module_exit();

在我们使用insmod加载驱动的时候就是间接调用module_init(),使用rmmod卸载驱动的时候间接调用module_exit()。为什么是间接调用呢?insmod和rmmod其实并不能识别module_init和module_exit,它们只能识别init_module和cleanup_module。

静态加载

在内核中如果是静态编译,函数调用关系如下:

根据上面的调用关系,可以知道module_init(x)和module_exit(x)的展开结果:

展开前:

module_init(x);

展开后:

static initcall_t __initcall_x6 __used \
    __attribute__((__section__(".initcall6.init"))) = x;

展开前:

module_exit(x);

展开后:

static exitcall_t __exitcall_x __used __attribute__ ((__section__(#.exitcall.exit))) = x

动态加载

在我们使用insmod加载驱动的时候就是间接调用module_init(),使用rmmod卸载驱动的时候间接调用module_exit()。为什么是间接调用呢?insmod和rmmod其实并不能识别module_init和module_exit,它们只能识别init_module和cleanup_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)));
/* 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)));

驱动导出

linux内核采用的是模块化的形式管理内核代码。内核中每个模块之间是相互独立的,也就是说A模块的全局变量和函数,B模块是无法访问的。若B模块想要使用A模块中的已有符号,那么必须将A模块中的符号做符号导出,导出到模块符号表中,然后B模块可以使用A模块导出的符号。我们常常使用下面宏来实现驱动导出。

EXPORT_SYMBOL(函数名);

EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。内核中的定义如下:

/* For every exported symbol, place a struct in the __ksymtab section */
#define __EXPORT_SYMBOL(sym, sec)       \
  extern typeof(sym) sym;         \
  __CRC_SYMBOL(sym, sec)          \
  static const char __kstrtab_##sym     \
  __attribute__((section("__ksymtab_strings"), aligned(1))) \
  = VMLINUX_SYMBOL_STR(sym);        \
  extern const struct kernel_symbol __ksymtab_##sym;  \
  __visible const struct kernel_symbol __ksymtab_##sym  \
  __used              \
  __attribute__((section("___ksymtab" sec "+" #sym), unused)) \
  = { (unsigned long)&sym, __kstrtab_##sym }
#define EXPORT_SYMBOL(sym)          \
  __EXPORT_SYMBOL(sym, "")

我们只需要会使用即可(使用EXPORT_SYMBOL()导出的符号,都将在ksymtab节中放置一个与之关联的结构体)。



目录
相关文章
|
Ubuntu Linux 编译器
字符驱动设备原理及其相关函数(一)
字符驱动设备原理及其相关函数(一)
125 0
|
5月前
|
Windows
【Windows内核驱动函数(1)】IoCreateSymbolicLink()-----创建符号链接函数
【Windows内核驱动函数(1)】IoCreateSymbolicLink()-----创建符号链接函数
|
Linux
字符设备驱动编程②(cdev结构)
字符设备驱动编程②(cdev结构)
115 0
|
Linux
字符驱动设备原理及其相关函数(二)
字符驱动设备原理及其相关函数(二)
65 0
进程间通信——有名管道原理及详解(附有案例代码)
进程间通信——有名管道原理及详解(附有案例代码)
|
存储 API Windows
驱动开发:内核中进程与句柄互转
在内核开发中,经常需要进行进程和句柄之间的互相转换。进程通常由一个唯一的进程标识符(PID)来标识,而句柄是指对内核对象的引用。在Windows内核中,`EProcess`结构表示一个进程,而HANDLE是一个句柄。为了实现进程与句柄之间的转换,我们需要使用一些内核函数。对于进程PID和句柄的互相转换,可以使用函数如`OpenProcess`和`GetProcessId`。OpenProcess函数接受一个PID作为参数,并返回一个句柄。GetProcessId函数接受一个句柄作为参数,并返回该进程的PID。
369 0
|
存储 API
驱动开发:内核文件读写系列函数
在应用层下的文件操作只需要调用微软应用层下的`API`函数及`C库`标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头即可在内核中使用,例如本章要讲解的文件与目录操作相关函数,多数ARK反内核工具都具有对文件的管理功能,实现对文件或目录的基本操作功能也是非常有必要的。
273 0
|
芯片
查询方式的按键驱动程序
查询方式的按键驱动程序
135 0
|
Linux C语言 SoC
设备树基本原理与操作方法
设备树基本原理与操作方法
189 0
设备树基本原理与操作方法
|
缓存 Linux C语言
库函数与系统调用之间的区别--扩展知识点1
库函数与系统调用之间的区别--扩展知识点1
208 0