字符设备驱动编程①

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

前言

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


静态加载与动态加载概念

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

动态加载:将驱动代码单独编译成.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节中放置一个与之关联的结构体)。



目录
相关文章
|
10月前
|
Ubuntu Linux 编译器
字符驱动设备原理及其相关函数(一)
字符驱动设备原理及其相关函数(一)
79 0
|
5月前
|
存储 API 开发者
6.7 Windows驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章`《内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。
35 1
6.7 Windows驱动开发:内核枚举LoadImage映像回调
|
5月前
|
监控 安全 API
6.9 Windows驱动开发:内核枚举进线程ObCall回调
在笔者上一篇文章`《内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。
54 1
6.9 Windows驱动开发:内核枚举进线程ObCall回调
|
10月前
|
Linux
字符设备驱动编程②(cdev结构)
字符设备驱动编程②(cdev结构)
74 0
|
10月前
|
Linux
字符驱动设备原理及其相关函数(二)
字符驱动设备原理及其相关函数(二)
38 0
|
10月前
|
存储 缓存 Unix
Linux设备驱动程序(三)——字符驱动
本章的目的是编写一个完整的字符设备驱动,我们开发一个字符驱动是因为这一类适合大部分简单硬件设备,字符驱动也比块驱动易于理解。
190 0
|
Linux C语言 SoC
设备树基本原理与操作方法
设备树基本原理与操作方法
138 0
设备树基本原理与操作方法
|
存储
驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章`《驱动开发:内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。
272 1
驱动开发:内核枚举LoadImage映像回调
驱动开发:内核中枚举进线程与模块
内核枚举进程使用`PspCidTable` 这个未公开的函数,它能最大的好处是能得到进程的EPROCESS地址,由于是未公开的函数,所以我们需要变相的调用这个函数,通过`PsLookupProcessByProcessId`函数查到进程的EPROCESS,如果`PsLookupProcessByProcessId`返回失败,则证明此进程不存在,如果返回成功则把EPROCESS、PID、PPID、进程名等通过DbgPrint打印到屏幕上。
428 0
驱动开发:内核中枚举进线程与模块
|
存储 算法 API
驱动开发:内核枚举PspCidTable句柄表
在上一篇文章`《驱动开发:内核枚举DpcTimer定时器》`中我们通过枚举特征码的方式找到了`DPC`定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找`Windows 10`系统下面的`PspCidTable`内核句柄表地址。
452 0
驱动开发:内核枚举PspCidTable句柄表