多线程同时操作一个epoll_fd
为什么选择多线程?而不是多进程?
比起多进程来说,线程间通信简单(全局变量就可以了),而多进程之间的通信相对而言更繁琐一些,呵呵...
我们的问题如何产生的?问题的根本原因是什么?
事情是这样的,模块之间需要通信,我们用了openwrt的开源代码ubus做消息转发
在我们的每个需要通信的模块中创建了一个线程(ubus thread)循环接收ubusd转发而来的消息(用的是libubox提供的API uloop_run)
在模块需要发送时,主线程调用ubus的消息发送接口(ubus_send_event)
大多数情况是这样的:
1. 模块(作为一个后台进程)启动时装载了libubox.so(注意,这个库的载入时加入了一个全局变量 int epoll_fd)
2. 随即就创建线程ubus thread循环等待接收消息,ubus thread的动作大致如下:
a. 建立和ubusd通信的socket(记为fd1)
b. 随即调用epoll_ctl将fd1加入到epoll_fd中
c. 然后调用epoll_wait....
3. 主线程执行,在有需要的时候,调用ubus_send_event发送
实际上这个库接口做的动作是这样的:
a. 建立和ubusd通信的socket(记为fd2)
b. 将fd2加入到全局变量epoll_fd,然后执行epoll_wait(epoll_fd没有创建的话,先调用epoll_create)
那么问题来了,一般都是ubus thread先执行,然后阻塞在epoll_wait中,主线程在ubus thread阻塞时将fd2加入到同一个epoll_fd中....
也就是两线程在操作同一个epoll_fd
结果fd2状态发生变化时,ubus thread被唤醒执行了,然后就乱套了......
尝试了一下,在主线程发送的时候去fork子进程,子进程完成发送的动作,反而更悲剧了,整个进程崩掉了....
为啥呢,因为fork出来的子进程没有执行exec切换进程执行上下文,完全是一个父进程的拷贝,那个出问题的epoll_fd也被它拿着了
然后子进程新建了一个和ubusd通信的fd2,加入到这个epoll_fd中,然后执行epoll_wait
结果fd2发生状态变化时,内核唤醒了ubus thread,尼玛,ubus thread哪里知道fd2的存在,结果非法地址访问,Segmentfault....呵呵...
最后,最后,因为我们这群小蜜蜂实在是飞得太低,所以,我们借助了ubus源码附带提供的ubus命令行工具
在主线程发送时 system("ubus send %s ", message)
这就相当于在主线程发送时fork子进程,然后子进程的执行环境切换到ubus工具,ubus工具会调用ubus_send_event
另一种解决方案:
还是在主线程发送的时候去fork子进程,子进程一上来直接去循环关闭从0-1024的fd,哈哈哈哈...(不知道会不会有什么其他的影响,不过我觉得应该可以)
for (i = 0; i < FDSET; i++)
close(i)
这样做是强迫子进程再次去调用epoll_create,而不是复用从父进程那里继承而来的epoll_fd。
所以,用了开源库,加上我们独特的线程设计,给自己整了个大坑....