共享内存会导致用户态和应用程序与内核交互态的频繁交互吗

用户态通信
user-level communication
用户态通信
基于1个网页-
user-level communication
- 引用次数:1
参考来源 -
&2,447,543篇论文数据,部分数据来源于
同时,本文改进了用户层和内核态的通信机制,实现了VPN的基本网络流程。
Meanwhile, we improve User-Kernel communication mechanism, and implement basic network flow of VPN.
通过消除数据传输过程中用户态和核心态之间上下文切换、将应用程序空间直接作为网络数据缓存来降低通信开销。
VI Architecture (VIA) reduces the communication overhead by eliminating the context switch between the user mode and the kernel mode and using the application space as the network data buffer .
的Unix System V还对用户态进程间通信提供了其他机制。这些机制还被很多Unix系统所采用。这些机制包括信号量,消息队列,共享内存;
AT&T's Unix System V introduced other kinds of interprocess communication among processes in User Mode, which have been adopted by many Unix kernels: semaphores , message queues , and shared memory .
$firstVoiceSent
- 来自原声例句
请问您想要如何调整此模块?
感谢您的反馈,我们会尽快进行适当修改!
请问您想要如何调整此模块?
感谢您的反馈,我们会尽快进行适当修改!博客访问: 676694
博文数量: 186
博客积分: 1528
博客等级: 上尉
技术积分: 1761
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
例子中,用户态程序的KERNEL_VIRT_ADDR就是内核模块打印的地址p 这里是hard coding(先加载内核模块,再把打印的地址赋值给KERNEL_VIRT_ADDR), 可以采用其他的方式传递。 2.6内核验证。
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wheelz");
MODULE_DESCRIPTION("mmap demo");
static unsigned long p = 0;
static int __init init(void)
&&&&//分配共享内存(一个页面)
&&&&p = __get_free_pages(GFP_KERNEL, 0);
&&&&SetPageReserved(virt_to_page(p));
&&&&printk(" p = 0x%08x\n", p);
&&&&//在共享内存中写上一个字符串
&&&&strcpy(p, "Hello world!\n");
&&&&return 0;
static void __exit fini(void)
&&&&ClearPageReserved(virt_to_page(p));
&&&&free_pages(p, 0);&&&&
module_init(init);
module_exit(fini);
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define PAGE_SIZE (4*1024)
#define PAGE_OFFSET&&&&&&&&0xc0000000
#define KERNEL_VIRT_ADDR&&&&0xc5e3c000
int main()
&&&&char *buf;
&&&&int fd;
&&&&unsigned long phy_addr;
&&&&fd=open("/dev/mem",O_RDWR);
&&&&if(fd == -1)
&&&&&&&&perror("open");
&&&&phy_addr=KERNEL_VIRT_ADDR - PAGE_OFFSET;
&&&&buf=mmap(0, PAGE_SIZE,
&&&&&&&&PROT_READ|PROT_WRITE, MAP_SHARED,
&&&&&&&&fd, phy_addr);
&&&&if(buf == MAP_FAILED)
&&&&&&&&perror("mmap");
&&&&puts(buf);//打印共享内存的内容
&&&&munmap(buf,PAGE_SIZE);
&&&&close(fd);
&&&&return 0;
阅读(3124) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。Linux 用户态与内核态的交互&&在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通 读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的主要方法之一。它的通信依据是一个对应于进程的标识,一般定为该进 程的 ID。当通信的一端处于中断过程时,该标识为 0。当使用 netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则不同。netlink 套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函 数。《UNIX Network Programming Volume 1 - 3rd Edition》第18章讲到BSD UNIX系统中routing socket的应用,这种套接字是按下面方式生成的:rt_socket = socket(AF_ROUTE, SOCK_RAW, 0);然 后就可以用它跟内核交互,进行网络环境管理的操作,如读取/设置/删除路由表信息,更改网关等等,但书中所列代码只在4.3BSD及以后版本的原始 UNIX系统下可用,Linux虽然实现了AF_ROUTE族套接字,但用法却完全不同。由于网上这方面知识的资料想对匮乏,现对Linux下 routing socket的使用做一介绍。由于我现在在Magic Linux1.0下工作,所以以下的讲解全部基于2.4.10内核。Linux从v2.2开始引入这一机制,因此可以肯定从v2.2到v2.4的内核都是适用的,更新的v2.6我没有试过。Linux下虽然也有AF_ROUTE族套接字,但是这个定义只是个别名,请看/usr/include/linux/socket.h, line 145:#define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */可 见在Linux内核当中真正实现routing socket的是AF_NETLINK族套接字。AF_NETLINK族套接字像一个连接用户空间和内核的双工管道,通过它,用户进程可以修改内核运行参 数、读取和设置路由信息、控制特定网卡的up/down状态等等,可以说是一个管理网络资源的绝佳途径。1 生成所需套接字,并绑定一个sockaddr结构先来看如何生成一个AF_NETLINK族套接字:sockfd = socket(AF_NETLINK, socket_type, netlink_faimly);这里socket_type可选SOCK_DGRAM或SOCK_RAW;因为AF_NETLINK族是面向数据报的套接字,所以不能使用SOCK_STREAM。netlink_family指定要和内核中的哪个子系统进行交互,目前支持:NETLINK_ROUTE 与路由信息相关,包括查询、设置和删除路由表中的条目等。待会儿我们将以这类family举个实际的例子;NETLINK_FIREWALL 接收由IPv4防火墙代码发送的包;NETLINK_ARPD 可以在用户空间进行arp缓存的管理;NETLINK_ROUTE6 在用户空间发送和接收路由表信息更新;还有几种虽然没有实现,但已经有了定义,为以后扩展做好了准备。接下来要给该套接字绑定一个sockaddr结构,实际上是一个sockaddr_nl结构:struct sockaddr_nl {sa_family_t nl_ /*AF_NETLINK*/unsigned short nl_ /* 0 */pid_t nl_ /* 进程pid */u_32 nl_ /* 多播组掩码*/}这个结构一般按照注释填好就可以了,nl_groups我也不知道怎么用,一般填零了,表示没有多播。绑定:bind(sockfd, (struct sockaddr*) &nl, sizeof(nl));2 填充所需数据结构,并通过sendmsg()/send()等函数写到套接字里去到 此为止,与内核通信的准备工作就完成了,下面要做的工作是,选取适当的数据结构进行填充,并作为sendmsg()的参数发送出去,并recv()收到的 消息。这个数据结构就是nlmsghdr,它只是一个信息头,后面可以接任意长的数据,这些数据实际上又是针对某一需求所采用的特定数据结构。先来看 nlmsghdr:struct nlmsghdr {_u32 nlmsg_ /* Length of msg including header */_u32 nlmsg_ /* 操作命令 */_u16 nlmsg_ /* various flags */_u32 nlmsg_ /* Sequence number */_u32 nlmsg_ /* 进程PID */};/* 紧跟着是实际要发送的数据,长度可以任意 */nlmsg_type 决定这次要执行的操作,如查询当前路由表信息,所使用的就是RTM_GETROUTE。标准nlmsg_type包括:NLMSG_NOOP, NLMSG_DONE, NLMSG_ERROR等。根据采用的nlmsg_type不同,还要选取不同的数据结构来填充到nlmsghdr后面:操作 数据结构RTM_NEWLINK ifinfomsgRTM_DELLINKRTM_GETLINKRTM_NEWADDR ifaddrmsgRTM_DELADDRRTM_GETADDRRTM_NEWROUTE rtmsgRTM_DELROUTERTM_GETROUTERTM_NEWNEIGH ndmsg/nda_chcheinfoRTM_DELNEIGHRTM_GETNEIGHRTM_NEWRULE rtmsgRTM_DELRULERTM_GETRULERTM_NEWQDISC tcmsgRTM_DELQDISCRTM_GETQDISCRTM_NEWTCLASS tcmsgRTM_DELTCLASSRTM_GETTCLASSRTM_NEWTFILTER tcmsgRTM_DELTFILTERRTM_GETTFILTER由于情形众多,我从现在开始将用一个特定的例子来说明问题。我们的目的是从内核读取IPV4路由表信息。从上面表看,nlmsg_type一定使用RTM_xxxROUTE操作,对应的数据结构是rtmsg。既然是读取,那么应该是RTM_GETROUTE了。struct rtmsg {unsigned char rtm_ /* 路由表地址族 */unsigned char rtm_dst_ /* 目的长度 */unsigned char rtm_src_ /* 源长度 */ (2.4.10头文件的注释标反了?)unsigned char rtm_ /* TOS */unsigned char rtm_ /* 路由表选取 */unsigned char rtm_ /* 路由协议 */unsigned char rtm_unsigned char rtm_unsigned int rtm_};对于RTM_GETROUTE操作来说,我们只需指定两个成员:rtm_family:AF_INET, rtm_table: RT_TABLE_MAIN。其他成员都初始化为0即可。将这个结构体跟nlmsghdr结合起来,得到我们自己的新结构体:struct {}填充好rt结构之后,还要调整nl结构相应成员的值。Linux定义了多个宏来处理nlmsghdr成员的值,我们这里用到的是NLMSG_LENGTH(size_t len);req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));这将计算nlmsghdr长度与rtmsg长度的和(其中包括了将rtmsg进行4字节边界对齐的调整),并存储到nlmsghdr的nlmsg_len成员中。接下来要做的就是将这个新结构体req放到sendmsg()函数的msghdr.iov处,并调用函数。sendmsg(sockfd, &msg, 0);3 接收数据,并进行分析接下来的操作是recv()操作,从该套接字读取内核返回的数据,并进行分析处理。recv(sockfd, p, sizeof(buf) - nll, 0);其中p是指向一个缓冲区buf的指针,nll是已接收到的nlmsghdr数据的长度。由于内核返回信息是一个字节流,需要调用者检查消息结尾。这是通过检查返回的nlmsghdr的nlmsg_type是否等于NLMSG_DONE来完成的。返回的数据格式如下:-----------------------------------------------------------| nlmsghdr+route entry | nlmsghdr+route entry | .........&-----------------------------------------------------------| 解出route entryV-----------------------------------------------------------| dst_addr | gateway | Output interface| ...............-----------------------------------------------------------可 以看出,返回消息由多个(nlmsghdr + route entry)组成,当某个nlmsghdr的nlmsg_type == NLMSG_DONE时就表示信息输出已经完毕。而每一个route entry由多个rtattr结构体组成,每个结构体表示该路由项的某个属性,如目的地址,网关等等。根据这个示意图我们就能够轻松解析需要的数据了。
阅读(...) 评论()嵌入式(934)
摘要:在进行设备驱动程序,内核功能模块等系统级开发时,通常需要在内核和用户程序之间交换信息。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(内核服务程序:防火墙)VSIptable(用户级程序:规则设置程序);IPSEC(内核服务程序:VPN协议部分)VSIKE(用户级程序:vpn密钥协商处理);当然还包括大量的设备驱动程序及相应的应用软件。这些应用都是由内核级和用户级程序通过相互交换信息来一起完成特定任务的。
信息交互方法
用户程序和内核的信息交换是双向的,也就是说既可以主动从用户空间向内核空间发送信息,也可以从内核空间向用户空间提交数据。当然,用户程序也可以主动地从内核提取数据。下面我们就针对内核和用户交互数据的方法做一总结、归纳。
信息交互按信息传输发起方可以分为用户向内核传送/提取数据和内核向用户空间提交请求两大类,先来说说:
由用户级程序主动发起的信息交互。
用户级程序主动发起的信息交互
A编写自己的系统调用
从前文可以看出,系统调用是用户级程序访问内核最基本的方法。目前linux大致提供了二百多个标准的系统调用(参见内核代码树中的include/asm-i386/unistd.h和arch/i386/kernel/entry.S文件),并且允许我们添加自己的系统调用来实现和内核的信息交换。比如我们希望建立一个系统调用日志系统,将所有的系统调用动作记录下来,以便进行入侵检测。此时,我们可以编写一个内核服务程序。该程序负责收集所有的系统调用请求,并将这些调用信息记录到在内核中自建的缓冲里。我们无法在内核里实现复杂的入侵检测程序,因此必须将该缓冲里的记录提取到用户空间。最直截了当的方法是自己编写一个新系统调用实现这种提取缓冲数据的功能。当内核服务程序和新系统调用都实现后,我们就可以在用户空间里编写用户程序进行入侵检测任务了,入侵检测程序可以定时、轮训或在需要的时候调用新系统调用从内核提取数据,然后进行入侵检测了。
B编写驱动程序
Linux/UNIX的一个特点就是把所有的东西都看作是文件(every thing is afile)。系统定义了简洁完善的驱动程序界面,客户程序可以用统一的方法透过这个界面和内核驱动程序交互。而大部分系统的使用者和开发者已经非常熟悉这种界面以及相应的开发流程了。
驱动程序运行于内核空间,用户空间的应用程序通过文件系统中/dev/目录下的一个文件来和它交互。这就是我们熟悉的那个文件操作流程:open()—— read() —— write() —— ioctl() ——close()。(需要注意的是也不是所有的内核驱动程序都是这个界面,网络驱动程序和各种协议栈的使用就不大一致,比如说套接口编程虽然也有open()close()等概念,但它的内核实现以及外部使用方式都和普通驱动程序有很大差异。)关于这部分的编程细节,请查阅参考资料3、4。
设备驱动程序在内核中要做的中断响应、设备管理、数据处理等等各种工作这篇文章不去关心,我们把注意力集中在它与用户级程序交互这一部分。操作系统为此定义了一种统一的交互界面,就是前面所说的open(),read(), write(),ioctl()和close()等等。每个驱动程序按照自己的需要做独立实现,把自己提供的功能和服务隐藏在这个统一界面下。客户级程序选择需要的驱动程序或服务(其实就是选择/dev/目录下的文件),按照上述界面和文件操作流程,就可以跟内核中的驱动交互了。其实用面向对象的概念会更容易解释,系统定义了一个抽象的界面(abstractinterface),每个具体的驱动程序都是这个界面的实现(implementation)。
所以驱动程序也是用户空间和内核信息交互的重要方式之一。其实ioctl, read,write本质上讲也是通过系统调用去完成的,只是这些调用已被内核进行了标准封装,统一定义。因此用户不必向填加新系统调用那样必须修改内核代码,重新编译新内核,使用虚拟设备只需要通过模块方法将新的虚拟设备安装到内核中(insmod上)就能方便使用。关于此方面设计细节请查阅参考资料5,编程细节请查阅参考资料6。
在linux中,设备大致可分为:字符设备,块设备,和网络接口(字符设备包括那些必须以顺序方式,像字节流一样被访问的设备;如字符终端,串口等。块设备是指那些可以用随机方式,以整块数据为单位来访问的设备,如硬盘等;网络接口,就指通常网卡和协议栈等复杂的网络输入输出服务)。如果将我们的系统调用日志系统用字符型驱动程序的方式实现,也是一件轻松惬意地工作。我们可以将内核中收集和记录信息的那一部分编写成一个字符设备驱动程序。虽然没有实际对应的物理设备,但这并没什么问题:Linux的设备驱动程序本来就是一个软件抽象,它可以结合硬件提供服务,也完全可以作为纯软件提供服务(当然,内存的使用我们是无法避免的)。在驱动程序中,我们可以用open来启动服务,用read()返回处理好的记录,用ioctl()设置记录&#26684;式等,用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系统启用路由功能。类&#20284;的,还有许多内核选项可以直接通过proc文件系统进行查询和调整。
除了系统已经提供的文件条目,proc还为我们留有接口,允许我们在内核中创建新的条目从而与用户程序共享信息数据。比如,我们可以为系统调用日志程序(不管是作为驱动程序也好,还是作为单纯的内核模块也好)在proc文件系统中创建新的文件条目,在此条目中显示系统调用的使用次数,每个单独系统调用的使用频率等等。我们也可以增加另外的条目,用于设置日志记录规则,比如说不记录open系统调用的使用情况等。关于proc文件系统得使用细节,请查阅参考资料7。
D: 使用虚拟文件系统
有些内核开发者认为利用ioctl()系统调用往往会&#20284;的系统调用意义不明确,而且难控制。而将信息放入到proc文件系统中会使信息组织混乱,因此也不赞成过多使用。他们建议实现一种孤立的虚拟文件系统来代替ioctl()和/proc,因为文件系统接口清楚,而且便于用户空间访问,同时利用虚拟文件系统使得利用脚本执行系统管理任务更家方便、有效。
我们举例来说如何通过虚拟文件系统修改内核信息。我们可以实现一个名为sagafs的虚拟文件系统,其中文件log对应内核存储的系统调用日志。我们可以通过文件访问特普遍方法获得日志信息:如
# cat /sagafs/log
使用虚拟文件系统——VFS实现信息交互使得系统管理更加方便、清晰。但有些编程者也许会说VFS 的API接口复杂不容易掌握,不要担心2.5内核开始就提供了一种叫做libfs的例程序帮助不熟悉文件系统的用户封装了实现VFS的通用操作。有关利用VFS实现交互的方法看参考资料。
E: 使用内存映像
Linux通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核中特定部分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一块相同的内存。这样做的直观效果显而易见:内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本无须数据拷贝。而在使用系统调用交互信息时,在整个操作过程中必须有一步数据拷贝的工作——或者是把内核数据拷贝到用户缓冲区,或只是把用户数据拷贝到内核缓冲区——这对于许多数据传输量大、时间要求高的应用,这无疑是致命的一击:许多应用根本就无法忍受数据拷贝所耗费的时间和资源。
我们曾经为一块高速采样设备开发过驱动程序,该设备要求在20兆采样率下以1KHz的重复频率进行16位实时采样,每毫秒需要采样、DMA和处理的数据量惊人,如果要使用数据拷贝的方法,根本无法达成要求。此时,内存映像成为唯一的选择:我们在内存中保留了一块空间,将其配置成环形队列供采样设备DMA输出数据。再把这块内存空间映射到在用户空间运行的数据处理程序上,于是,采样设备刚刚得到并传送到主机上的数据,马上就可以被用户空间的程序处理。
实际上,内存影射方式通常也正是应用在那些内核和用户空间需要快速大量交互数据的情况下,特别是那些对实时性要求较强的应用。Xwindow系统的服务器的虚拟内存区域,就可以被看做是内存映像用法的一个典型例子: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在当前进程(currentprocess)的堆空间上扩展一块新的临时缓冲区,再用put_user将内核数据导出到这个确定的用户空间去。
还记得刚才我们在内核中调用用户程序的过程吗?在那里,我们有一个跳过参数检查的操作,现在有了这种方法,可以另辟蹊径了:我们在当前进程的堆上扩展一块空间,把系统调用要用到的参数通过put_user()拷贝到新扩展得到的用户空间里,然后在调用execve的时候以这个新开辟空间地址作为参数,于是,参数检查的障碍不复存在了。
char * program_path = &/bin/ls& ;
mmm=current-&mm-&
ret = brk(*(void)(mmm&#43;256));
put_user((void*)2,program_path,strlen(program_path)&#43;1);
execve((char*)(mmm&#43;2));
tmp = brk((void*)mmm);
这种方法没有一般性(具体的说,这种方法有负面效应吗),只能作为一种技巧,但我们不难发现:如果你熟悉内核结构,就可以做到很多意想不到的事情!
C: 使用信号:
信号在内核里的用途主要集中在通知用户程序出现重大错误,强行杀死当前进程,这时内核通过发送SIGKILL信号通知进程终止,内核发送信号使用send_sign(pid,sig)例程,可以看到信号发送必须要事先知道进程序号(pid),所以要想从内核中通过发信号的方式异步通知用户进程执行某项任务,那么必须事先知道用户进程的进程号才可。而内核运行时搜索到特定进程的进程号是个费事的工作,可能要遍历整个进程控制块链表。所以用信号通知特定用户进程的方法很糟糕,一般在内核不会使用。内核中使用信号的情形只出现在通知当前进程(可以从current变量中方便获得pid)做某些通用操作,如终止操作等。因此对内核开发者该方法用处不大。
类&#20284;情况还有消息操作。这里不罗嗦了。
总结&&由用户级程序主动发起的信息交互,无论是采用标准的调用方式还是透过驱动程序界面,一般都要用到系统调用。而由内核主动发起信息交互的情况不多。也没有标准的界面,操作大不方便。所以一般情况下,尽可能用本文描述的前几种方法进行信息交互。毕竟,在设计的根源上,相对于客户级程序,内核就被定义为一个被动的服务提供者。因此,我们自己的开发也应该尽量遵循这种设计原则。
1 周明德,保护方式下的80386及其编程,清华大学出版社,1993
2 Robert Love, Linux Kernel Development,SamsPublishing,2003
3 W.Richard Stevens, Advanced Programming in the UNIXEnvironment,Addision Wesley,1992
4 W.Richard Stevens, UNIX Network Programming, Prentic Hall,1998
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,1999
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:681955次
积分:14663
积分:14663
排名:第619名
原创:742篇
转载:520篇
评论:102条
(2)(5)(1)(2)(4)(5)(5)(2)(2)(2)(2)(3)(1)(7)(8)(7)(11)(37)(14)(25)(35)(32)(79)(52)(74)(91)(39)(9)(26)(19)(35)(22)(15)(14)(574)}

我要回帖

更多关于 宫颈囊肿会导致闭经吗 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信