打印

Linux内核模块与应用程序的对比

[复制链接]
126|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
スモモ|  楼主 | 2018-9-27 13:14 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
用户态程序               Linux内核模块运行     用户空间          内核空间

入口     main()             module_init()指定;

出口     无                   module_exit()指定;

编译     gcc –c            Makefile

连接     ld                     insmod

运行     直接运行          insmod

调试     gdb                  kdbug, kdb, kgdb等



Linux内核模块的优点与缺点

优点

使得内核更加紧凑和灵活

修改内核时,不必全部重新编译整个内核。系统如果需要使用新模块,只要编译相应的模块,然后使用insmod将模块装载即可

模块的目标代码一旦被链接到内核,它的作用域和静态链接的内核目标代码完全等价。

缺点

由于内核所占用的内存是不会被换出的,所以链接进内核的模块会给整个系统带来一定的性能和内存利用方面的损失;

装入内核的模块就成为内核的一部分,可以修改内核中的其他部分,因此,模块的使用不当会导致系统崩溃;

为了让内核模块能访问所有内核资源,内核必须维护符号表,并在装入和卸载模块时修改符号表;

模块会要求利用其它模块的功能,所以,内核要维护模块之间的依赖性。





调用的函数对比:

      程序员并不总是自己写所有用到的函数。一个常见的基本的例子就是printf(),使用这些C标准库,libc提供的库函数。这些函数(像printf()) 实际上在连接之前并不进入你的程序。在连接时这些函数调用才会指向你调用的库,从而使你的代码最终可以执行。

      内核模块有所不同。在模块中使用函数 printk() 没有包含标准I/O库。这是因为模块是在insmod加载时才连接的目标文件。那些要用到的函数的符号链接是内核自己提供的。也就是说,你可以在内核模块中使用的函数只能来自内核本身。如果你对内核提供了哪些函数符号链接感兴趣,看一看文件/proc/kallsyms。对printk函数说明:每个printk都会有个优先级,内核一共有8个优先级,它们都有对应的宏定义。如果未指定优先级,内核会选择默认的优先级DEFAULT_MESSAGE_LOGLEVEL。如果优先级数字比int console_loglevel变量小的话,消息就会打印到控制台上。如果syslogd和klogd守护进程在运行的话,则不管是否向控制台输出,消息都会被追加进/var/log/messages文件。klogd 只处理内核消息,syslogd 处理其他系统消息,比如应用程序。

      说明:

      库函数和系统调用的区别:

      库函数是高层的,完全运行在用户空间,为程序员提供调用更方便的接口,而真正在幕后完成实际事务的是系统调用。系统调用在内核态运行并且由内核自己提供。标准C库函数printf()可以被看做是一个通用的输出语句,但它实际做的是将数据转化为符合格式的字符串并且调用系统调用 write()输出这些字符串。(可以用调试工具strace命令,它可以接管被跟踪进程执行的系统调用和收到的信号,然后把每一个执行的系统调用的名字,参数和返回值打印出来。)一般库函数在用户态执行。 库函数调用一个或几个系统调用,而这些系统调用为库函数完成工作。



内存对比:

      用户编程:使用malloc()和free()函数申请和释放内存

      内核模块:进行内核编程时,最常用的内存申请和释放函数为在include/linux/kernel.h文件中声明的kmalloc()和kfree(),其原型为:

void *kmalloc(unsigned int len, int priority);

void kfree(void *__ptr);

      参数说明:

      kmalloc的priority参数通常设置为GFP_KERNEL,如果在中断服务程序里申请内存则用GFP_ATOMIC参数,因为使用GFP_KERNEL参数可能会引起睡眠,不能用于非进程上下文中(在中断中是不允许睡眠的)。

      说明:

      由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而应该使用Linux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):

unsigned long copy_from_user(void *to, const void *from, unsigned long n);

unsigned long copy_to_user (void * to, void * from, unsigned long len);

      copy_from_user、copy_to_user函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。include/asm/uaccess.h中定义的put_user和get_user用于内核空间和用户空间的单值交互(如:char、int、long)。



其它:

      一种内核模块是设备驱动程序,为使用硬件设备像电视卡和串口而编写。在Unix中,任何设备都被当作路径/dev 的设备文件处理,并通过这些设备文件提供访问硬件的方法。设备驱动为用户程序访问硬件设备。举例来说,声卡设备驱动程序es1370.o将会把设备文件 /dev/sound同声卡硬件Ensoniq IS1370联系起来。这样用户程序像 mp3blaster 就可以通过访问设备文件/dev/sound 运行而不必知道那种声卡硬件安装在系统上。

      在linux中,设备大致可分为:字符设备,块设备,和网络接口(字符设备包括那些必须以顺序方式,像字节流一样被访问的设备;如字符终端,串口等。块设备是指那些可以用随机方式,以整块数据为单位来访问的设备,如硬盘等;网络接口,就指通常网卡和协议栈等复杂的网络输入输出服务)。区别是块设备有缓冲区,所以它们可以对请求进行优化排序。这对存储设备尤其重要,因为读写相邻的文件总比读写相隔很远的文件要快。另一个区别是块设备输入和输出都是以数据块为单位的,但是字符设备就可以自由读写任意量的字节。大部分硬件设备为字符设备,因为它们不需要缓冲区和数据不是按块来传输的。你可以通过命令ls -l输出的头一个字母识别一个设备为何种设备。如果是'b' 就是块设备,如果是'c'就是字符设备。如果你想看一下已分配的主设备号都是些什么设备可以看一下文件 /usr/src/linux/Documentation/devices.txt。如果将我们的系统调用日志系统用字符型驱动程序的方式实现,也是一件轻松惬意地工作。我们可以将内核中收集和记录信息的那一部分编写成一个字符设备驱动程序。在驱动程序中,我们可以用open来启动服务,用read()返回处理好的记录,用ioctl()设置记录格式等,用close()停止服务,write()没有用到,那么我们可以不去实现它。然后在/dev/目录下建立一个设备文件对应我们新加入内核的系统调用日志系统驱动程序。

      注意:   

      当系统访问一个系统文件时,系统内核只使用主设备号来区别设备类型和决定使用何种内核模块。系统内核并不需要知道从设备号。内核模块驱动本身才关注从设备号,并用之来区别其操纵的不同设备。

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

458

主题

484

帖子

1

粉丝