file_operations结构体详细分析

http://www.linuxidc.com/Linux/2011-09/43530.htm

struct module *owner

第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针.
这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为
THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。
loff_t (*llseek) (struct file * filp , loff_t  p,  int  orig);
(指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位
的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t 参数是一个”long offset”, 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在”file 结构” 一节中描述).

ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);
(指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),
参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值)
这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL(“Invalid argument”) 失败.
一个非负返回值代表了成功读取的字节数( 返回值是一个 “signed size” 类型, 常常是目标平台本地的整数类型).

ssize_t (*aio_read)(struct kiocb *  , char __user *  buffer, size_t  size ,  loff_t   p);
可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的,
异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。
异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体);
初始化一个异步读 — 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
(有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)

ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);
(参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,
ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界)
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
(注:这个操作和上面的对文件进行读的操作均为阻塞操作)

ssize_t (*aio_write)(struct kiocb *, const char __user *  buffer, size_t  count, loff_t * ppos);
初始化设备上的一个异步写.参数类型同aio_read()函数;

int (*readdir) (struct file *  filp, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
(这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针)
这个函数返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。
每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。
(poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞.
poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能.
如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
(这里通常将设备看作一个文件进行相关的操作,而轮询操作的取值直接关系到设备的响应情况,可以是阻塞操作结果,同时也可以是非阻塞操作结果)

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
(inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.
cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.
如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的.
因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.)
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.
如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, “设备无这样的 ioctl”), 系统调用返回一个错误.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
(如果想对这个函数有个彻底的了解,那么请看有关“进程地址空间”介绍的书籍)

int (*open) (struct inode * inode , struct file *  filp ) ;
(inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;
但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息)
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
与open()函数对应的是release()函数。

int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作.
这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用;
SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.

int (*release) (struct inode *, struct file *);
release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:
void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

int(*synch)(struct file *,struct dentry *,int datasync);
刷新待处理的数据,允许进程把所有的脏缓冲区刷新到磁盘。
int (*aio_fsync)(struct kiocb *, int);
这是 fsync 方法的异步版本.所谓的fsync方法是一个系统调用函数。系统调用fsync
把文件所指定的文件的所有脏缓冲区写到磁盘中(如果需要,还包括存有索引节点的缓冲区)。
相应的服务例程获得文件对象的地址,并随后调用fsync方法。通常这个方法以调用函数__writeback_single_inode()结束,
这个函数把与被选中的索引节点相关的脏页和索引节点本身都写回磁盘。

int (*fasync) (int, struct file *, int);
这个函数是系统支持异步通知的设备驱动,下面是这个函数的模板:
static int ***_fasync(int fd,struct file *filp,int mode)
{
struct ***_dev * dev=filp->private_data;
return fasync_helper(fd,filp,mode,&dev->async_queue);//第四个参数为 fasync_struct结构体指针的指针。
//这个函数是用来处理FASYNC标志的函数。(FASYNC:表示兼容BSD的fcntl同步操作)当这个标志改变时,驱动程序中的fasync()函数将得到执行。
}
此操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述.
这个成员可以是NULL 如果驱动不支持异步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作;
这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个.
例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.
这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]

int (*check_flags)(int)
这个方法允许模块检查传递给 fnctl(F_SETFL…) 调用的标志.

int (*dir_notify)(struct file *, unsigned long);
这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.

一般情况下,进行设备驱动程序的设计只是比较注重下面的几个方法:
struct file_operations ***_ops={
.owner =  THIS_MODULE,
.llseek =  ***_llseek,
.read =  ***_read,
.write =  ***_write,
.ioctl =  ***_ioctl,
.open =  ***_open,
.release = ***_release,
};

 

process infomation get within kernel

linux get process name from pid within kernel

  unsigned int pid = task_pid_nr(current);

 unsigned char * comm= current->comm;

printk(KERN_INFO “Simon:pid=%u,comm=%s\n,pid,comm);

How to get current process’s UID and EUID in Linux Kernel 4.2?

http://stackoverflow.com/questions/39229639/how-to-get-current-processs-uid-and-euid-in-linux-kernel-4-2

unsigned int uid = current_uid();

unsigned int euid = current_euid();

 unsigned int suid = current_suid();

unsigned int gid = current_gid();

Get time within kernel :

unsigned int get_kernel_time_sec()

{

        struct timeval ctv;

        do_gettimeofday(&ctv);

        return (unsigned int)ctv.tv_usec;

}// And then you can create a rand function based on time. 

unsigned int rand(unsigned int limit)

{

        unsigned int c_tv = get_kernel_time_sec();

        if(limit==0)

        {

                return c_tv;

        }

        else

        {

                return c_tv%limit;

        }

}

#include <linux/cred.h> // current_uid & current_euid

#include <linux/time.h> // current_kernel_time

#include <linux/types.h> //timespec

#include <linux/timer.h>

#include <linux/sched.h> // task_pid_nr    current->comm;

Linux驱动开发错误:module license ‘unspecified’ taints kernel.

原文链接: http://blog.csdn.net/zengxianyang/article/details/50710695

前言

今天我要来说说在Linux驱动开发中Makefile编写规则的问题。其实这是驱动开发中的基础性的问题,怪自己基础不够扎实啊,犯了这样的低级错误。写这篇文章让自己巩固一下基础吧,唯有厚积,才能薄发!扎实的编程基础,是一个底层软件工程师应该具备的素质,这样才能年薪百万,迎娶白富美,走上人生巅峰!麻痹,老子又在意淫了,女朋友在哪里都还不知道,说多了都是泪,言归正传!别装逼了!讲正事!

1 Linux驱动Makefile编写规则介绍

1.1 Linux驱动Makefile实例讲解

这里,我们简单的举一个hello驱动的Makefile,来讲解驱动开发中,makefile的编写规则
[objc] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. obj-m := hello.o
  2. hello-obj := hello_main.o file1.o file2.o
  3. KERNELDR := /usr/src/linux-2.6.26
  4. PWD := $(shell pwd)
  5. modules:
  6.     $(MAKE) -C $(KERNELDR) M=$(PWD) modules
  7. moduels_install:
  8.     $(MAKE) -C $(KERNELDR) M=$(PWD) modules_install
  9. clean:
  10.     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
(1)obj-m := hello.o
表面驱动模块从目标文件hello.o建立,从目标文件建立后,模块的名字为hello.ko。
  (2) module-objs:如果模块由N个文件组成,那么其他文件就应该描述如下:module-objs:= file1.o file2.o,由于我们的模块叫做hello,在这个例子中应该写为hello-objs 。
(3)KERDIR  := /usr/src/linux-2.6.26
用来定位用于编译驱动的内核源码的目录位置。
(4)-C表示kernel source目录,在/lib/modules/<uname -r’>/build,在那里可以找到kernel的最高lenvel的makefile,M=表示在建立模块target的时候,makefile回归到我  们模块程序的目录。

2 驱动加载错误:module license ‘unspecified’ taints kernel

当时我的液晶驱动是由多个C文件组成的一个模块,但是由于多个C文件当中的一个xxx.o文件和模块目标文件xxx.o重名了,所以导致这个错误的,当时加载驱动的时候如下错误:
[objc] view plain copy

 在CODE上查看代码片派生到我的代码片

  1. root@Phoenix /root#insmod mxc_elcdif_fb.ko
  2. mxc_elcdif_fb: module license ‘unspecified’ taints kernel.
  3. Disabling lock debugging due to kernel taint
  4. mxc_elcdif_fb: Unknown symbol self_pid (err 0)
  5. mxc_elcdif_fb: Unknown symbol gpio_free (err 0)
  6. mxc_elcdif_fb: Unknown symbol drv_version (err 0)
  7. mxc_elcdif_fb: Unknown symbol reset_gpio (err 0)
  8. mxc_elcdif_fb: Unknown symbol disp_init_gpio (err 0)

2.1 解决办法

如果我们有多个c文件,可以在test-objs参数中加入他们的obj文件。接下来就是make了,编译后,生成hello.o文件和hello.ko,还有hello.mod.c及其obj文件,Module.markers Module.sysvers Modules.order 文件。

我们对多个*.c文件情况做一个说明:我们希望创建一个模块的名字叫做hello,我们有三个*.c文件,分别为hello.c, file1.c和file2.c。这样是有问题的,因为在Makefile中obj-m := hello.o,这是指定模块的名称, hello-objs := file1.o file2.o hello.o,这里是说hello模块包括的的obj文件,如果我们在里面不填写hello.o,那么实际并没有编译hello.c,而是在CC[M] file1.o和file2.o,通过LD[M]得到模块hello.o,如果我们在这里填写了hello.o,那么在obj-m和hello-objs中都含有hello.o,对make来讲会产生循环和混淆,因此也不能这样书写。如果我们由多个C文件来构造一个模块,那么C文件的名字不能和模块名字一样,在这个例子中我们可以将hello.c改名为hello_main.c,在Makefile中obj-m := hello.o,hello-objs = file1.o file2.o hello_main.o。

内核符号表详解——如何在module中利用内核符号

 

原文链接: http://blog.csdn.net/trochiluses/article/details/9446955

 

前言:在内核开发中,有时候我们必须检查某些内核状态,或者我们想冲用某些内核功能,我们需要得到(read,write,exe)内核符号。本文主要为你介绍内核如何保存这些符号表,我们怎样应用这些内核符号表。本文仅仅是阅读内核源码的一个guide,通过阅读内核源码,我们将有更深入的理解。

1.什么是内核符号

先看基础知识:在编程语言中,符号指的是一个变量或者函数。更一般地说,符号是一个代表着内存中指定空间的名称,这个空间存储着数据(可读或者可写的变量)或者指令(可以执行的函数)。为了让不同的内核功能单元能够更好地协同工作,linux内核中有着数以千计的内核符号。一个全局变量在一个函数之外定义。一个全局函数声明的时候不能带有inline和static。所有的全局符号在/proc/kallsyms中列出,如下:

$ tail /proc/kallsyms
ffffffff81da9000 b .brk.dmi_alloc
ffffffff81db9000 B __brk_limit
ffffffffff600000 T vgettimeofday
ffffffffff600140 t vread_tsc
ffffffffff600170 t vread_hpet
ffffffffff600180 D __vsyscall_gtod_data
ffffffffff600400 T vtime
ffffffffff600800 T vgetcpu
ffffffffff600880 D __vgetcpu_mode
ffffffffff6008c0 D __jiffies

这是一个nm输出的形式,第一列是符号地址,第二列是符号表,详细信息参见 “man nm”。一般来说,这是“nm vmlinux”的输出结果。然而,这个符号表中的一些条目来源于可加载内核模块,那么这些条目是如何被列出来的呢?我们来看看这个符号表是怎么产生的。

2./proc/kallsyms是如何产生的

如同我们在前两篇文章中看到的那样:procfs文件是在内核中读取的,所以不要在磁盘上寻找,而应该去内核源码中寻找答案。首先,我们找到产生/kernel/kallsyms.c中文件的代码:

  1. static const struct file_operations kallsyms_operations = {
  2.         .open = kallsyms_open,
  3.         .read = seq_read,
  4.         .llseek = seq_lseek,
  5.         .release = seq_release_private,
  6. };
  7. static int __init kallsyms_init(void)
  8. {
  9.         proc_create(“kallsyms”, 0444, NULL, &kallsyms_operations);
  10.         return 0;
  11. }
  12. device_initcall(kallsyms_init);

在创建这些文件的时候,内核将 open()和 kallsyms_open()关联起来, read()->seq_read(), llseek()->seq_lseek() and release()->seq_release_private(). 这里,我们可以看到:这个文件是一个序列文件。
对这个序列文件的详细讨论已经超出了本文的范畴。在内核文档中,有关于这个的很好理解的描述,如果你不了解什么是sequence文件,可以参考 Documentation/filesystems/seq_file.txt。简单地说,因为proc_read_t篇幅有限,内核引进了sequence 文件来给用户提供大量的信息。
回到源码,在kallsyms_open()中,它仅仅创建和重置了对seq_read 操作的迭代器,当然也设置了seq_operations.

  1. static const struct seq_operations kallsyms_op = {
  2.         .start = s_start,
  3.         .next = s_next,
  4.         .stop = s_stop,
  5.         .show = s_show
  6. };

因此,为了我们的目标,我们关心s_start() and s_next().它们都调用了update_iter(),而update_iter()的核心是get_ksymbol_mod(),紧跟着的是get_ksmbol_mod(),最后是module_get_kallsym().这些函数都在kernel/module.c中。

  1. int module_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
  2.                         char *name, char *module_name, int *exported)
  3. {
  4.         struct module *mod;
  5.         preempt_disable();
  6.         list_for_each_entry_rcu(mod, &modules, list) {
  7.                 if (symnum < mod->num_symtab) {
  8.                         *value = mod->symtab[symnum].st_value;
  9.                         *type = mod->symtab[symnum].st_info;
  10.                         strlcpy(name, mod->strtab + mod->symtab[symnum].st_name,
  11.                                 KSYM_NAME_LEN);
  12.                         strlcpy(module_name, mod->name, MODULE_NAME_LEN);
  13.                         *exported = is_exported(name, *value, mod);
  14.                         preempt_enable();
  15.                         return 0;
  16.                 }
  17.                 symnum -= mod->num_symtab;
  18.         }
  19.         preempt_enable();
  20.         return -ERANGE;
  21. }

在 module_get_kallsym()中,它迭代了所有的模块和符号。五个性质是指定的值。value是这个符号的地址,type是符号类型,name是符号名字,module_name是模块名字(如果这个模块没有被编进内核;反之它就是空);exported表明这个符号是否是exported。那么为什么这里有很多local的符号呢?我们来看看s_low()

  1. if (iter->module_name[0]) {
  2.                 char type;
  3.                 /*
  4.                  * Label it “global” if it is exported,
  5.                  * “local” if not exported.
  6.                  */
  7.                 type = iter->exported ? toupper(iter->type) :
  8.                                         tolower(iter->type);
  9.                 seq_printf(m, “%0*lx %c %s\t[%s]\n”,
  10.                            (int)(2 * sizeof(void *)),
  11.                            iter->value, type, iter->name, iter->module_name);
  12.         } else
  13.                 seq_printf(m, “%0*lx %c %s\n”,
  14.                            (int)(2 * sizeof(void *)),
  15.                            iter->value, iter->type, iter->name);

好了,现在明白了吧,所有的这些符号都是C语言中的全局变量,但是者有exported 符号被打上“global”的标签。在迭代结束之后,我们来看看/proc/kallsyms中的 内容。

3.如何得到符号表

这里,得到指的是可读,可写,可执行,我们来看一看以下模块:

  1. #include <linux/module.h>
  2. #include <linux/init.h>
  3. #include <linux/kernel.h>
  4. #include <linux/jiffies.h>
  5. MODULE_AUTHOR(“Stephen Zhang”);
  6. MODULE_LICENSE(“GPL”);
  7. MODULE_DESCRIPTION(“Use exported symbols”);
  8. static int __init lkm_init(void)
  9. {
  10.     printk(KERN_INFO “[%s] module loaded.\n”, __this_module.name);
  11.     printk(“[%s] current jiffies: %lu.\n”, __this_module.name, jiffies);
  12.     return 0;
  13. }
  14. static void __exit lkm_exit(void)
  15. {
  16.     printk(KERN_INFO “[%s] module unloaded.\n”, __this_module.name);
  17. }
  18. module_init(lkm_init);
  19. module_exit(lkm_exit);

在这个模块中,我们使用了printk()和jiffies,这些都是来自内核空间的模块。为什么这些模块能够为我们的代码所用呢?因为它们是exported。你可以认为内核符号在内核源代码中的三个不同层次上。

“static”, and therefore visible only within their own source file
“external”, and therefore potentially visible to any other code built into the kernel itself, and
“exported”, and therefore visible and available to any loadable module.

内核使用两个宏来导出符号:

EXPORT_SYMBOL exports the symbol to any loadable module
EXPORT_SYMBOL_GPL exports the symbol only to GPL-licensed modules.

回到以上的代码,我们可以发现以上的两个符号在以下的内核源码中被导出。
kernel/printk.c:EXPORT_SYMBOL(printk);
kernel/time.c:EXPORT_SYMBOL(jiffies);
除了检查内核源码来判断一个符号是否被导出之外,还有其他的方式来鉴别吗?当然可以。所有被导出的条目有另外一个带有__ksymab_前缀的符号,例如:
ffffffff81a4ef00 r __ksymtab_printk
ffffffff81a4eff0 r __ksymtab_jiffies
Let’s just have another look at the definition of 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))) \
= MODULE_SYMBOL_PREFIX #sym;                            \
static const struct kernel_symbol __ksymtab_##sym       \
__used                                                  \
__attribute__((section(“__ksymtab” sec), unused))       \
= { (unsigned long)&sym, __kstrtab_##sym }

#define EXPORT_SYMBOL(sym)                                      \
__EXPORT_SYMBOL(sym, “”)
The highlighted line places a struct kernel_symbol __ksymtab_##sym int the symbol table.

还有两外的一个事情值得注意,__this_module既不是一个导出符号,也没有在内核源码中定义。在内核源码中,我们仅仅能找到的关于_this_module的信息就是如下两行代码:

extern struct module __this_module;

#define THIS_MODULE (&__this_module)

How?! 它没有被定义在内核源代码中,那么在insmod的时候被链接到哪里呢?不要慌张,你注意到编译内核的时候产生的临时文件hello.mod.c了吗?这里有__this_module  的定义:

struct module __this_module
__attribute__((section(“.gnu.linkonce.this_module”))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
到目前为止,我们可以看到,在我们的内核模块中,我们仅仅能够使用导出的内核符号;我们需要做的唯一事情就是include相应的内核头文件,或者仅仅使用正确的声明。那么,如果我们想得到其他的内核符号,应该怎么处理呢?尽管引用没有导出的符号并不是一个好注意,这些符号是为了避免别人访问它们从而避免潜在的危险;有一天,仅仅为了满足某些人的好奇心,或者某个对他的行为后果非常清除,我们必须要得到non-exported符号,让我们深入探讨:

5.How to access non-exported symbol

对于内核中的每一个符号,我们都可以在/proc/kallsyms中找到相应的条目,而且可以得到它的地址。因为我们在内核中,我们能够看见任何东西。我们以resume_file为例,上代码:

  1. #include <linux/module.h>
  2. #include <linux/kallsyms.h>
  3. #include <linux/string.h>
  4. MODULE_LICENSE(“GPL”);
  5. MODULE_DESCRIPTION(“Access non-exported symbols”);
  6. MODULE_AUTHOR(“Stephen Zhang”);
  7. static int __init lkm_init(void)
  8. {
  9.     char *sym_name = “resume_file”;
  10.     unsigned long sym_addr = kallsyms_lookup_name(sym_name);
  11.     char filename[256];
  12.     strncpy(filename, (char *)sym_addr, 255);
  13.     printk(KERN_INFO “[%s] %s (0x%lx): %s\n”, __this_module.name, sym_name, sym_addr, filename);
  14.     return 0;
  15. }
  16. static void __exit lkm_exit(void)
  17. {
  18. }
  19. module_init(lkm_init);
  20. module_exit(lkm_exit);

这里,我们使用kallsyms_lookup_name()而不是解析/proc/kallsyms来找到一个符号的地址。接着,我们把这个地址当成char *,这也是resume_file的类型,然后利用strncpy来读。
看看运行结果:
sudo insmod lkm_hello.ko
dmesg | tail -n 1
[lkm_hello] resume_file (0xffffffff81c17140): /dev/sda6
grep resume_file /proc/kallsyms
ffffffff81c17140 d resume_file
我们做到了!我们可以看见kallsyms_lookup_name()返回的符号地址和 /proc/kallsyms完全相同。同样,你也可以write到一个符号地址,但是对只读地址空间是不行的,不然你会得到一个oops错误。然而,你可以关掉保护,从而向只读空间写入东西。遵循以下操作,基本思想就是改变页属性。

  1. int set_page_rw(long unsigned int _addr)
  2. {
  3.     struct page *pg;
  4.     pgprot_t prot;
  5.     pg = virt_to_page(_addr);
  6.     prot.pgprot = VM_READ | VM_WRITE;
  7.     return change_page_attr(pg, 1, prot);
  8. }
  9. int set_page_ro(long unsigned int _addr)
  10. {
  11.     struct page *pg;
  12.     pgprot_t prot;
  13.     pg = virt_to_page(_addr);
  14.     prot.pgprot = VM_READ;
  15.     return change_page_attr(pg, 1, prot);
  16. }

 

6.2.4与2.6内核关于内核符号表的区别

对于2.4内核和2.6内核的内核符号表是有区别的,2.4内核默认情况下模块中的非静态全局变量以及非静态函数在模块加载后会自动导出到内核符号表中,而2.6内核默认情况下是不会自动导出的,需要显式调用宏EXPORT_SYMBOL才能导出。导出的符号前面一般标注有r标记。可以通过nm -l xx.ko来查看某一个模块里的符号情况。或者通过查看内核符号表文件也行。对于2.4是:cat /proc/ksyms,对于2.6是:cat /proc/kallsyms.

6.Conclusion

 

本文中,我们首先挖掘了内核源码来找到内核符号表是如何产生的。接着我们学习了在我们的模块中如何利用导出的内核符号。最后我们学习了如何在模块中利用所有内核符号的技巧。