excelA列用户名重复验证(有重复)对应B列的日期,求统一个A列用户名重复验证,对应的日期明细;

:转载时请以超链接形式标明文章原始出处和作者信息及本声明
1 用户一般的数据操作只能在用户态进行;要操作内核态数据,必须利用标准的接口实现。&&&有很多方法可以读取或者修改内核态的数据。&& 1.1 可以利用动态模块,向用户态一样操作核心态数据结构&& 1.2 可以利用标准的系统调用、添加的系统调用来操作核心态数据&& 1.3 利用procfs读取、写入核心态数据 [sysctl系统调用]&&&1.4 利用sysfs读取、写入核心态数据&&&1.5 利用seq_file接口操作核心态数据&[自己添加内核操作方法]&& 1.6&利用kprobe、jprobe、jretprobe调试技术操作核心态数据&& 1.7 利用netlink钩子函数操作核心态数据结构&& 1.8 利用访问设备文件的方式操作核心态数据结构&&&.....2&&内核态和用户态区别&& 内核态的出现源于保护模式,从本质上讲linux系统不相信用户、也不认为用户有能力能很好的利用os所提供& && 的强大的资源管理能力。为此OS运行在与用户隔离的空间中,运行级别为0,所有进程的内核态数据空间都是一样&& 的,因为那里跑的是操作系统的代码,执行基本的资源管理任务,线性地址空间位于oxc000 0000以上,&& 内存映射方式为:实际内存=线性内存-3G;用户态属于每个进程的私有空间[这也是进程间会有差别的原因],我&& 们一&般打交道使用的空间都是用户空间,用户空间的管理依靠于task_strut的mm内存管理单元,其将内存划分 && 为若干内存区域(vm_area_struct),然后依靠页表来管理这些上述的内存区域,用户空间是可以被内存交换换进换&&&出的,而内核空间显然不能被换出...3 内核态用户态交互的接口&& 1 中描述了大量的内核态交互方法,这好比linux系统的底层不同文件系统的访问[其在vfs层总是归结于同一系统&&&调用read和write操作]。内核数据交互也是类似,不过与fs访问正好相反,上面大量的交互方式都归结于下面两个&& 最基本的函数[copy_from_user和copy_to_user]。所以摁其咽喉,方能掌握本质,分析分析copy_from_user&& 函数4 copy_from_user()详解&&& 2.6.35.3内核版本4-1& arch/x86/include/asm/Uaccess_32.h& static inline unsigned long __must_check copy_from_user(void *to,&&&&&& const void __user *from,&&&&&& unsigned long n){&int sz = __compiletime_object_size(to);&& //宏定义
&if (likely(sz == -1 || sz &= n))&&n = _copy_from_user(to, from, n);&else&&copy_from_user_overflow();
&}解释:先判断内核空间to的空间是否满足拷贝数据n大小,不满足发出WARN(1, "Buffer overflow detected!\n");4-2 _copy_from_user(to,from,n)/**&* copy_from_user: - Copy a block of data from user space.&* @to:&& Destination address, in kernel space.&* @from: Source address, in user space.&* @n:&&& Number of bytes to copy.&*&* Context: User context only.& This function may sleep.&*&* Copy data from user space to kernel space.&*&* Returns number of bytes that could not be copied.&* On success, this will be zero.&*&* If some data could not be copied, this function will pad the copied&* data to the requested size using zero bytes.&*/unsigned long_copy_from_user(void *to, const void __user *from, unsigned long n){&if (access_ok(VERIFY_READ, from, n))&&n = __copy_from_user(to, from, n);&else&&memset(to, 0, n);&}解释:注释非常清楚,access_ok()检查用户空间合理性[不超过0xc000 0000],userspace映射问题后面在看4-3 __copy_from_user(to,from,n)& &arch/x86/include/asm/Uaccess_32.h/*&* An alternate version - __copy_from_user_inatomic() - may be called from&* atomic context and will fail rather than sleep.& In this case the&* uncopied bytes will *NOT* be padded with zeros.& See fs/filemap.h&* for explanation of why this is needed.&*/static __always_inline unsigned long__copy_from_user(void *to, const void __user *from, unsigned long n){&might_fault();&if (__builtin_constant_p(n)) {&&
&&switch (n) {&&case 1:&&&__get_user_size(*(u8 *)to, from, 1, ret, 1);&&&&&case 2:&&&__get_user_size(*(u16 *)to, from, 2, ret, 2);&&&&&case 4:&&&__get_user_size(*(u32 *)to, from, 4, ret, 4);&&&&&}&}&return __copy_from_user_ll(to, from, n);}解释:首先查看n是否为固定值1,2,4字节,如果是固定大小,则操作简单;否则调用通用拷贝函数,适合于大块传输4-4& __copy_from_user_ll(to, from, n)& /arch/x86/libunsigned long __copy_from_user_ll(void *to, const void __user *from,&&&&&unsigned long n){&if (movsl_is_ok(to, from, n))&&__copy_user_zeroing(to, from, n);&else&&n = __copy_user_zeroing_intel(to, from, n);&}解释:首先判断是否需要大规模数据拷贝,一般返回1注:linux采用AT&T编码方式,左边值为原操作数,右边值为目的操作数,与intel编码方式不同4-5 __copy_user_zeroing(to, from, size)&&&& &/arch/x86/lib& 进入copy的关键#define& __copy_user_zeroing(to, from, size)&&&&\do {&&&&&&&&&\&int __d0, __d1, __d2;&&&&&&\&__asm__ __volatile__(&&&&&&\&&&&&&&&&&&&&&&&&&& #注:以4字节做为成串传送的基本单位&&"&cmp& $7,%0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&& #比较size是否大于7,即判断是否需要成串传送,cmp中右为原操作数&&"&jbe& 1f\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& #如果小于7,跳到1处,以单字节作为传送单位&&"&movl %1,%0\n"&&&&&\&&&&&&&&&&&&&&&&&#ecx=to地址,此时ecx大于7字节,需要把余8单字节传,其余串传ecx=n&&"&negl %0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& #ecx取补码,&& 正数补码为自身&&"&andl $7,%0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#ecx=ecx%8,显然操作前ecx=n,且andl中右为原操作数&&"&subl %0,%3\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&#寄存器X-=ecx,ecx为模8取余,显然此时寄存器X=n - n%8&&"4:& movsb\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&#余8剩余字节按照字节拷贝,显然此时ecx为n模8取余的值&&"&movl %3,%0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&#ecx=该寄存器X值,X值应为n-n%8,movl中左为原操作数&&"&shrl $2,%0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&& #然后ecx=ecx/4,此时ecx为movsl的次数,shrl中右为原操作数&&"&andl $3,%3\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#该寄存器X=X%4,andl中右为原操作数&&"&.align 2,0x90\n"&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&& #&&"0:& movsl\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&#按照四字节倍数拷贝,每次拷贝4个字节,&& 此时ecx=n/4&&"&movl %3,%0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&& #ecx值设为n%4,&& 此时该寄存器X=n%4&&"1:& movsb\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&& #以单字节拷贝&&"2:\n"&&&&&&&\&&&&&&&&&&&&&& &&".section .fixup,\"ax\"\n"&&&&\&&"5:&addl %3,%0\n"&&\&&&#寄存器X值[n-n%8] + ecx值[n%8-已拷贝字节] --& &ecx,addl,subl中右为原操作数&&"&jmp 6f\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#跳转到后边标号6处,此时ecx显然为剩余拷贝的字节数&&"3:&lea 0(%3,%0,4),%0\n"&&&&\&&&&&&&&&&&&&&& #ecx=ecx*4+n%4,ecx=在出错时剩余拷贝的字节数 &&"6:&pushl %0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#ecx压入栈,待后面出错返回时使用&&"&pushl %%eax\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&& #eax寄存器压栈,用0填充内核剩余空间时用eax值填&&"&xorl %%eax,%%eax\n"&&&&\&&&&&&&&&&&&&&&&& #eax清0&&"& stosb\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& #此时将内核剩余复制空间清0&&"&popl %%eax\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&& #恢复eax值&&"&popl %0\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#ecx=剩余未拷贝的字节数&&"&jmp 2b\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#跳到前面2标号处,即退出内存拷贝&&".previous\n"&&&&&&\&&&& &&".section __ex_table,\"a\"\n"&&&&\&&&&&&&&&&&&& #专用的异常地址表,用于拷贝过程中的异常恢复&&"&.align 4\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& #4字节为单位对其&&"&.long 4b,5b\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& #标号5 为标号4的异常处理程序地址&&"&.long 0b,3b\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#标号3 为标号0的异常处理程序地址&&"&.long 1b,6b\n"&&&&&\&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&#标号6 为标号1的异常处理程序地址&&".previous"&&&&&&\&&: "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2)&\&&: "3"(size), "0"(size), "1"(to), "2"(from)&&\&&: "memory");&&&&&&\} while (0)解释:1&变量& 绑定寄存器& 初始值 输出值&&& 0&&&&& ecx&&&&&&&&&& size&&&& size&& 1&&&&& edi&&&&&&&&&&& to指针& __do变量&&&2&&&&& esi&&&&&&&&&&&&from&&&&__d1变量&& 3&&&&& 寄存器&&&&&&&&size&&&&&__d2变量2 分析2-1& gnu的gcc和ld支持四个段:text段、data段、fixup段、__ex_table段。& fixup段用于异常发生后的恢复操作,和text段没有太大差别& __ex_tabel段用于异常地址表2-2在cpu进行访址的时候,内核空间和用户空间使用的都是线性地址,cpu在访址的过程中会自动完成从线性地址到物理地址的转换[用户态、内核态都得依靠进程页表完成转换],而合理的线性地址意味着:该线性地址位于该进程task_struct-&mm虚存空间的某一段vm_struct_mm中,而且建立线性地址到物理地址的映射,即线性地址对应内容在物理内存中。如果访存失败,有两种可能:该线性地址存在在进程虚存区间中,但是并未建立于物理内存的映射,有可能是交换出去,也有可能是刚申请到线性区间[内核是很会偷懒的],要依靠缺页异常去建立申请物理空间并建立映射;第2种可能是线性地址空间根本没有在进程虚存区间中,这样就会出现常见的坏指针,就会引发常见的段错误[也有可能由于访问了无权访问的空间造成保护异常]。如果坏指针问题发生在用户态,最严重的就是杀死进程[最常见的就是在打dota时候出现的大红X,然后dota程序结束],如果发生在内核态,整个系统可能崩溃[xp的蓝屏很可能就是这种原因形成的]。所以linux当然不会任由这种情况的发生,其措施如下:&& &linux内核对于可能发生问题的指令都会准备"修复地址",比如前面的fixup部分,而且遵循谁使用这些指令,谁负责修复工作的原则。比如前面的代码中,标号5即为标号4的修复指令,3为0,6为1的修复指令。在编译过程中,编译器会将5,4等的地址对应的存入struct exception_table_entry{unsigned long insn,fixup;}中。insn即可能为4的地址,而fixup可能为5的地址,如果4为坏地址[即该地址并未在虚存区间中],则在页面异常处理过程中,会转入bad_area处,如果发生在用户态直接杀死进程即可。如果发生在内核态,首先通过search_exception_table查找异常处理表exception_table。即找到某一个exception_table_entry,假设其insn=标号4地址,fixup=标号5地址.内核将发生:regs-&ip=fixup,即通过修改当前的内核地址,从而将内核从死亡的边缘拉回来,通过标号5地址处的修复工作从而全身而退。&& 欢迎探讨、交流!
引用地址:
访问统计:
| 作者:NICK 素材来源:您的位置:
Linux下内核态-用户态高效易用的数据交互方法研究
Linux下内核态-用户态高效易用的数据交互方法研究
Research of efficient and easy to use Linux kernel-user data interaction method
发布时间:  浏览量:0  收藏数:0  评论数:
北京邮电大学网络技术研究院,北京 100876;
Linux操作系统是一款单内核操作系统,它将各子系统包含在内核中,并为所有进程提供服务。为了保护子系统的安全,Linux将进程空间划分为用户态和内核态两部分,并且相互隔离。为了更好的协调内核态程序与用户态程序运行,开发人员需要一套高效易用的内核态-用户态数据交互方法。而现有的几种方法或效率底下,或实现复杂等,均无法满足要求。因此本文在Linux提供的几种交互方法的基础上提出了一个基于共享内存、使用netlink发送控制信息、使用读写队列管理共享内存的数据通信方法。并对该方法进行了测试,结果显示该方法可以有效的提高内核态用户态程序间的通信效率,并为数据传输提供简单易用的读写接口。
内核态,数据传输,零拷贝,netlink,读写队列
Xu Mingkun,
Hu Yonggang
Institute of Network Technology, Beijing University of Posts and Telecommunications, Beijing 100876;
Abstract:
Linux operating system is a single-kernel operating system, which put its subsystems into the kernel, and provides services for all processes. In order to protect system security, Linux divides the process space into two parts: user mode and kernel mode, separate from each other. In order to coordinate the behaviour of kernel mode and user mode programs, developers need a set of efficient and easy method to transfer data between the kernel-user state. Existing methods are unable to meet the requirements, due to their inefficiency or complexity. Therefore, based on exsiting methods provided by Linux, this paper discussed a new method that transferring data by the shared-memory, sending control message by Netlink, and managing share-memories with reading-writing-queues. And this method was tested, the results show that the method can improve the communication efficiency between kernel-mode user mode programs, and provides easy to use interface to read and write for data transmission.
Keywords:
kernel-space, data transferring, zero copy, netlink, reading-writing-queue
PDF全文下载:
作者简介:
通信联系人:
【收录情况】
中国科技论文在线:徐明昆,胡勇刚.&Linux下内核态-用户态高效易用的数据交互方法研究[EB/OL].北京:中国科技论文在线&
[].http://www./releasepaper/content/.
发表期刊:
首发论文搜索
&> 信息科学与系统科学
&> 地球科学
&> 畜牧、兽医科学
&> 基础医学
&> 临床医学
&> 预防医学与卫生学
&> 军事医学与特种医学
&> 中医学与中药学
&> 工程与技术科学基础学科
&> 测绘科学技术
&> 材料科学
&> 矿山工程技术
&> 冶金工程技术
&> 机械工程
&> 动力与电气工程
&> 能源科学技术
&> 核科学技术
&> 电子、通信与自动控制技术
&> 计算机科学技术
&> 化学工程
&> 纺织科学技术
&> 食品科学技术
&> 土木建筑工程
&> 水利工程
&> 交通运输工程
&> 航空航天科学技术
&> 环境科学技术
&> 安全科学技术
&> 图书馆、情报与文献学
&> 体育科学
尊敬的作者,欢迎您在本站投稿:
注:请投稿作者直接在本站注册并登录提交文章,
任何个人或机构宣称代理在本站投稿均为侵权行为
本学科今日推荐
本文作者合作关系
本文相关论文
&&&&&&&&&&&&&&
中国科技论文在线
&|&&|&&|&&|&&|&nbsp
计算机科学技术基础学科
计算机系统结构
计算机软件
计算机工程
计算机应用
计算机科学技术其他学科
Linux下内核态-用户态高效易用的数据交互方法研究
&&收藏本文
&&推荐本文给好友
&&订阅本文所在学科
&&分享到我的圈子
多个邮箱请用逗号“,”隔开
分享到我的圈子一、基础知识
1.Netfilter
2.Netlink机制
二、IPQueue编程接口
三、一个实现接收内核态发送的IPQueue数据包的用户态例程
1.libipq.h
2.libipq.c
3.ipq_user.c
四、应用程序的测试
1.测试环境的建立
2.程序的测试
一、基础知识
基础知识部分的很多部分内容都重点参考或者直接引用了《如何用IPQueue机制编写用
户态防火墙》,原文的链接为:
在此,向该文的作者表示感谢。
1.Netfilter
Linux内核在Netfilter(下文简称NF)框架的基础上提供了IPQueue机制,使得基于用
户态(UserMode)的防火墙开发成为可能。
内核中NF对网络报文的处理这里不做详细描述。假设读者已经熟悉NF的工作原理和工作
流程。但这里还是要简单介绍一下NF中各个钩子(hook)函数对数据包处理的返回值,
即该函数告诉内核对该数据包的处理意见。所有的返回值如下:
NF_DROP:丢弃该报文,释放所有与该报文相关的资源;
NF_ACCEPT:接受该报文,并继续处理;
NF_STOLEN:该报文已经被HOOK函数接管,协议栈无须继续处理;
NF_QUEUE:将该报文传递到用户态去做进一步的处理;
NF_REPEAT:再次调用本HOOK函数。
当HOOK处理函数返回值为NF_QUEUE时,内核协议栈将通过IPQueue机制把当前报
文传递到用户态,由用户态的应用程序进行处理。这样,只要能够在相应的HOOK点上返
回NF_QUEUE值,就可以将符合要求的报文传送到用户态去做进一步对报文行处理。随
后,用户态程序会将处理后的报文以及对报文的处理意见(ACCEPT,DROP等)传递给
内核协议栈。内核协议栈就会按照用户态对报文的处理意见将报文做接受、丢弃等处理。
整个处理的过程就相当于一个用户态的防火墙,所有数据包的实质性处理都放在用户态进
行。这样,即使是不具有深入内核知识的开发人员,也可以开发出适应一定应用场合的用
户态防火墙。
2.Netlink机制
前面讲到,所谓IPQueue机制,只是当NF上Hook函数对数据包处理的返回值为
NF_QUEUE时,协议栈会将数据包交给内核中的ip_queue模块。而ip_queue又是怎么
将数据包传递给用户态的呢?这里就涉及到在内核开发中常见的问题,如何将内核态的数
据传递到用户态,实现内核空间和用户空间的通信。具体实现的方法有多种。本人的博客
中也总结了若干种,并配有测试的例程:
.对于IPQueue,则是使用Netlink
机制实现内核态和用户态的交互。
NetLink是Linux系统特有的、基于socket编程接口的通信机制。它是一个面向数据报
文的服务,并提供NETLINK_ROUTE(更新和修改路由操作)、NETLINK_FIREWALL
(接受和发送IPv4协议NF传输的包,基于内核的ip_queue模块),NETLINK_ARPD
(用户态ARP表操作)等多种通信协议。在创建基于IPQueue的NetLinkSocket时,
将采用如下系统调用:
fd=socket(PF_NETLINK,SOCK_RAW,NETLINK_FIREWALL);
这里,PF_NETLINK指明要创建NetLinkSocket;SOCK_RAW指明采用原始套接
字,也可以采用SOCK_DGRAM,因为NetLink机制的实现并不区分SOCK_RAW和
SOCK_DGRAM;参数NETLINK_FIREWALL则指明通信协议采用IPQueue。
既然IPQueue是基于NetLink的,其消息格式自然也遵从NetLink的规范。NetLink消
息由两部分组成:消息头(structnlmsghdr)和数据负载(datapayload)。
消息头的定义如下(include/linux/netlink.h):
structnlmsghdr
__u32nlmsg_/*消息长度*/
__u16nlmsg_/*消息类型*/
__u16nlmsg_/*额外的标志*/
__u32nlmsg_/*序列号*/
__u32nlmsg_/*进程号*/
structnlmsghdr
__u32nlmsg_/*消息长度*/
__u16nlmsg_/*消息类型*/
__u16nlmsg_/*额外的标志*/
__u32nlmsg_/*序列号*/
__u32nlmsg_/*进程号*/
所有的IPQueue消息都将包含一个structnlmsghdr消息头,具体的IPQueue消息则
包含在NetLink消息的数据负载中。有关NetLink消息格式的详情可以参见手册页
Netlink(7)。
二、IPQueue编程接口
使用IPQueue机制的程序必须包含如下的头文件:
#include&linux/netfilter_ipv4/ip_queue.h&
在这个头文件中定义了所有IPQueue消息的格式。以下谈到关于IPQueue的若干个数
据结构以及宏定义都包含在该文头件。
IPQueue消息可以分为两大类:由内核协议栈发给用户态进程的IPQueue消息和由用户
态进程发给内核的IPQueue消息。
由内核协议栈发给用户态进程的IPQueue消息(nlmsghdr.nlmsg_type=
IPQM_PACKET),其数据结构为ipq_packet_msg_t,定义如下:
/*Messagessentfromkernel*/
typedefstructipq_packet_msg{
unsignedlongpacket_/*报文的ID号*/
/*NF标记值*/
longtimestamp_/*报文到达时间(秒)*/
longtimestamp_/*报文到达时间(毫秒)*/
/*报文所处的NFhook点*/
charindev_name[IFNAMSIZ];/*流入网口名称*/
charoutdev_name[IFNAMSIZ];/*流出网口名称*/
unsignedshorthw_/*硬件协议(网络顺序)*/
unsignedshorthw_/*硬件类型*/
unsignedcharhw_/*硬件地址长度*/
unsignedcharhw_addr[8];/*硬件地址*/
size_tdata_/*报文数据的长度*/
unsignedcharpayload[0];/*报文本身的数据,可选*/
}ipq_packet_msg_t;
这个数据结构也被称为“报文的元数据”。个人理解,所谓“报文的元数据”应该是关于报文
的摘要信息,而不包括报文数据的本身。从上面的这个结构体中也可以看出来。内核除可
以单独向用户进程传递“报文的元数据”以外,也可以同时传递报文本身。此时,报文本身
的数据将存储在ipq_packet_msg_t数据成员payload开始的地方。
至于内核在什么情况下向用户传递报文的元数据,什么情况下向用户传递报文的元数据加
报文本身的数据,那就要看用户所请求的模式了。下面将讲述用户态发到内核态消息的格
式,也正好解答了我们这里提出的问题。
用户态发到内核态的消息,其数据结构如下所示:
typedefstructipq_peer_msg{
ipq_verdict_msg_
ipq_mode_msg_
}ipq_peer_msg_t;
通过该数据结构可知,这类消息又分为“模式设置消息(nlmsghdr.nlmsg_type=
IPQM_MODE)”和“断言消息(nlmsghdr.nlmsg_type=IPQM_VERDICT)”两个子类。
“模式设置消息”的数据结构定义如下:
typedefstructipq_mode_msg{
/*请求的模式*/
size_/*请求拷贝的报文长度*/
}ipq_mode_msg_t;
这里,请求模式value的值可以是IPQ_COPY_NONE、IPQ_COPY_META和
IPQ_COPY_PACKET,具体解释如下:
(1)请求模式value为IPQ_COPY_NONE时,报文将被丢弃;
(2)请求模式value为IPQ_COPY_META时,内核将在其后的报文传递中只传递“报文的
元数据”。
以上两种情形传递range的值将被内核忽略。内核中会将该值置为0。
(3)请求模式value为IPQ_COPY_PACKET时,内核将同时传递“报文的元数据”和报文
本身,报文本身的传递长度由ipq_mode_msg_t的另一个数据成员range指定。range
的最大值不能超过IP报文的最大长度,也就是0xFFFF。否则,会被自动置为0xFFFF。
如果请求的长度大于报文自身的长度,将会按照报文自身长度进行传递。
另一子类即“断言消息”,其数据类型定义如下:
typedefstructipq_verdict_msg{
size_tdata_
unsignedcharpayload[0];
}ipq_verdict_msg_t;
其中,value是用户态程序回传给内核的对当前报文的处理意见,可以是NF_ACCEPT或
NF_DROP等值。id则是用以区分报文的标识号,即内核传来的ipq_packet_msg_t结构
中的packet_id。当用户态程序修改了当前报文以后,需要将报文重新传递回内核,此
时,新的报文内容必须存储在payload的开始处,并由data_len指明新报文的长度。
从上述内容可以看出,在整个IPQueue的报文传递过程中,用户态程序和内核协议栈之
间的互动顺序是:
(1)用户态程序利用“模式设置消息”告诉内核协议栈所请求的报文传递模式;
(2)根据这个模式,内核组织好等待传递的消息,通过NetLinkSocket发给用户态程
(3)用户态程序对接收到的数据包进行处理,得出该报文的处理意见(可能同时修改当
前报文),并回传给内核。
三、一个实现接收内核态发送的IPQueue数据包的用户态例程
由于IPQueue是使用Netlink机制进行内核态和用户态通信的。因此,用户态要接
收内核态发送的IPQueue数据包,就需要设计相应的Netlink程序,也就是设计相应的
相关资料推荐
linux 数据包处理,文档最后附有源码
这些资料仅供linux爱好者参考学习
内核的同步机制,做驱动必看
Linux内核的Softirq机制的介绍。
课程设计资料
计算机数学
内核编程有用的资料
Linux内核机制之等待队列
今天被袭击了,问了我linux的同步机制,谁记得哦!看看资料就得了
在此可输入您对该资料的评论~
资料阅读排行
(window.slotbydup=window.slotbydup || []).push({
id: '2371234',
container: s,
size: '300,250',
display: 'inlay-fix'
该用户的其它资料
请选择举报的类型
赌博犯罪类
资料评价:
所需积分:0嵌入式&内核态和用户态的交互
摘要:在进行设备驱动程序,内核功能模块等系统级开发时,通常需要在内核和用户程序之间交换信息。Linux提供了多种方法可以用来完成这些任务。本文总结了各种常用的信息交换方法,并用简单的例子演示这些方法各自的特点及用法。其中有大家非常熟悉的方法,也有特殊条件下方可使用的手段。通过对比明确这些方法,可以加深我们对Linux内核的认识,更重要的是,可以让我们更熟练驾御linux内核级的应用开发技术。
内核空间(kernel-space) VS 用户空间(user-space)
作为一个Linux开发者,首先应该清楚内核空间和用户空间的区别。关于这个话题,已经有很多相关资料,我们在这里简单描述如下:
现代的计算机体系结构中存储管理通常都包含保护机制。提供保护的目的,是要避免系统中的一个任务访问属于另外的或属于操作系统的存储区域。如在IntelX86体系中,就提供了特权级这种保护机制,通过特权级别的区别来限制对存储区域的访问。
基于这种构架,Linux操作系统对自身进行了划分:一部分核心软件独立于普通应用程序,运行在较高的特权级别上,(Linux使用Intel体系的特权级3来运行内核。)它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。
相对的,其它部分被作为应用程序在用户空间执行。它们只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,不能直接访问硬件,不能直接访问内核空间,当然还有其他一些具体的使用限制。(Linux使用Intel体系的特权级0来运行用户程序。)
从安全角度讲将用户空间和内核空间置于这种非对称访问机制下是很有效的,它能抵御恶意用户的窥探,也能防止质量低劣的用户程序的侵害,从而使系统运行得更稳定可靠。但是,如果像这样完全不允许用户程序访问和使用内核空间的资源,那么我们的系统就无法提供任何有意义的功能了。为了方便用户程序使用在内核空间才能完全控制的资源,而又不违反上述的特权规定,从硬件体系结构本身到操作系统,都定义了标准的访问界面。关于X86系统的细节,请查阅参考资料1
一般的硬件体系机构都提供一种“门”机制。“门”的含义是指在发生了特定事件的时候低特权的应用程序可以通过这些“门”进入高特权的内核空间。对于IntelX86体系来说,Linux操作系统正是利用了“系统门”这个硬件界面(通过调用int
$0x80机器指令),构造了形形色色的系统调用作为软件界面,为应用程序从用户态陷入到内核态提供了通道。通过“系统调用”使用“系统门”并不需要特别的权限,但陷入到内核的具体位置却不是随意的,这个位置由“系统调用”来指定,有这样的限制才能保证内核安全无虞。我们可以形象地描述这种机制:作为一个游客,你可以买票要求进入野生动物园,但你必须老老实实的坐在观光车上,按照规定的路线观光游览。当然,不准下车,因为那样太危险,不是让你丢掉小命,就是让你吓坏了野生动物。
出于效率和代码大小的考虑,内核程序不能使用标准库函数(当然还有其它的顾虑,详细原因请查阅参考资料2)因此内核开发不如用户程序开发那么方便。而且由于目前(linux2.6还没正式发布)的内核是“非抢占”的,因此正在内核空间运行的进程是不会被其他进程取代的(除非该进程主动放弃CPU的控制,比如调用sleep(),schedule()等),所以无论是在进程上下文中(比如正在运行read系统调用),还是在中断上下文(正在中断服务程序中),内核程序都不能长时间占用CPU,否则其它程序将无法执行,只能等待。
内核空间和用户空间的相互作用
现在,越来越多的应用程序需要编写内核级和用户级的程序来一起完成具体的任务,通常采用以下模式:首先,编写内核服务程序利用内核空间提供的权限和服务来接收、处理和缓存数据;然后编写用户程序来和先前完成的内核服务程序交互,具体来说,可以利用用户程序来配置内核服务程序的参数,提取内核服务程序提供的数据,当然,也可以向内核服务程序输入待处理数据。
比较典型的应用包括: Netfilter(内核服务程序:防火墙)VS
Iptable(用户级程序:规则设置程序);IPSEC(内核服务程序:VPN协议部分)VS
IKE(用户级程序:vpn密钥协商处理);当然还包括大量的设备驱动程序及相应的应用软件。这些应用都是由内核级和用户级程序通过相互交换信息来一起完成特定任务的。
信息交互方法
用户程序和内核的信息交换是双向的,也就是说既可以主动从用户空间向内核空间发送信息,也可以从内核空间向用户空间提交数据。当然,用户程序也可以主动地从内核提取数据。下面我们就针对内核和用户交互数据的方法做一总结、归纳。
信息交互按信息传输发起方可以分为用户向内核传送/提取数据和内核向用户空间提交请求两大类,先来说说:
由用户级程序主动发起的信息交互。
用户级程序主动发起的信息交互
A编写自己的系统调用
从前文可以看出,系统调用是用户级程序访问内核最基本的方法。目前linux大致提供了二百多个标准的系统调用(参见内核代码树中的include/
asm-i386/unistd.h和arch/i386/kernel/entry.S文件),并且允许我们添加自己的系统调用来实现和内核的信息交换。比如我们希望建立一个系统调用日志系统,将所有的系统调用动作记录下来,以便进行入侵检测。此时,我们可以编写一个内核服务程序。该程序负责收集所有的系统调用请求,并将这些调用信息记录到在内核中自建的缓冲里。我们无法在内核里实现复杂的入侵检测程序,因此必须将该缓冲里的记录提取到用户空间。最直截了当的方法是自己编写一个新系统调用实现这种提取缓冲数据的功能。当内核服务程序和新系统调用都实现后,我们就可以在用户空间里编写用户程序进行入侵检测任务了,入侵检测程序可以定时、轮训或在需要的时候调用新系统调用从内核提取数据,然后进行入侵检测了。
B编写驱动程序
Linux/UNIX的一个特点就是把所有的东西都看作是文件(every thing is a
file)。系统定义了简洁完善的驱动程序界面,客户程序可以用统一的方法透过这个界面和内核驱动程序交互。而大部分系统的使用者和开发者已经非常熟悉这种界面以及相应的开发流程了。
驱动程序运行于内核空间,用户空间的应用程序通过文件系统中/dev/目录下的一个文件来和它交互。这就是我们熟悉的那个文件操作流程:open()
—— read() —— write() —— ioctl() ——
close()。(需要注意的是也不是所有的内核驱动程序都是这个界面,网络驱动程序和各种协议栈的使用就不大一致,比如说套接口编程虽然也有open()close()等概念,但它的内核实现以及外部使用方式都和普通驱动程序有很大差异。)关于这部分的编程细节,请查阅参考资料3、4。
设备驱动程序在内核中要做的中断响应、设备管理、数据处理等等各种工作这篇文章不去关心,我们把注意力集中在它与用户级程序交互这一部分。操作系统为此定义了一种统一的交互界面,就是前面所说的open(),
read(), write(),
ioctl()和close()等等。每个驱动程序按照自己的需要做独立实现,把自己提供的功能和服务隐藏在这个统一界面下。客户级程序选择需要的驱动程序或服务(其实就是选择/dev/目录下的文件),按照上述界面和文件操作流程,就可以跟内核中的驱动交互了。其实用面向对象的概念会更容易解释,系统定义了一个抽象的界面(abstract
interface),每个具体的驱动程序都是这个界面的实现(implementation)。
所以驱动程序也是用户空间和内核信息交互的重要方式之一。其实ioctl, read,
write本质上讲也是通过系统调用去完成的,只是这些调用已被内核进行了标准封装,统一定义。因此用户不必向填加新系统调用那样必须修改内核代码,重新编译新内核,使用虚拟设备只需要通过模块方法将新的虚拟设备安装到内核中(insmod上)就能方便使用。关于此方面设计细节请查阅参考资料5,编程细节请查阅参考资料6。
在linux中,设备大致可分为:字符设备,块设备,和网络接口(字符设备包括那些必须以顺序方式,像字节流一样被访问的设备;如字符终端,串口等。块设备是指那些可以用随机方式,以整块数据为单位来访问的设备,如硬盘等;网络接口,就指通常网卡和协议栈等复杂的网络输入输出服务)。如果将我们的系统调用日志系统用字符型驱动程序的方式实现,也是一件轻松惬意地工作。我们可以将内核中收集和记录信息的那一部分编写成一个字符设备驱动程序。虽然没有实际对应的物理设备,但这并没什么问题:Linux的设备驱动程序本来就是一个软件抽象,它可以结合硬件提供服务,也完全可以作为纯软件提供服务(当然,内存的使用我们是无法避免的)。在驱动程序中,我们可以用open来启动服务,用read()返回处理好的记录,用ioctl()设置记录格式等,用close()停止服务,write()没有用到,那么我们可以不去实现它。然后在/dev/目录下建立一个设备文件对应我们新加入内核的系统调用日志系统驱动程序。
C: 使用proc 文件系统
proc是Linux提供的一种特殊的文件系统,推出它的目的就是提供一种便捷的用户和内核间的交互方式。它以文件系统作为使用界面,使应用程序可以以文件操作的方式安全、方便的获取系统当前运行的状态和其它一些内核数据信息。
proc文件系统多用于监视、管理和调试系统,我们使用的很多管理工具如ps,top等,都是利用proc来读取内核信息的。除了读取内核信息,proc文件系统还提供了写入功能。所以我们也就可以利用它来向内核输入信息。比如,通过修改proc文件系统下的系统参数配置文件(/proc/sys),我们可以直接在运行时动态更改内核参数;再如,通过下面这条指令:
echo 1 & /proc/sys/net/ip_v4/ip_forward
开启内核中控制IP转发的开关,我们就可以让运行中的Linux系统启用路由功能。类似的,还有许多内核选项可以直接通过proc文件系统进行查询和调整。
除了系统已经提供的文件条目,proc还为我们留有接口,允许我们在内核中创建新的条目从而与用户程序共享信息数据。比如,我们可以为系统调用日志程序(不管是作为驱动程序也好,还是作为单纯的内核模块也好)在proc文件系统中创建新的文件条目,在此条目中显示系统调用的使用次数,每个单独系统调用的使用频率等等。我们也可以增加另外的条目,用于设置日志记录规则,比如说不记录open系统调用的使用情况等。关于proc文件系统得使用细节,请查阅参考资料7。
D: 使用虚拟文件系统
有些内核开发者认为利用ioctl()系统调用往往会似的系统调用意义不明确,而且难控制。而将信息放入到proc文件系统中会使信息组织混乱,因此也不赞成过多使用。他们建议实现一种孤立的虚拟文件系统来代替ioctl()和/proc,因为文件系统接口清楚,而且便于用户空间访问,同时利用虚拟文件系统使得利用脚本执行系统管理任务更家方便、有效。
我们举例来说如何通过虚拟文件系统修改内核信息。我们可以实现一个名为sagafs的虚拟文件系统,其中文件log对应内核存储的系统调用日志。我们可以通过文件访问特普遍方法获得日志信息:如
# cat /sagafs/log
使用虚拟文件系统——VFS实现信息交互使得系统管理更加方便、清晰。但有些编程者也许会说VFS 的API
接口复杂不容易掌握,不要担心2.5内核开始就提供了一种叫做libfs的例程序帮助不熟悉文件系统的用户封装了实现VFS的通用操作。有关利用VFS实现交互的方法看参考资料。
E: 使用内存映像
Linux通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核中特定部分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一块相同的内存。这样做的直观效果显而易见:内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本无须数据拷贝。而在使用系统调用交互信息时,在整个操作过程中必须有一步数据拷贝的工作——或者是把内核数据拷贝到用户缓冲区,或只是把用户数据拷贝到内核缓冲区——这对于许多数据传输量大、时间要求高的应用,这无疑是致命的一击:许多应用根本就无法忍受数据拷贝所耗费的时间和资源。
我们曾经为一块高速采样设备开发过驱动程序,该设备要求在20兆采样率下以1KHz的重复频率进行16位实时采样,每毫秒需要采样、DMA和处理的数据量惊人,如果要使用数据拷贝的方法,根本无法达成要求。此时,内存映像成为唯一的选择:我们在内存中保留了一块空间,将其配置成环形队列供采样设备DMA输出数据。再把这块内存空间映射到在用户空间运行的数据处理程序上,于是,采样设备刚刚得到并传送到主机上的数据,马上就可以被用户空间的程序处理。
实际上,内存影射方式通常也正是应用在那些内核和用户空间需要快速大量交互数据的情况下,特别是那些对实时性要求较强的应用。X
window系统的服务器的虚拟内存区域,就可以被看做是内存映像用法的一个典型例子:X服务器需要对视频内存进行大量的数据交换,相对于lseek/write来说,将图形显示内存直接影射到用户空间可以显著提高效能。
并不是任何类型的应用都适合mmap,比如像串口和鼠标这些基于流数据的字符设备,mmap就没有太大的用武之地。并且,这种共享内存的方式存在不好同步的问题。由于没有专门的同步机制可以让用户程序和内核程序共享,所以在读取和写入数据时要有非常谨慎的设计以保证不会产生干绕。
mmap完全是基于共享内存的观念了,也正因为此,它能提供额外的便利,但也特别难以控制。
由内核主动发起的信息交互
在内核发起的交互中,我们最关心和感兴趣的应该是内核如何向用户程序发消息,用户程序又是怎样接收这些消息的,具体问题通常集中在下面这几个方面:内核可否调用用户程序?是否可以通过向用户进程发信号来告知用户进程事件发生?
前面介绍的交互方法最大的不同在于这些方式是由内核采取主动,而不是等系统调用来被动的返回信息的。
A 从内核空间调用用户程序。
即使在内核中,我们有时也需要执行一些在用户级才提供的操作:如打开某个文件以读取特定数据,执行某个用户程序从而完成某个功能。因为许多数据和功能在用户空间是现有的或者已经被实现了,那么没有必要耗费大量的资源去重复。此外,内核在设计时,为了拥有更好的弹性或者性能以支持未知但有可能发生的变化,本身就要求使用用户空间的资源来配合完成任务。比如内核中动态加载模块的部分需要调用kmod。但在编译kmod的时候不可能把所有的内核模块都订下来(要是这样的话动态加载模块就没有存在意义了),所以它不可能知道在它以后才出现的那些模块的位置和加载方法。因此,模块的动态加载就采用了如下策略:加载任务实际上由位于用户空间的modprobe程序帮助完成——最简单的情形是modprobe用内核传过来的模块名字作为参数调用insmod。用这种方法来加载所需要的模块。
内核中启动用户程序还是要通过execve这个系统调用原形,只是此时的调用发生在内核空间,而一般的系统调用则在用户空间进行。如果系统调用带参数,那将会碰到一个问题:因为在系统调用的具体实现代码中要检查参数合法性,该检查要求所有的参数必须位于用户空间——地址处于0x0000000——0xC0000000之间,所以如果我们从内核传递参数(地址大于0xC0000000),那么检查就会拒绝我们的调用请求。为了解决这个问题,我们可以利用set_fs宏来修改检查策略,使得允许参数地址为内核地址。这样内核就可以直接使用该系统调用了。
例如:在kmod通过调用execve来执行modprobe的代码前需要有set_fs(KERNEL_DS):
set_fs(KERNEL_DS);
if (execve(program_path, argv, envp) & 0)
上述代码中program_path 为"/sbin/modprobe",argv为{ modprobe_path, "-s",
"-k", "--", (char*)module_name, NULL },envp为{ "HOME=/",
"TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }。
从内核中打开文件同样使用带参数的open系统调用,所需的仍是要先调用set_fs宏。
B 利用brk系统调用来导出内核数据
内核和用户空间传递数据主要是用get_user(ptr)和put_user(datum,ptr)例程。所以在大部分需要传递数据的系统调用中都可以找到它们的身影。可是,如果我们不是通过用户程序发起的系统调用——也就是说,没有明确的提供用户空间内的缓冲区位置——的情况下,如何向用户空间传递内核数据呢?
显然,我们不能再直接使用put_user()了,因为我们没有办法给它指定目的缓冲区。所以,我们要借用brk系统调用和当前进程空间:brk用于给进程设置堆空间的大小。每个进程拥有一个独立的堆空间,malloc等动态内存分配函数其实就是进程的堆空间中获取内存的。我们将利用brk在当前进程(current
process)的堆空间上扩展一块新的临时缓冲区,再用put_user将内核数据导出到这个确定的用户空间去。
还记得刚才我们在内核中调用用户程序的过程吗?在那里,我们有一个跳过参数检查的操作,现在有了这种方法,可以另辟蹊径了:我们在当前进程的堆上扩展一块空间,把系统调用要用到的参数通过put_user()拷贝到新扩展得到的用户空间里,然后在调用execve的时候以这个新开辟空间地址作为参数,于是,参数检查的障碍不复存在了。
char * program_path = "/bin/ls" ;
mmm=current-&mm-&
ret = brk(*(void)(mmm+256));
put_user((void*)2,program_path,strlen(program_path)+1);
execve((char*)(mmm+2));
tmp = brk((void*)mmm);
这种方法没有一般性(具体的说,这种方法有负面效应吗),只能作为一种技巧,但我们不难发现:如果你熟悉内核结构,就可以做到很多意想不到的事情!
C: 使用信号:
信号在内核里的用途主要集中在通知用户程序出现重大错误,强行杀死当前进程,这时内核通过发送SIGKILL信号通知进程终止,内核发送信号使用send_sign(pid,sig)例程,可以看到信号发送必须要事先知道进程序号(pid),所以要想从内核中通过发信号的方式异步通知用户进程执行某项任务,那么必须事先知道用户进程的进程号才可。而内核运行时搜索到特定进程的进程号是个费事的工作,可能要遍历整个进程控制块链表。所以用信号通知特定用户进程的方法很糟糕,一般在内核不会使用。内核中使用信号的情形只出现在通知当前进程(可以从current变量中方便获得pid)做某些通用操作,如终止操作等。因此对内核开发者该方法用处不大。
类似情况还有消息操作。这里不罗嗦了。
总结&&由用户级程序主动发起的信息交互,无论是采用标准的调用方式还是透过驱动程序界面,一般都要用到系统调用。而由内核主动发起信息交互的情况不多。也没有标准的界面,操作大不方便。所以一般情况下,尽可能用本文描述的前几种方法进行信息交互。毕竟,在设计的根源上,相对于客户级程序,内核就被定义为一个被动的服务提供者。因此,我们自己的开发也应该尽量遵循这种设计原则。
1 周明德,保护方式下的80386及其编程,清华大学出版社,1993
2 Robert Love, Linux Kernel Development,Sams
Publishing,2003
3 W.Richard Stevens, Advanced Programming in the UNIX
Environment,Addision Wesley,1992
4 W.Richard Stevens, UNIX Network Programming, Prentic Hall,
5 Maurice J. Bach, The Design of the UNIX Operating System,
Prentic Hall, 1990
6 Linux Device Driver, O’Reilly
7 Ori Pomerantz ,Linux Kernel Module Programming Guide,
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 php防止用户重复登录 的文章

 

随机推荐