linux系统内核模块与用户程序对比

来源:互联网 发布:原生js循环遍历dom 编辑:程序博客网 时间:2024/06/06 14:13

linux系统内核模块与用户程序对比:


开始与结束对比:
      用户程序:用户程序通常从函数main()开始,执行一系列的指令并且当指令执行完成后结束程序。
      内核模块:内核模块从函数init_module 或是用宏module_init指定的函数调用开始。这就是内核模块的入口函数。它告诉内核模块提供那些功能扩展并且让内核准备好在需要时调用它。当它完成这些后,该函数就执行结束了。模块在被内核调用前也什么都不做。 所有的模块或是调用cleanup_module或是用宏module_exit指定的函数。这是模块的退出函数。它撤消入口函数所做的一切。例如注销入口函数所注册的功能。

 

调用的函数对比:
      程序员并不总是自己写所有用到的函数。一个常见的基本的例子就是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/目录下建立一个设备文件对应我们新加入内核的系统调用日志系统驱动程序。
      注意:   
      当系统访问一个系统文件时,系统内核只使用主设备号来区别设备类型和决定使用何种内核模块。系统内核并不需要知道从设备号。内核模块驱动本身才关注从设备号,并用之来区别其操纵的不同设备。

 

内核空间和用户空间的相互作用:
       现在,越来越多的应用程序需要编写内核级和用户级的程序来一起完成具体的任务,通常采用以下模式:首先,编写内核服务程序利用内核空间提供的权限和服务来接收、处理和缓存数据;然后编写用户程序来和先前完成的内核服务程序交互,具体来说,可以利用用户程序来配置内核服务程序的参数,提取内核服务程序提供的数据,当然,也可以向内核服务程序输入待处理数据。比较典型的应用包括: Netfilter(内核服务程序:防火墙)VS Iptable(用户级程序:规则设置程序);IPSEC(内核服务程序:VPN协议部分)VS IKE(用户级程序:vpn密钥协商处理);当然还包括大量的设备驱动程序及相应的应用软件。这些应用都是由内核级和用户级程序通过相互交换信息来一起完成特定任务的。驱动程序也是用户空间和内核信息交互的重要方式之一。

 

原创粉丝点击