Chinaunix首页 | 论坛 | 博客
  • 博客访问: 122480
  • 博文数量: 41
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 306
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-01 10:41
文章分类

全部博文(41)

文章存档

2011年(1)

2009年(40)

我的朋友

分类:

2009-04-10 16:42:46

多个源程序组成的模块的Makefile
1.在除了一个以外的所有源文件中,增加一行#define __NO_VERSION__。这是很重要的,因为module.h一般包括kernel_version的定义,这是一个全局变量,包含模块编译的内核 版本。如果你需要version.h,你需要把自己把它包含进去,因为如果有__NO_VERSION__的话module.h不会自动包含。
2.象通常一样编译源文件。
3.把所有目标文件联编成一个。在X86下,用ld –m elf_i386 –r –o .o <1st source file>

printk
printk是内核空间中printf的替代品,其用法 为:printk("format string", var)。n定义了日志等级,范围从0到7,其中0到4是紧急事件,5和6是普通信息,7为调试(例如SMP_DEBUG)。其它用户空间的函数也不能使 用;程序员在内核空间中可以使用的函数都记录在/proc/ksyms中,也可以使用ksyms -a命令查看。
注:在终端下insmod模块,printk信息送往终端
By the way, the reason why the Makefile recommends against doing insmod from X is because when the kernel has a message to print with printk, it sends it to the console. When you don use X, it just goes to the virtual terminal you e using (the one you chose with Alt-F) and you see it. When you do use X, on the other hand, there are two possibilities. Either you have a console open with xterm -C, in which case the output will be sent there, or you don , in which case the output will go to virtual terminal 7 -- the one `covered by X.

函数/变量和内核版本的关系
刚才我们说到 kernel 本身会 export 出一些 function 或 variable 来让 module 使用,但是,我们不是万能的,我们怎幺知道 kernel 有开放那里东西让我们使用呢 ? Linux 提供一个 command,叫 ksyms,你只要执行 ksyms -a 就可以知道 kernel 或目前载到 kernel 里的 module 提供了那些 function 或 variable。底下是我的系统的情形:

c0216ba0 drive_info_R744aa133
c01e4a44 boot_cpu_data_R660bd466
c01e4ac0 EISA_bus_R7413793a
c01e4ac4 MCA_bus_Rf48a2c4c
c010cc34 __verify_write_R203afbeb
. . . . .

在 kernel 里,有一个 symbol table 是用来记录 export 出去的 function 或 variable。除此之外,也会记录着那个 module export 那些 function。上面几行中,表示 kernel 提供了 drive_info 这个 function/variable。所以,我们可以在 kernel 里直接使用它,等载到 kernel 里时,会自动做好 link 的动作。由此,我们可以知道,module 本身其实是还没做 link 的一些 object code。一切都要等到 module 被加载 kernel 之后,link 才会完成。各位应该可以看到 drive_info 后面还接着一些奇怪的字符串。_R744aa133,这个字符串是根据目前 kernel 的版本再做些 encode 得出来的结果。为什幺额外需要这一个字符串呢?

Linux 不知道从那个版本以来,就多了一个 config 的选项,叫做 Set version number in symbols of module。这是为了避免对系统造成不稳定。我们知道 Linux 的 kernel 更新的很快。在 kernel 更新的过程,有时为了效率起见,会对某些旧有的 data structure 或 function 做些改变,而且一变可能有的 variable 被拿掉,有的 function 的 prototype 跟原来的都不太一样。如果这种情形发生的时候,那可能以前 2.0.33 版本的 module 拿到 2.2.1 版本的 kernel 使用,假设原来 module 使用了 2.0.33 kernel 提供的变量叫 A,但是到了 2.2.1 由于某些原因必须把 A 都设成 NULL。那当此 module 用在 2.2.1 kernel 上时,如果它没去检查 A 的值就直接使用的话,就会造成系统的错误。也许不会整个系统都死掉,但是这个 module 肯定是很难发挥它的功能。为了这个原因,Linux 就在 compile module 时,把 kernel 版本的号码 encode 到各个 exported function 和 variable 里。

所以,刚才也许我们不应该讲 kernel 提供了 drive_info,而应该说 kernel 提供了 driver_info_R744aa133 来让我们使用。这样也许各位会比较明白。也就是说,kernel 认为它提供的 driver_info_R744aa133 这个东西,而不是 driver_info。所以,我们可以发现有的人在加载 module 时,系统都一直告诉你某个 function 无法 resolved。这就是因为 kernel 里没有你要的 function,要不然就是你的 module 里使用的 function 跟 kernel encode 的结果不一样。所以无法 resolve。解决方式,要嘛就是将 kernel 里的 set version 选项关掉,要嘛就是将 module compile 成 kernel 有办法接受的型式。

那有人就会想说,如果 kernel 认定它提供的 function 名字叫做 driver_info_R744aa133 的话,那我们写程序时,是不是用到这个 funnction 的地方都改成 driver_info_R744aa133 就可以了。答案是 Yes。但是,如果每个 function 都要你这样写,你不会觉得很烦吗 ? 比方说,我们在写 driver 时,很多人都会用到 printk 这个 function。这是 kernel 所提供的 function。它的功能跟 printf 很像。用法也几乎都一样。是 debug 时很好用的东西。如果我们 module 里用了一百次 printk,那是不是我们也要打一百次的 printk_Rdd132261 呢 ? 当然不是,聪明的人马上会想到用 #define printk printk_Rdd132261 就好了嘛。所以啰,Linux 很体贴的帮我们做了这件事。
如果各位的系统有将set version的选项打开的话,那大家可以到/usr/src/linux/include/linux/modules这个目录底下。这个目录底下有所 多的..ver档案。这些档案其实就是用来做#define用的。我们来看看ksyms.ver 这个档案里,里面有一行是这样子的 :

#define printk _set_ver(printk)

set_ver是一个macro,就是用来在printk后面加上version number的。有兴趣的朋友可以自行去观看这个macro的写法。用了这些ver檔,我们就可以在module里直接使用printk这样的名字了。而 这些ver档会自动帮我们做好#define的动作。可是,我们可以发现这个目录有很多很多的ver檔。有时候,我们怎幺知道我们要呼叫的 function是在那个ver档里有定义呢?Linux又帮我们做了一件事。/usr/src/linux/include/linux /modversions.h这个档案已经将全部的ver档都加进来了。所以在我们的module里只要include这个档,那名字的问题都解决了。但 是,在此,我们奉劝各位一件事,不要将modversions.h这个档在module里include进来,如果真的要,那也要加上以下数行:

#ifdef MODVERSIONS
#include linux/modversions.h
#endif

加入这三行 的原因是,避免这个 module 在没有设定 kernel version 的系统上,将 modversions.h 这个档案 include 进来。各位可以去试试看,当你把 set version 的选项关掉时,modversions.h 和 modules 这个目录都会不见。如果没有上面三行,那 compile 就不会过关。所以一般来讲,modversions.h 我们会选择在 compile 时传给 gcc 使用。就像下面这个样子。
gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS main.c
-include usr/src/linux/include/linux/modversions.h
在 这个 command line 里,我们看到了 -D__KERNEL__,这是说要定义 __KERNEL__ 这个 constant。很多跟 kernel 有关的 header file,都必须要定义这个 constant 才能 include 的。所以建议你最好将它定义起来。另外还有一个 -DMODVERSIONS。这个 constant 我刚才忘了讲。刚才我们说要解决 fucntion 或 variable 名字 encode 的方式就是要 include modversions.h,其实除此之外,你还必须定义 MODVERSIONS 这个 constant。再来就是 MODULE 这个 constant。其实,只要是你要写 module 就一定要定义这个变量。而且你还要 include module.h 这个档案,因为 _set_ver 就是定义在这里的。
讲到这里,相信各位应该对 module 有一些认识了,以后遇到 module unresolved 应该不会感到困惑了,应该也有办法解决了。

变量/函数的导出(export)

刚才讲的都是使用别人的 function 上遇到的名字 encode 问题。但是,如果我们自己的 module 想要 export 一些东西让别的 module 使用呢。很简单。在 default 上,在你的 module 里所有的 global variable 和 function 都会被认定为你要 export 出去的。所以,如果你的 module 里有 10 个 global variable,经由 ksyms,你可以发现这十个 variable 都会被 export 出去。这当然是个很方便的事啦,但是,你知道,有时候我们根本不想把所有的 variable 都 export 出去,万一有个 module 没事乱改我们的 variable 怎幺办呢 ? 所以,在很多时候,我们都只会限定几个必要的东西 export 出去。在 2.2.1 之前的 kernel (不是很确定) 可以利用 register_symtab 来帮我们。但是,现在更新的版本早就出来了。所以,在此,我会介绍 kernel 2.2.1 里所提供的。kernel 2.2.1 里提供了一个 macro,叫做 EXPORT_SYMBOL,这是用来帮我们选择要 export 的 variable 或 function。比方说,我要 export 一个叫 full 的 variable,那我只要在 module 里写:

EXPORT_SYMBOL(full);

就会自动将 full export 出去,你马上就可以从 ksyms 里发现有 full 这个变量被 export 出去。在使用 EXPORT_SYMBOL 之前,要小心一件事,就是必须在 gcc 里定义 EXPORT_SYMTAB 这个 constant,否则在 compile 时会发生 parser error。所以,要使用 EXPORT_SYMBOL 的话,那 gcc 应该要下:

gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS -DEXPORT_SYMTAB
main.c -include /usr/src/linux/include/linux/modversions.h

如果我们不想 export 任何的东西,那我们只要在 module 里下

EXPORT_NO_SYMBOLS;

就可以了。使用 EXPORT_NO_SYMBOLS 用不着定义任何的 constant。其实,如果各位使用过旧版的 register_symbol 的话,一定会觉得新版的方式比较好用。至少我是这样觉得啦。因为使用 register_symbol 还要先定义出自己的 symbol_table,感觉有点麻烦。

当我们使用 EXPORT_SYMBOL 把一些 function 或 variable export 出来之后,我们使用 ksyma -a 去看一些结果。我们发现 EXPORT_SYMBOL(full) 的确是把 full export出来了 :

c8822200 full [my_module]
c01b8e08 pci_find_slot_R454463b5
. . .

但是,结果怎幺跟我们想象中的不太一样,照理说,应该是 full_Rxxxxxx 之类的东西才对啊,怎幺才出现 full 而已呢 ? 奇怪,问题在那里呢 ?

其实,问题就在于我们没有对本身的 module 所 export 出来的 function 或 variable 的名字做 encode。想想,如果在 module 的开头。我们加入一行

#define full full_Rxxxxxx

之后,我们再重新 compile module 一次,载到 kernel 之后,就可以发现 ksyms -a 显示的是

c8822200 full_Rxxxxxx [my_module]
c01b8e08 pci_find_slot_R454463b5
. . . . .

了。那是不是说,我们要去对每一个 export 出来的 variable 和 function 做 define 的动作呢 ? 当然不是啰。记得吗,前头我们讲去使用 kernel export 的 function 时,由于 include 了一些 .ver 的档案,以致于我们不用再做 define 的动作。现在,我们也要利用 .ver 的档案来帮我们,使我们 module export 出来的 function 也可以自动加入 kernel version 的 information。也就是变成 full_Rxxxxxx 之类的东西。

Linux 里提供了一个 command,叫 genksyms,就是用来帮我们产生这种 .ver 的档案的。它会从 stdin 里读取 source code,然后检查 source code 里是否有 export 的 variable 或 function。如果有,它就会自动为每个 export 出来的东西产生一些 define。这些 define 就是我们之前说的。等我们有了这些 define 之后,只要在我们的 module 里加入这些 define,那 export 出来的 function 或 variable 就会变成上面那个样子。

假设我们的程序都放在一个叫 main.c 的档案里,我们可以使用下列的方式产生这些 define。

gcc -E -D__GENKSYMS__ main.c | genksyms -k 2.2.1 > main.ver

gcc 的 -E 参数是指将 preprocessing 的结果 show 出来。也就是说将它 include 的档案,一些 define 的结果都展开。-D__GENKSYMS__ 是一定要的。如果没有定义这个 constant,你将不会看到任何的结果。用一个管线是因为 genksyms 是从 stdin 读资料的,所以,经由管线将 gcc 的结果传给 genksyms。-k 2.2.1 是指目前使用的 kernel 版本是 2.2.1,如果你的 kernel 版本不一样,必须指定你的 kernel 的版本。产生的 define 将会被放到 main.ver 里。产生完 main.ver 档之后,在 main.c 里将它 include 进来,那一切就 OK 了。有件事要告诉各位的是,使用这个方式产生的 module,其 export 出来的东西会经由 main.ver 的 define 改头换面。所以如果你要让别人使用,那你必须将 main.ver 公开,不然,别人就没办法使用你 export 出来的东西了。

内核模块和系统的交互
/proc和设备驱动程序是内核和系统进行交互的两种方式。
/proc
/proc的使用:

defined in /kerneltree/include/linux/proc_fs.h
2.4.0:
struct proc_dir_entry {
unsigned short low_ino;
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
unsigned long size;
struct inode_operations * proc_iops;
struct file_operations * proc_fops;
get_info_t *get_info;
struct module *owner;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;
write_proc_t *write_proc;
atomic_t count; /* use count */
int deleted; /* delete flag */
kdev_t rdev;
};

extern struct proc_dir_entry *proc_sys_root;

#ifdef CONFIG_SYSCTL
EXPORT_SYMBOL(proc_sys_root);
#endif
EXPORT_SYMBOL(proc_symlink);
EXPORT_SYMBOL(proc_mknod);
EXPORT_SYMBOL(proc_mkdir);
EXPORT_SYMBOL(create_proc_entry);
EXPORT_SYMBOL(remove_proc_entry);
EXPORT_SYMBOL(proc_root);
EXPORT_SYMBOL(proc_root_fs);
EXPORT_SYMBOL(proc_net);
EXPORT_SYMBOL(proc_bus);
EXPORT_SYMBOL(proc_root_driver);

2.2.18
struct proc_dir_entry {
unsigned short low_ino;
unsigned short namelen;
const char *name;
mode_t mode;
nlink_t nlink;
uid_t uid;
gid_t gid;
unsigned long size;
struct inode_operations * ops;
int (*get_info)(char *, char **, off_t, int, int);
void (*fill_inode)(struct inode *, int);
struct proc_dir_entry *next, *parent, *subdir;
void *data;
int (*read_proc)(char *page, char **start, off_t off,
int count, int *eof, void *data);
int (*write_proc)(struct file *file, const char *buffer,
unsigned long count, void *data);
int (*readlink_proc)(struct proc_dir_entry *de, char *page);
unsigned int count; /* use count */
int deleted; /* delete flag */
};


#ifdef CONFIG_SYSCTL
EXPORT_SYMBOL(proc_sys_root);
#endif
EXPORT_SYMBOL(proc_register);
EXPORT_SYMBOL(proc_unregister);
EXPORT_SYMBOL(create_proc_entry);
EXPORT_SYMBOL(remove_proc_entry);
EXPORT_SYMBOL(proc_root);
EXPORT_SYMBOL(proc_root_fs);
EXPORT_SYMBOL(proc_get_inode);
EXPORT_SYMBOL(proc_dir_inode_operations);
EXPORT_SYMBOL(proc_net_inode_operations);
EXPORT_SYMBOL(proc_net);
EXPORT_SYMBOL(proc_bus);

example:
struct proc_dir_entry Our_Proc_File =
{
0, /* Inode number - ignore, it will be filled by
* proc_register[_dynamic] */

4, /* Length of the file name */
"test", /* The file name */
S_IFREG | S_IRUGO, /* File mode - this is a regular
* file which can be read by its
* owner, its group, and everybody
* else */

1, /* Number of links (directories where the
* file is referenced) */

0, 0, /* The uid and gid for the file - we give it
* to root */

80, /* The size of the file reported by ls. */
NULL, /* functions which can be done on the inode
* (linking, removing, etc.) - we don
* support any. */

procfile_read, /* The read function for this file,
* the function called when somebody
* tries to read something from it. */

NULL /* We could have here a function to fill the
* files inode, to enable us to play with
* permissions, ownership, etc. */

};

使用:
2.2.* & 2.0.*
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
/* In version 2.2, proc_register assign a dynamic
* inode number automatically if it is zero in the
* structure , so theres no more need for
* proc_register_dynamic
*/

return proc_register(&proc_root, &Our_Proc_File);
#else
return proc_register_dynamic(&proc_root, &Our_Proc_File);
#endif


proc_unregister(&proc_root, Our_Proc_File.low_ino);

2.4.*


#ifndef _LINUX_SYSCTL_H
#define _LINUX_SYSCTL_H

#include
#include
#include

/* A sysctl table is an array of struct ctl_table: */
struct ctl_table
{
int ctl_name; /* Binary ID */
const char *procname; /* Text ID for /proc/sys, or zero */
void *data;
int maxlen;
mode_t mode;
ctl_table *child;
proc_handler *proc_handler; /* Callback for text formatting */
ctl_handler *strategy; /* Callback function for all r/w */
struct proc_dir_entry *de; /* /proc control block */
void *extra1;
void *extra2;
};

/* struct ctl_table_header is used to maintain dynamic lists of
ctl_table trees. */

struct ctl_table_header
{
ctl_table *ctl_table;
struct list_head ctl_entry;
};

struct ctl_table_header * register_sysctl_table(ctl_table * table,
int insert_at_head);
void unregister_sysctl_table(struct ctl_table_header * table);

例:
/kerneltree/net/khttpd/sysctl.c

1.#inlclude
2./kerneltree/inlcude/linux/sysctl.h进行修改

设备驱动程序
设备文件的使用:

/usr/include/linux/fs.h

struct file_operations {
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *);
int (*fasync) (int, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
int (*lock) (struct file *, int, struct file_lock *);
};

重新定义相应的函数。

设备和系统的交互:

#include /* The character device
* definitions are here */

#include /* A wrapper which does
* next to nothing at
* at present, but may
* help for compatibility
* with future versions
* of Linux */


主码/从码
printk("Device: %d.%dn", inode->i_rdev >> 8, inode->i_rdev & 0xFF);
primary key secondery key
1. 字符设备
Major = module_register_chrdev(0,
DEVICE_NAME,
&Fops);
ret = module_unregister_chrdev(Major, DEVICE_NAME);
2. 块设备
Major = module_register_blkdev(0,
DEVICE_NAME,
&Fops);
ret = module_unregister_blkdev(Major, DEVICE_NAME);

/defined in &

系统调用
和模块有关的系统调用
sys_create_module()
sys_init_module()
sys_delete_module()
sys_get_kernel_syms()
get_module_list()
get_ksyms_list()
sys_query_module()
int request_module (const char *name);
int release_module (const char* name, int waitflag);
int delayed_release_module (const char *name);
int cancel_release_module (const char *name);
系统调用原型:
syscalln(rettype, name, type1, parm1,……)
#include
系统调用的使用:
1. 系统调用表
#include

asmlinkage int (*original_call)(const char *, int, int);
asmlinkage int our_sys_open(const char *filename,
int flags,
int mode)

original_call = sys_call_table[__NR_open];
sys_call_table[__NR_open] = our_sys_open;
if (sys_call_table[__NR_open] != our_sys_open) {
printk("Somebody else also played with the ");
printk("open system calln");
printk("The system may be left in ");
printk("an unstable state.n");
}

sys_call_table[__NR_open] = original_call;

2. syscalln

static inline _syscall1(int, brk, void *, end_data_segment);

...

int ret, tmp;
char *truc = OLDEXEC;
char *nouveau = NEWEXEC;
unsigned long mmm;
mmm = current->mm->brk; /*定位当前进程数据段大小*/
ret = brk((void ) (mmm 256)); /*利用系统调用brk为当前进程增加内存256个字节*/
if (ret < 0)
return ret; /*分配不成功*/
memcpy_tofs((void *) (mmm 2), nouveau, strlen(nouveau) 1);

3. 扩展
#define _syscall1(type,name,type1,arg1)
type name(type1 arg1)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)));
if (__res >= 0)
return (type) __res;
errno = -__res;
return -1;
}

阅读(797) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~