DongHao: 06 2010存档
去年公司电话紧张,于是收了我的座机,让我和边上的同事(赵明华)共用一部。
于是我在旺旺上的签名写成:
”分机:70663(请门口赵大爷叫一声)“
我的旺旺名字(也就是花名)是”三百“。
结果,朋友不久就来了个消息:
“你们公司三百个人用一个电话号码?!“
“你们公司三百个人用一个电话号码?!“
我到26层去查故障,顺便上个厕所。一进厕所就撞见苏宁。
苏:你搬下来了?
我:没有啊
苏:那你是专程来这里上厕所的?
我:是的
苏:难得啊。我代表26层全体马桶欢迎你。
我:嗯,你来代表,你是他们的“桶帅”嘛。马桶里面最帅的。
linux内核里对于要发送到网络上去的包,是通过sk_buffer结构组织的,这个结构主要就是包含一个指针和一个长度,指针指向要发送数据的开头处,长度当然就是要发送的长度。当我们使用send系统调用时,内核要把用户空间的数据拷往内核空间在(创建一个副本),然后构造sk_buffer指向这个内核空间里的副本,接着把sk_buffer排到队列里去,等待网卡处理。
后来诞生了sendfile,sendfile会直接将sK_buffer指向文件在内存中的cache,这样就节省了一次拷贝。
这里有个注意事项:sendfile不适合频繁改动的文件。假如你调用了一次sendfile,sk_buffer指向文件的某块cache,然后被放入队列;接着,你改动了文件的内容,那么排在发送队列里的那个sk_buffer指向的cache内容就变化了!那发出去的消息就是你改动后的数据。在不知道sk_buffer是否已发往网络的情况下,一边改动文件一边调用sendfile会造成发送出去的内容不确定。所以,sendfile更适合用在静态文件的场合。
当然,如果有个办法能查询sk_buffer是否真的发送出去了(而不是呆在发送队列里),那么“改一次文件内容,调一次sendfile;再改一次文件内容,再调一次sendfile”也是一个不错的节省拷贝时间的发送策略。
做一个文件系统(姑且叫它“蝗虫文件系统“),需要在进程退出时自动删除它打开的文件。另外此文件还支持poll(我自己做的,一般的文件系统不支持),支持poll当然需要等待队列,所以我把一个等待队列放在文件对应的struct inode里,实现该文件struct file_operations里的flush方法,在flush时删除文件并释放该等待队列(kfree)。
在把多个文件句柄加入epoll里的时候,这些文件句柄对应的inode上的等待队列要被epoll挂在一个数据结构上(struct eppoll_entry),当进程退出,这些等待队列当然要从数据结构上拿掉,但是,这些等待队列已经被我kfree了,所以panic。看来把kfree放在flush是不行了,那放哪儿?有哪个调用是比eventpoll_release发生的更晚的?有!
这样做似乎一切顺利,直到应用程序开始使用epoll:先在蝗虫文件系统里创建几个文件,再把它们的fd放入epoll(epoll_ctl),然后进程退出,如此多来几次,内核就panic了。还好我用的是QEMU,可以清楚看见在什么地方panic的,结果是 __fput --> eventpoll_release --> eventpoll_release_file --> ep_remove --> ep_unregister_pollwait --> remove_wait_queue,看看ep_unregister_pollwait的代码:
[fs/eventpoll.c --> ep_unregister_pollwait]
1109 static void ep_unregister_pollwait(struct eventpoll *ep, struct epitem *epi)
1110 {
1111 int nwait;
1112 struct list_head *lsthead = &epi->pwqlist;
1113 struct eppoll_entry *pwq;
1114
1115 /* This is called without locks, so we need the atomic exchange */
1116 nwait = xchg(&epi->nwait, 0);
1117
1118 if (nwait) {
1119 while (!list_empty(lsthead)) {
1120 pwq = list_entry(lsthead->next, struct eppoll_entry, llink);
1121
1122 ep_list_del(&pwq->llink);
1123 remove_wait_queue(pwq->whead, &pwq->wait);
1124 kmem_cache_free(pwq_cache, pwq);
1125 }
1126 }
1127 }
[fs/file_table.c --> __fput]
153 void fastcall __fput(struct file *file)
154 {
155 struct dentry *dentry = file->f_dentry;
156 struct vfsmount *mnt = file->f_vfsmnt;
157 struct inode *inode = dentry->d_inode;
158
159 might_sleep();
160
161 fsnotify_close(file);
162 /*
163 * The function eventpoll_release() should be the first called
164 * in the file cleanup chain.
165 */
166 eventpoll_release(file);
167 locks_remove_flock(file);
168
169 if (file->f_op && file->f_op->release)
170 file->f_op->release(inode, file);
171 security_file_free(file);
172 if (unlikely(inode->i_cdev != NULL))
173 cdev_put(inode->i_cdev);
174 fops_put(file->f_op);
175 if (file->f_mode & FMODE_WRITE)
176 put_write_access(inode);
177 file_kill(file);
178 file->f_dentry = NULL;
179 file->f_vfsmnt = NULL;
180 file_free(file);
181 dput(dentry);
182 mntput(mnt);
183 }
struct file_operations里的release方法就是在epoll释放之后调用的,所以应该把kfree挪到release里去执行。
照此改之,没有panic了。
不知道是我听错了还是别人说错了,我一直以为 tail -f 是通过poll实现的,而每个ext2(或ext3)文件都支持poll。当文件有新数据时,针对此文件的poll会返回结果。
昨天想参考kernel是怎么让设备实现poll的,看了ext2文件系统,发现它没提供poll接口。于是自己写了个程序来试试,对着一个ext2文件epoll,结果,即使文件里的数据增加了,epoll_wait也没有任何反应。就socket支持poll,硬盘文件不支持的。那tail -f是怎么实现的?下了coreutils一看,喔,其实是每1秒检查一遍文件,看大小有否变化,如果有,则异步的read该文件,取得新数据,打印到终端......
昨天想参考kernel是怎么让设备实现poll的,看了ext2文件系统,发现它没提供poll接口。于是自己写了个程序来试试,对着一个ext2文件epoll,结果,即使文件里的数据增加了,epoll_wait也没有任何反应。就socket支持poll,硬盘文件不支持的。那tail -f是怎么实现的?下了coreutils一看,喔,其实是每1秒检查一遍文件,看大小有否变化,如果有,则异步的read该文件,取得新数据,打印到终端......
想想也对,tail命令出现的很早,那时候说不定还没有poll呢,它就用这个简单办法不也解决了问题么。