Linux内核编程_linux内核开发工具

2022-11-08 10:07:47 浏览数 (1)

【转载】Linux内核编程与应用编程对比

转载链接1:http://www.arrowapex.cn/archives/66.html

在此之前也不清楚linux内核编程跟用户应用程序编程之间有什么不同,正好这几天做了一点linux模块编程,遇到问题请教朋友并查一些资料,感觉对内核编程和用户应用程序编程的几点不同有了一点体会,就写了下来。

1.linux内核编程和用户应用程序编程最大的不同是,前者是在内核态下运行的,而后者主要在用户态下运行,有时通过一些系统调用切换到内核态下运行,但这时间不会太长。

2.内核编程引进的头文件都在内核源码的include文件夹下,比如我的debian linux 2.6环境下是:/usr/src/linux/include下,而用户应用程序编程引进的头文件都是从开发环境头文件的include文件夹下,比如 我的环境下是:/usr/include下。也就是凡是要include的头文件在内核源码include底下没有的都不能用。

3.要查询一个函数能否在内核编程中用,可以通过http://lxr-itec.uni-klu.ac.at/linux-2.6.4/ident查 (这是针对linux2.6内核,也有针对2.4内核的),如果能查到Defined as a function,那就可以用,否则就不行。

4.举个简单的例子:当socket编程时,用户在应用程序编程时,基本上都用到socket()函数创建一个socket描述符,include的头文 件主要是<sys/socket.h>,<sys/types.h>和<netinet/ip.h>,这几个头文件 在内核源码include下都没有,所有不能用,但内核编程有它自己的一套。内核socket编程时,需要用sock_create()得到一个 socket结构体,如果想跟用户应用程序一样用socket描述符来操作socket,可以再用sock_map_fd()新建一个对应的描述符,而且 需要时可以通过sockfd_lookup(),实现通过描述符查找对应的socket结构体。其实socket函数内核实现时就是先有 sock_create(),再有sock_map_fd()。

转载链接2:http://blog.chinaunix.net/uid-23629988-id-3993750.html

目前,内核编程给我最大的感触是程序的执行流比较多,并发逻辑比应用编程要复杂的多。这个“执行流”是我杜撰的名词,但基本上可以表达我的意思。应用编程中,谈到并发,无非是多进程多线程,一般对共享资源使用锁保护就基本上没有问题了。一个线程可以视为一个执行流,除非被信号打断,该线程都是按照代码顺序执行。也就是说,我们在应用层编写的代码和业务逻辑,只会被我们定义的线程或者进程执行。信号处理函数一般情况下,都会写的比较简单,大多是设置标志位。而在内核中,有中断,软中断,定时器,还有系统调用等诸多会涉及业务逻辑的执行流。由于内核自身的特性,对共享资源的保护,也要斟酌使用不同的手段。

对于某些共享资源,有时候使用spin_lock进行保护,但随着功能需求的增加。需要加入与用户空间的交互,在代码实现上,有时候会直接调用现成的代码。结果那些代码中对共享资源的保护使用的是spin_lock,而数据包转发的业务逻辑代码都是运行在软中断中,结果造成了死锁。

我还修正过一些别人写的bug。其中有一个bug,给我留下的印象也很深。当时产品总是不定期重启,而我们这里又无法重现。我当时刚刚接触已有的产品代码,对于这种重启的bug,在不能重现的情况下,我选择review代码这看似笨重却非常有效的方法。还好产品的关键功能的代码量不算多,花了2天的时间把大部分代码读懂,同时顺手修正了一些有可能造成重启的问题。客户升级以后,大部分没有问题了,但还是有个别重启的现象,那么这意味着还有漏网之鱼。当时我基本已经把关键流程全部理通了,修正这个问题的流程很有意思。我靠在椅背上,眼睛望向天花板,心里把数据包从入口到出口的流程走了一遍,并考虑所有的分支和特殊情况。然后Get it!大概花了不到15分钟的时间。然后看代码,验证自己的猜想。

造成重启问题的原因如下:因为某种业务逻辑需求,申请了一个动态结构,并设置了定时器超时,到期释放。当业务逻辑访问这个动态结构时,会刷新它的访问时间,延长其生命周期。但是在某些情况下,可能需要提前删除这个结构时,会调用del_timer删除定时器,然后释放内存。看到这样的代码,我立刻就怀疑当del_timer删除定时器时,如果该定时器正在处于执行阶段,怎么办?上网查询了一下,果不其然,del_timer返回时不能保证没有正在执行的定时器。那么当定时器还在执行的时候,这个动态结构就被释放了,定时器也会随着动态结构的释放而释放。这样的代码肯定是有问题的。如何解决这个问题呢?第一个念头,就是保证同步删除定时器。根据搜索的结果,可以使用del_timer_sync。然而我仔细一想,这样仍然有问题。本来这个动态结构是使用定时器来释放,但是这里确实强制释放,那么即使使用了del_timer_sync停掉了定时器,那么这时定时器可能已经完成了超时,并释放了动态结构。这时再强制释放等于double free。同时del_timer_sync还有一个问题,这种同步操作,必然带来性能上的下降。所以最终的选择方案是增加一个标志,在强制删除时,将标志置位,保证释放操作只有一个执行者,同时引入引用计数。

最近,为了优化性能,我也引入了两个bug,还好都及时修正了。bug造成的原因,还是由于对linux内核本身不太熟悉造成的。其中一个最近发现的bug,居然花费我一天的时间才找到原因。当使用某个应用程序时,会造成内核崩溃。起初我一度甚至怀疑这是内核的bug——虽然我觉得不大可能,于是我就开始验证排除这个可能。因为不开这个应用程序时,内核模块完全没有问题。打开应用程序时,内核就会崩溃。而这个应用程序跟内核模块,完全没有任何的交互。后来分析这个应用程序的代码,与网络关系紧密的就是注册了一个PF_PACKET的socket,用于抓取所有网卡的数据包。于是我去查看了相关代码,当PF_PACKET的接受包函数,会检查skb是否被共享,如果是共享的就clone一份,ip_rcv入口处也有类似的代码。那么当该应用程序运行时,就意味着ip_rcv会检测发现这个skb是共享的,于是就会clone一份。这就是该应用程序运行时与不运行时,内核处理数据包流程的最大区别。于是,我修改ip_rcv的代码,不再坚持skb是否是共享,而是直接clone。果然,内核在不启动该应用程序时候,依然崩溃。这样就证明了,问题还是出自自己的代码处,而且是与skb相关的代码。经过一番查找,最终找到了根本原因。

我在netfilter的两个hook点上,注册了两个hook函数。前一个钩子函数,初始化了一些per cpu变量,后一个钩子函数,简单检测了per_cpu->skb与hook的参数skb如果是相等的情况,就不再初始化,直接使用per cpu的变量了。造成问题的原因就在于,在有skb_clone调用时,不同hook调用时,skb->data发生了变化。第二个hook位置,skb->data指向的内存与第一个hook处不一致。但是skb_clone本身并不会造成这样的结果。这说明在netfilter的不同hook之间,当skb被clone了,会重新分配skb的数据空间——具体是哪处代码,我暂时没有找到。

这个bug让我吸取了一个教训。内核编程,由于你不可能熟悉linux内核所有的代码,所有在编程中,要想着,除非内核已经明确定义的行为,才能放心使用。不是明确定义的行为,不能根据平时简单的测试,就确信没有问题。如上面的例子,内核从来没有说过两个hook点之间,skb是一样的,skb的数据空间即skb->data是一样的。

对于在linux内核实现网关的某些功能时,我发现,虽然linux已经提供了很多现成的东西,可以保证快速开发。但是内核本身架构是一个通用计算机,不是专门针对网络处理的。其网络模块的架构本身有很多弊端和不便处,尤其是对比我前公司的产品架构——该架构看上去挺简单的,但越体会越能感觉,简单就是美!就是效率——一个是产品效率即性能,还一个是开发效率。

Note: 其实做网络设备的,做到高性能的产品,大部分架构都比较相似,但在细微处的不同,造就了不同的产品性能。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/184480.html原文链接:https://javaforall.cn

0 人点赞