Category: 程序设计

如何在内核空间对文件进行操作

By ganquan, 2010年05月6日 09:52

在内核空间中,是不能直接使用open, read, write之类的系统调用直接访问文件的。那怎么在驱动中访问一个文件呢?

我们知道在内核中用struct file来表示一个打开的文件,file结构体中有一个字段f_op,它是struct file_operation的指针。有了这个指针,就可以对文件进行访问了。
代码如下:

struct file *filp;
mm_segment_t fs;

filp = filp_open("/path/to/your/file", O_RDWR, 0);

if(!IS_ERR(filp))
{
    fs=get_fs();
    set_fs(KERNEL_DS);

    filp->f_op->read(...);
    filp->f_op->write(...);

    set_fs(fs);

    filp->f_op->mmap(...);

    /* ... */
}

filp_close(filp, NULL);


f_op->read()和f_op->write()的第二个参数是char __user *,也就是说是用户空间的地址,所以在驱动中直接传递kernel空间的指针,这两个函数都会返回失败-EFAULT。因此,在f_op->read()和f_op->write()之前需要使用void set_fs(mm_segment_t fs);来改变kernel对内存地址检查的处理方式。
该函数的参数fs只有两个取值:USER_DS,KERNEL_DS,分别代表用户空间和内核空间,默认情况下,kernel取值为USER_DS。所以可以用set_fs(KERNEL_DS);来让这个两个函数可以接受内核空间的内存。在内核空间中其他用__user修饰的参数,需要用kernel空间的内存代替时,都可以用类似的方法。
另外一个常用的函数f_op->mmap()则不需要设置fs,因为它的参数中不需要用户空间的内存。事实上,在struct file_operation中,只有read和write函数需要用户空间的内存,其他函数都不需要设置fs。
EOF

判斷一個IPV4地址是否有效

By ganquan, 2010年03月7日 01:37

給一個xxx.xxx.xxx.xxx的字符串,判斷該串是否為合法的IP格式。

不要去耍酷用正則表達式,不要去絞盡腦汁寫一堆帶N多bug的C代碼然後覺得自己的代碼效率還可以。

實際生產中,只用這個方法:

bool  IsValidIP(const char*  ip)
{
    return inet_addr(ip) == -1 ? false : true;
}

gtk中menu的item同时设置label和icon

By ganquan, 2009年12月8日 23:36

我要同时设置label和使用STOCK中的icon,其实很简单,只要调用gtk_image_menu_item_set_image( )函数就可以了。

这个函数的原型是
void gtk_image_menu_item_set_image (GtkImageMenuItem *image_menu_item, GtkWidget *image);

这个函数这样调用比较方便:

menu_item = gtk_image_menu_item_new_with_label (“Label you want to add”);
gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM(menu_item), gtk_image_new_from_stock (GTK_STOCK_ID, GTK_ICON_SIZE_MENU));

起先没走对路子,折腾死人。

程序员八荣八耻

By ganquan, 2009年11月19日 23:03

今天无意见到,风趣幽默又富含经验,虽然是讲Python的,但是所有编程语言都是相通的,好好学习学习。

程序员八荣八耻

以动手实践为荣 , 以只看不练为耻;

以打印日志为荣 , 以单步跟踪为耻;

以空格缩进为荣 , 以制表缩进为耻;

以单元测试为荣 , 以人工测试为耻;

以模块复用为荣 , 以复制粘贴为耻;

以多态应用为荣 , 以分支判断为耻;

以Pythonic为荣 , 以冗余拖沓为耻;

以总结分享为荣 , 以跪求其解为耻;

TCP协议的TIME_WAIT状态详解

By ganquan, 2009年09月17日 14:09

原创文章,转载请注明出处:www.imganquan.org

我发现有个公司面试的时候问了对TCP协议比较细节的地方,那就写一下帮助自己加深理解和记忆。在开始说TIME_WAIT状态之前,要知道TCP协议是如何关闭连接的。
很多人对TCP协议的三次握手都很熟悉(不知道的可以参考我以前的帖子),因为学校的垃圾考试都爱考三次握手,但是很多知道三次握手的人都对TCP协议是如何关闭连接不了解。不说废话了,TCP关闭连接过程如下图,寡人用photoshop画的,凑合看一下:
tcpclose
为了方便描述,我给这个TCP连接的一端起名为Client,给另外一端起名为Server。上图描述的是Client主动关闭的过程,FTP协议中就这样的。如果要描述Server主动关闭的过程,只要交换描述过程中的Server和Client就可以了,HTTP协议就是这样的。

描述过程:
Client调用close()函数,给Server发送FIN,请求关闭连接;Server收到FIN之后给Client返回确认ACK,同时关闭读通道(不清楚就去看一下shutdown和close的差别),也就是说现在不能再从这个连接上读取东西,现在read返回0。此时Server的TCP状态转化为CLOSE_WAIT状态。
Client收到对自己的FIN确认后,关闭 写通道,不再向连接中写入任何数据。
接下来Server调用close()来关闭连接,给Client发送FIN,Client收到后给Server回复ACK确认,同时Client关闭读通道,进入TIME_WAIT状态。
Server接收到Client对自己的FIN的确认ACK,关闭写通道,TCP连接转化为CLOSED,也就是关闭连接。
Client在TIME_WAIT状态下要等待最大数据段生存期的两倍,然后才进入CLOSED状态,TCP协议关闭连接过程彻底结束。

以上就是TCP协议关闭连接的过程,现在说一下TIME_WAIT状态。
从上面可以看到,主动发起关闭连接的操作的一方将达到TIME_WAIT状态,而且这个状态要保持Maximum Segment Lifetime的两倍时间。为什么要这样做而不是直接进入CLOSED状态?

原因有二:
一、保证TCP协议的全双工连接能够可靠关闭
二、保证这次连接的重复数据段从网络中消失

先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。

再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

各种协议都是前人千锤百炼后得到的标准,规范。从细节中都能感受到精巧和严谨。每次深入都有同一个感觉,精妙。

TCP: time wait bucket table overflow

By ganquan, 2009年09月16日 02:28

redhat网站查到下面的信息,说是因为内存不够的原因。我觉得这个可以当作出现这个问题的解释,但是却解释得不够“完美”,我仍旧还在疑惑中:如果是是因为内存不够的原因,那么在每次测试之前,只要保证机器状态一样,那么TCP: time wait bucket table overflow这条信息的输出次数就应该是一样的,或者差的不是很多,因为每次有一个socket来了,就给一个数据结构,直到内存用完输出TCP: time wait bucket table overflow信息,但是我测试的结果是差异不小,这就让我感觉疑惑了,莫非每次分配的数据结构不一样大?这不可能。我每次单独测试都是重启开发板,然后不运行任何其他程序就进行测试的,这样应该可以保证每次测试机器的状态不是差的很多吧。

The “TCP: time wait bucket table overflow” message shows when the kernel is unable to allocate a data structure to put a socket in the TIME_WAIT state.

This is happening according to linux/net/ipv4/tcp_minisocks.c:

if (tcp_tw_count < sysctl_tcp_max_tw_buckets)
tw = kmem_cache_alloc(tcp_timewait_cachep, SLAB_ATOMIC);

if(tw != NULL) {
(..)
} else {
/* Sorry, if we’re out of memory, just CLOSE this
* socket up. We’ve got bigger problems than
* non-graceful socket closings.
*/
if (net_ratelimit())
printk(KERN_INFO “TCP: time wait bucket table overflow\\n”);
}

This problem is more likely to happen on systems creating a lot of TCP connections at a fast pace. RFC 793 decided that those sockets should stay in the TIME_WAIT state for 2*MSL (Maximum Segment Life), but the Linux implementation seems to make the TIME_WAIT state last for 1 minute.

Monitor the resources used by those time wait buckets by watching:

# cat /proc/slabinfo | grep tcp_tw_bucket

The size of the time wait bucket can be adjusted by writing to /proc/sys/net/ipv4/tcp_max_tw_buckets. However, the link between the client and the server cannot cause packets to arrive out of order, then the TIME_WAIT state can be skipped and sockets can be recycled immediately. Socket recycling can be configured in /proc/sys/net/ipv4/tcp_tw_recycle, but check with your network administrator to verify whether it’s safe to do so.

服务器性能

By ganquan, 2009年09月15日 23:34

时间好快啊,一眨眼就晚上11点过了。赶紧写今天的日记。

今天写了一个超级超级简陋的web服务器,采用epoll多线程的方式:主线程负责accpet,子线程处理链接。问题出在测试环节:

在笔记本上用ab模拟50个并发连接,10000次请求,进行测试,一切OK。可是移植到mini2440开发板上去运行,测试参数不变,就出现这样的输出(截取一部分)
TCP: time wait bucket table overflow
TCP: time wait bucket table overflow
__ratelimit: 1745 callbacks suppressed
TCP: time wait bucket table overflow
TCP: time wait bucket table overflow

我第一反应是服务器连接队列太小,我立刻改大了再测试,还是有问题。我又找了一个别人写的单进程服务器移植到mini2440开发板上来测试,一样有这样的输出。但是这些服务器在笔记本上测试都没有这种问题,很明显是硬件处理能力跟不上的原因。

索性用开发板上自带的boa服务器来测试,靠,问题更加严重,出现好多好多这样的输出。

不解。回头一定要搞明白这个问题。

性能测试结果对比如下,都是在mini2440开发板上进行测试:

1.epoll+pthread服务器,处理10000次请求总共耗时22.341秒,平均每秒处理447.61个请求,测试结果性能最好
2.单进程服务器,处理10000次请求总共耗时28.922秒,平均每秒处理个345.75请求,测试结果性能最差
3.mini2440系统里面自带的boa服务器,处理10000次请求总共耗时26.739秒,平均每秒处理个373.98个请求,测试性能第二

从结果来看多线程方式比单进程的处理方式要好一点,估计boa性能略差的原因是因为boa比较完善,支持动态网页。而另外两个都只支持静态页面。不过这样分析也不对,因为测试时我没有让他们输出任何内容,仅仅是一个报文头而已。所以其实这三个服务器的测试基础是一样的。后面完善了服务器之后再测试一下。

一次DoS攻击

By ganquan, 2009年09月13日 00:47

甲流了,封校了,在宿舍憋了一天。晚上学校孝敬我的饭菜没有拍照,只好明天再拍照纪念。
白天又蹉跎了,唉。。。

TCP协议的3次握手过程如下图所示(朕用photoshop随便画的,将就看一下):

tcp_shakehand

罗嗦一下:客户端发送SYN,服务器收到后发送对应的ACK 和自己的SYN,客户端收到对自己的SYN确认后,发送对服务器SYN的确认,客户端完成建立连接。服务器收到客户端对自己的SYN的确认,整个TCP连接建立完成。

如果要进行一次DoS攻击,则可以在3次握手的过程中进行破坏,破坏方法如下图:

DoS

利用原始socket修改源地址,使得服务器把ACK发送到一个不存在的地址,然后服务器就会傻逼一样等待这个不存在的地址的应答信息,那就一直等吧,等到地老天荒去。但是服务器还没有这个能耐等到地老天荒海枯石烂,服务器的未完成连接队列长度是有限的,所以当那些不存在的地址把这个队列霸占之后,合法的主机再来连接服务器就会被deny。

以上就是DoS攻击的原理。
下面开始攻击:

目标服务器:我的mini2440开发板,上面运行了一个http服务器,端口80,地址是192.168.0.222
在我的主机上访问这个地址是完全正常的,如下图:

2009-09-12-235327_432x236_scrot

然后开始攻击:

2009-09-12-235534_482x125_scrot

可以看到当前发送速率如下:

2009-09-12-235648_698x138_scrot

发送一段时候后,再次访问http://192.168.0.222,结果如下:

2009-09-13-000325_473x376_scrot

可以用tcpdump抓包看到,发送的数据包源地址都是随机伪造的:

21:49:04.958398 IP 165.73.220.37.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.958402 IP 182.200.63.94.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.958406 IP 218.31.87.38.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966416 IP 132.23.33.98.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966424 IP 200.16.93.88.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966430 IP 171.242.43.18.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966436 IP 78.39.31.69.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966442 IP 34.132.193.5.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966448 IP 55.211.162.46.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966454 IP 89.112.75.6.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0
21:49:04.966460 IP 215.57.37.36.8888 > 192.168.0.222.www: S 1732610923:1732610923(0) win 0

代码过程如下:
1.生成原始socket
socket(AF_INET,SOCK_RAW,IPPROTO_TCP)
2.设置socket选项
setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
设置了IP_HDRINCL选项,需要手动为IP数据包填写IP数据包首部内容。
3.填写两个结构体:struct ip 和 struct tcphdr
4.伪造源地址并且发送给服务器
ip->ip_src.s_addr = random();
sendto(sockfd,buffer,head_len,0,(struct sockaddr *)&addr,sizeof(struct sockaddr));

一点了,睡觉去。

volatile关键字

By ganquan, 2009年08月23日 11:47

用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。在嵌入式系统中,用来作为硬件动作的结果。

例如,通过一个串口接收到一个字符,结果串口状态寄存器更新,这完全在程序流程之外发生。最好就把该寄存器声明为volatile,编译器不会试图优化一个volatile寄存器,而是每次重载它。所以在嵌入式设备的程序中,将所有外设寄存器声明为volatile是一个好习惯。

[转载]linux下的共享库(动态库)和静态库

By ganquan, 2009年08月4日 15:24

1.什么是库
在windows平台和linux平台下都大量存在着库。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
由于windows和linux的本质不同,因此二者库的二进制是不兼容的。
本文仅限于介绍linux下的库。

2.库的种类
linux下的库有两种:静态库和共享库(动态库)。
二者的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。

3.库存在的意义
库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

4.库文件是如何产生的在linux下
静态库的后缀是.a,它的产生分两步
Step 1.由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表
Step 2.ar命令将很多.o转换成.a,成文静态库
动态库的后缀是.so,它由gcc加特定参数编译产生。
例如:
$ gcc -fPIC -c *.c $ gcc -shared -Wl,-soname, libfoo.so.1 -o libfoo.so.1.0 *.

5.库文件是如何命名的,有没有什么规范
在linux下,库文件一般放在/usr/lib /lib下,
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号

6.如何知道一个可执行程序依赖哪些库
ldd命令可以查看一个可执行程序依赖的共享库,
例如# ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
可以看到ln命令依赖于libc库和ld-linux库

7.可执行程序在执行的时候如何定位共享库文件
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径
此时就需要系统动态载入器(dynamic linker/loader)
对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目录找到库文件后将其载入内存

8.在新安装一个库之后如何让系统能够找到他
如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。
如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下
1.编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
2.运行ldconfig,该命令会重建/etc/ld.so.cache文件

Panorama Theme by Themocracy