关于
我的项目
热度排行
- [转] 宫崎骏用动漫教给我们的人生哲理,每一句都能说到心里! - (日期:[八月 24, 2013] 点击:[53,157])
- Google 网页爬虫报告无法连接站点解决办法 - (日期:[七月 20, 2014] 点击:[38,630])
- 架设Tiny Tiny RSS(TTRSS)阅读器,找回Google Reader! - (日期:[九月 27, 2013] 点击:[27,762])
- SkyDrive、DropBox和Google Drive三大公有云存储服务对比 - (日期:[六月 25, 2013] 点击:[25,562])
- 升级到至强E5440后,与i5 CPU笔记本性能对比 - (日期:[二月 18, 2014] 点击:[23,695])
- 公钥私钥加密解密数字证书数字签名详解 - (日期:[四月 19, 2014] 点击:[22,955])
- 本站建站技术合集 - (日期:[九月 20, 2013] 点击:[22,481])
- 使用OpenerDNS解决无法访问Google的问题 - (日期:[七月 5, 2014] 点击:[21,774])
- WordPress博客添加“返回顶部”按钮 - (日期:[七月 14, 2013] 点击:[21,189])
- Linux文件系统基础之inode和dentry - (日期:[三月 13, 2015] 点击:[20,161])
- 云存储中的HTTP鉴权算法分析 - (日期:[二月 7, 2014] 点击:[18,636])
- 存储基础知识之——磁盘阵列原理及操作实战 - (日期:[二月 9, 2014] 点击:[17,478])
- 精选37条强大的常用linux shell命令组合 - (日期:[九月 4, 2013] 点击:[17,425])
- DNS原理、架构和配置详解 - (日期:[九月 6, 2013] 点击:[16,797])
- Netty和Jetty的Java NIO 网络框架模型分析 - (日期:[七月 13, 2013] 点击:[16,329])
- CoreOS 初识之安装 - (日期:[十一月 16, 2014] 点击:[16,162])
- Windows与Linux文件系统互访的几种方法 - (日期:[八月 21, 2014] 点击:[15,725])
- Dijkstra算法求解最短路径分析 - (日期:[七月 12, 2014] 点击:[14,921])
- NAS解决方案实现多媒体文件共享播放 - (日期:[十二月 21, 2014] 点击:[13,899])
- 简介 - (日期:[九月 1, 2012] 点击:[13,746])
- 如何编程实现 2 + 2 = 5? - (日期:[六月 2, 2014] 点击:[13,266])
- 搭建了一个iNews程序 - (日期:[十月 15, 2013] 点击:[13,233])
- 2014年9月曝出的Bash ShellShock漏洞简析 - (日期:[九月 26, 2014] 点击:[13,134])
- 彻底解决WordPress博客垃圾评论的问题 - (日期:[八月 5, 2013] 点击:[13,080])
- 如何使用1M的内存排序100万个8位数 - (日期:[三月 27, 2014] 点击:[12,551])
- 全部日志列表 - (日期:[十一月 11, 2012] 点击:[12,316])
- 关于回调函数和this指针探讨 - (日期:[八月 24, 2014] 点击:[12,206])
- 给定一个long型常量,其值为x,给定long型变量a,要求a & x 的取值集合 - (日期:[九月 8, 2012] 点击:[11,695])
- WordPress建站必备实用插件 - (日期:[八月 7, 2014] 点击:[11,357])
- Amazon 云计算业务全面介绍 - (日期:[三月 9, 2014] 点击:[11,263])
分类目录
文章归档
- 2024年四月 (1)
- 2024年二月 (1)
- 2023年九月 (1)
- 2023年一月 (1)
- 2022年十月 (1)
- 2022年八月 (2)
- 2022年四月 (1)
- 2022年三月 (1)
- 2021年十二月 (2)
- 2021年十月 (2)
- 2021年九月 (1)
- 2021年八月 (1)
- 2021年五月 (1)
- 2021年三月 (2)
- 2021年一月 (2)
- 2020年十二月 (5)
- 2020年十一月 (2)
- 2020年十月 (2)
- 2020年九月 (1)
- 2020年八月 (5)
- 2020年七月 (2)
- 2019年九月 (1)
- 2018年八月 (1)
- 2018年七月 (1)
- 2018年六月 (1)
- 2018年五月 (1)
- 2018年三月 (1)
- 2018年二月 (1)
- 2018年一月 (2)
- 2017年十二月 (3)
- 2017年十月 (4)
- 2017年九月 (1)
- 2017年七月 (1)
- 2017年六月 (1)
- 2016年十二月 (1)
- 2016年十月 (1)
- 2016年九月 (1)
- 2016年七月 (2)
- 2016年六月 (1)
- 2016年二月 (3)
- 2015年十二月 (3)
- 2015年十一月 (2)
- 2015年十月 (1)
- 2015年八月 (2)
- 2015年七月 (4)
- 2015年六月 (1)
- 2015年三月 (2)
- 2015年二月 (1)
- 2015年一月 (4)
- 2014年十二月 (2)
- 2014年十一月 (2)
- 2014年十月 (5)
- 2014年九月 (8)
- 2014年八月 (11)
- 2014年七月 (17)
- 2014年六月 (7)
- 2014年五月 (15)
- 2014年四月 (16)
- 2014年三月 (14)
- 2014年二月 (5)
- 2013年十二月 (5)
- 2013年十一月 (3)
- 2013年十月 (13)
- 2013年九月 (13)
- 2013年八月 (13)
- 2013年七月 (9)
- 2013年六月 (8)
- 2013年五月 (1)
- 2013年三月 (3)
- 2013年一月 (1)
- 2012年十一月 (1)
- 2012年九月 (12)
- 2012年八月 (3)
- 2011年二月 (1)
- 2009年三月 (1)
- 2009年二月 (1)
- 2008年十一月 (1)
- 2008年六月 (1)
- 2008年四月 (1)
- 2008年三月 (1)
分类目录: 软件技术
[转] 高并发系统设计
一、服务器内部设计
服务器设计涉及Socket的阻塞/非阻塞,操作系统IO的同步和异步(之前被人问到过两次。第一次让我说说知道的网络模型,我说ISO模型和TCP/IP模型,结果被鄙视了。最后人说了解linux epoll吗?不了解呀!汉,回去查资料才知道是这回事。第二次让我说说知道线程模型,汉!这个名词感觉没有听说过,线程?模型?半同步/半异步,领导者/跟随者知道吗。再汉,我知道同步/异步,还有半同步/半异步?啥呀?领导者/跟随者,我现在没有领导。回去一顿恶补,原来是ACE框架里边经常有这样的提法,Reactor属于同步/半同步,PREACTOR属于领导者/跟随者模式。瀑布汗。小插曲一段,这些不懂没关系,下边我慢慢分解),事件分离器,线程池等。内部设计希望通过各个模块的给出一个简单设计,经过您的进一步的组合和打磨,就可以实现一个基本的高并发服务器。
1. Java高并发服务器
Java设计高并发服务器相对比较简单。直接是用ServerSocket或者Channel+selector实现。前者属于同步IO设计,后者采用了模拟的异步IO。为什么说模拟的异步IO呢?记得网上看到一篇文章分析了java的selector。在windows上通过建立一个127.0.0.1到127.0.0.1的连接实现IO的异步通知。在linux上通过建立一个管道实现IO的异步通知。考虑到高并并发系统的要求和java上边的异步IO的限制(通常操作系统同时打开的文件数是有限制的)和效率问题,java的高并发服务器设计不做展开深入的分析,可以参考C高并发服务器的分析做同样的设计。
2. C高并发服务器设计
1) 基本概念
Ø 阻塞和非阻塞socket
所谓阻塞Socket,是指其完成指定的任务之前不允许程序调用另一个函数,在Windows下还会阻塞本线程消息的发送。所谓非阻塞Socket,是指操作启动之后,如果可以立即得到结果就返回结果,否则返回表示结果需要等待的错误信息,不等待任务完成函数就返回。一个比较有意思的问题是accept的Socket是阻塞的还是非阻塞的呢?下边是MSDN上边的一段话:The accept function extracts thefirst connection on the queue of pending connections on socket s. It thencreates and returns a handle to the new socket. The newly created socket is thesocket that will handle the actual connection; it has the same properties assocket s, including the asynchronous events registered with the WSAAsyncSelector WSAEventSelect functions.
Ø 同步/异步IO
有两种类型的文件IO同步:同步文件IO和异步文件IO。异步文件IO也就是重叠IO。
在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。
如果IO请求需要大量时间执行的话,异步文件IO方式可以显著提高效率,因为在线程等待的这段时间内,CPU将会调度其他线程进行执行,如果没有其他线程需要执行的话,这段时间将会浪费掉(可能会调度操作系统的零页线程)。如果IO请求操作很快,用异步IO方式反而还低效,还不如用同步IO方式。
同步IO在同一时刻只允许一个IO操作,也就是说对于同一个文件句柄的IO操作是序列化的,即使使用两个线程也不能同时对同一个文件句柄同时发出读写操作。重叠IO允许一个或多个线程同时发出IO请求。异步IO在请求完成时,通过将文件句柄设为有信号状态来通知应用程序,或者应用程序通过GetOverlappedResult察看IO请求是否完成,也可以通过一个事件对象来通知应用程序。高并发系统通常采用异步IO方式提高系统性能。
Ø 事件分离器
事件分离器的概念是针对异步IO来说的。在同步IO的情况下,执行操作等待返回结果,不要事件分离器。异步IO的时候,发送请求后,结果是通过事件通知的。这是产生了事件分离器的需求。事件分离器主要任务是管理和分离不同文件描述符上的所发生的事件,让后通知相应的事件,派发相应的动作。
Ø 线程池
线程池基本上比较简单,实现线程的借入和借出,创建和销毁。最完好可以做到通过一个事件触发一个线程开始工作(注:在epoll中,事件触发又分为边沿触发和水平触发)。
2) 常见的设计模式
根据Socket的阻塞非阻塞,IO的同步和异步。可以分为如下4中情形
阻塞同步 | 阻塞异步 |
非阻塞同步 | 非阻塞异步 |
阻塞同步方式是原始的方式,也是许多教科书上介绍的方式,因为Socket和IO默认的为阻塞和同步方式。基本流程如下:
listen_fd = socket( AF_INET,SOCK_STREAM,0 )
bind( listen_fd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr_in))
listen( listen_fd,1 )
accept( listen_fd, (struct sockaddr*)&remote_addr,&addr_len )
recv( accept_fd ,&in_buf ,1024 ,0 )
close(accept_fd)
阻塞异步方式有所改进,但是Socket的阻塞方式,前一个连接没有处理完成,下一个连接不能接入,是高并发服务器所不可接收的方式。只不过在上边阻塞同步方式的基础上使用select(严格来说select是一种IO多路服用技术。因为linux尚没有完整的实现异步IO,而winsock实在理解socket没有linux上面那么直观。,这里为了方便,没有做严格的区分)或者其它异步IO方式。
非阻塞同步方式,通过设置socket选项为NONBLOCK,可以很快的接收连接,但是处理采用同步IO方式,服务器处理性能也比较差。
上边三种方式不做深入介绍。下边主要从非阻塞异步IO方式介绍。
非阻塞异步IO方式中,由于异步IO方式在同一系统可能有多种实现,不同系统也有不同实现,下边介绍几种常见的IO方式和服务器框架。
Ø Select
Select采用轮训注册的fd方式。是一种比较老的IO多路服用实现方式,效率相对要差一些。Select方式在windows和linux上都支持。
基本框架如下:
socket( AF_INET,SOCK_STREAM,0 )
fcntl(listen_fd, F_SETFL,flags|O_NONBLOCK);
bind( listen_fd, (structsockaddr *)&my_addr,sizeof(struct sockaddr_in))
listen( listen_fd,1 )
FD_ZERO( &fd_sets );
FD_SET(listen_fd,&fd_sets);
for(k=0; k<=i; k++){
FD_SET(accept_fds[k],&fd_sets);
}
events = select( max_fd + 1,&fd_sets, NULL, NULL, NULL );
if(FD_ISSET(listen_fd,&fd_sets) ){
accept_fd = accept( listen_fd, (structsockaddr
*)&remote_addr,&addr_len );
}
for( j=0; j<=i; j++ ){
if(
FD_ISSET(accept_fds[j],&fd_sets) ){
recv( accept_fds[j] ,&in_buf ,1024 ,0 );
}
}
Ø Epoll
Epoll是linux2.6内核以后支持的一种高性能的IO多路服用技术。服务器框架如下:
socket( AF_INET,SOCK_STREAM,0 )
fcntl(listen_fd, F_SETFL,flags|O_NONBLOCK);
bind( listen_fd, (structsockaddr *)&my_addr,sizeof(struct sockaddr_in))
listen( listen_fd,1 )
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
ev_s = epoll_wait(epfd,events,20,500 );
for(i=0; i<ev_s;i++){
if(events[i].data.fd==listen_fd){
accept_fd = accept( listen_fd,(structsockaddr *)&remote_addr,&addr_len
);
fcntl(accept_fd, F_SETFL,flags|O_NONBLOCK);
epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev);
}
else if(events[i].events&EPOLLIN){
recv( events[i].data.fd ,&in_buf,1024 ,0 );
}
}
Ø AIO
在windows上微软实现了异步IO,通过AIO可以方便的实现高并发的服务器。框架如下:
WSAStartup( 0x0202 , & wsaData)
CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0 , 0 )
WSASocket(AF_INET,SOCK_STREAM, 0 , NULL, 0 , WSA_FLAG_OVERLAPPED)
bind(Listen, (PSOCKADDR) & InternetAddr, sizeof
(InternetAddr))
listen(Listen, 5 )
WSAAccept(Listen, NULL, NULL,NULL, 0 )
PerHandleData =(LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof
(PER_HANDLE_DATA)
CreateIoCompletionPort((HANDLE)Accept, CompletionPort, (DWORD) PerHandleData,
0 )
PerIoData= (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof
(PER_IO_OPERATION_DATA))
WSARecv(Accept,&(PerIoData->DataBuf),1,&RecvBytes,&Flags,&(PerIoData->Overlapped),
NULL)
(GetQueuedCompletionStatus(CompletionPort, & BytesTransferred,
(LPDWORD) &
PerHandleData,(LPOVERLAPPED * ) & PerIoData, INFINITE)
if (PerIoData -> BytesRECV >PerIoData ->
BytesSEND){
WSASend(PerHandleData-> Socket, & (PerIoData
->DataBuf), 1 , & SendBytes, 0 ,
& (PerIoData ->Overlapped), NULL)
}
3) 引入线程池和事件分离器后
由于上边只是单纯的使用非阻塞Socket和异步IO的方式。提高了接收连接和处理的速度。但是还是不能解决两个客户端同时连接的问题。这时就需要引入多线程机制。引入多线程后,又有许多策略。Linux上通常采用主进程负责接收连接,之后fork子进程处理连接。Windows通常采用线程池方式,避免线程创建和销毁的开销,当然linux上也可以采用线程池方式。采用多进程和多线程方式后。事件处理也可以再优化,定义一个简单的事件处理器,把所有事件放入一个队列,各个线程去事件队列取相应的事件,然后自己开始工作。这就是我上边提到的半同步/半异步方式了。如果线程工作的时候是接收到连接后,自己处理后续的发送和接收,然后选出另外一个线程作为领导继续接收连接,其它线程作为追随者。这就是领导者/追随者模式了。具体可以参考ACE的Reactor和Preactor的具体实现。半同步和/半异步网上也有很多的讨论,可以自己深入研究。代码就比较复杂了,这里就不给出代码了。
二、分布式系统设计
前面讲述了分布式系统中的核心的服务器的实现。可以是http服务器,缓存服务器,分布式文件系统等的内部实现。下边主要从一个高并发的大型网站出发,看一个高并发系统的设计。下边是一个高并发系统的逻辑结构:
主要是参考这篇文章http://www.chinaz.com/web/2010/0310/108211.shtml。下边主要想从这个架构的各个部分的实现展开。
1. 缓存系统
缓存是每一个高并发,高可用系统不可或缺的模块。下边就几个常见缓存系统系统进行介绍。
Squid
Squid作为一个前端缓存,通常部署在网络的离用户最近的地方,通过缓存网站的页面,使用户不必每次都跑到服务器去取数据,提高系统响应和性能。实现应该比较简单:一个带有存储功能的代理。用户访问页面的时候,由它代理,然后存储请求结果,下次再访问的时候,查看是否需要更新,有更新就去服务器取新数据,否则直接返回用户页面。
Ehcache
Ehcache是一个对象缓存系统。通常在J2EE中配合Hibernate使用,这里请原谅作者本人之前是做J2EE开发的,其它使用方式暂不是很了解。应用查询数据库,对经常需要查询,却更新不频繁的数据,可以放入ehcache缓存,提高访问速度。Ehcahe支持内存缓存和硬盘两种方式,支持分布式缓存。数据缓存的基本原理就是:为需要缓存的对象建立一个map,临时对象放入map,查询的时候先查询map,没有找到再查找数据库。关机时可以序列化到硬盘。分布式缓存没有研究过。
页面缓存和动态页面静态化
在大型网站经常使用的一种缓存技术就是动态页面的缓存。由于动态页面经常更新,上边的缓存就不起作用了。通常会采用SSI(Server side include)等技术将动态页面的或者页面片段进行缓存。
还有一种就是动态页面静态化。
2. 负载均衡系统
Ø 负载均衡策略
负载均衡策略有随机分配,平均分配,分布式一致性hash等。随机分配就是通过随机数选择一个服务器来服务。平均分配就是一次循环分配一次。分布式一致性hash算法,比较负载,把资源和节点映射到一个换上,然后通过一定的算法资源对应到节点上,使得添加和去掉服务器变得非常容易,减少对其它服务器的影响。很有名的一个算法,据说是P2P的基础。了解不是很深,就不详细说了,要露马脚了。
Ø 软件负载均衡
软件负载均衡可以采用很多方案,常见的几个方案有:
基于DNS的负载均衡,通过DNS正向区域的配置,将一个域名根据一定的策略解析到多个ip地址,实现负载均衡,这里需要DNS服务器的配合。
基于LVS的负载均衡。LVS可以将多个linux服务器做成一个虚拟的服务器,对外提供服务器,实现负载均衡。
基于Iptables的负载均衡。Iptables可以通过做nat,对外提供一个虚拟IP,对内映射到多个服务器实现负载均衡。基本上可以和硬件均衡方案一致了,这里的linux服务器相当于一台路由器。
Ø 硬件负载均衡
基于路由器的负载均衡,在路由器上配置nat实现负载均衡。对外网一个虚拟IP,内网映射几个内网IP。一些网络设备厂商也提供了一些负载均衡的设备,如F5,不过价格不菲哦。数据库的负载均衡数据库的负载均衡可以是数据库厂商提供的集群方案。
转载信息:
作者:周顺利
原文链接:http://blog.csdn.net/shatty/article/details/6629896
注:本文大多数观点和代码都是从网上或者开源代码中抄来的,为了疏理和组织这片文章,作者也费了不少心血,为了表示对我劳动的尊重,请转载时注明作者和出处。
DRBD源码分析(二)——内核模块网络配置和启动
在上一篇里面分析到了基于netlink的connector,connector正是内核态与用户态配置命令交互的通道。用户通过调用用户态的工具,发送相应的命令参数,用户态工具将命令参数转换成相应的消息包,内核态解析消息后得到相应的指令,继续转换成函数调用,最后得以执行。
首先仔细看一下上一节提到的创建connector时注册的收数据的回调函数:
#ifdef KERNEL_HAS_CN_SKB_PARMS
STATIC void drbd_connector_callback(struct cn_msg *req, struct netlink_skb_parms *nsp)
{
#else
STATIC void drbd_connector_callback(void *data)
{
struct cn_msg *req = data;
#endif
struct drbd_nl_cfg_req *nlp = (struct drbd_nl_cfg_req *)req->data;
struct cn_handler_struct *cm;
struct cn_msg *cn_reply;
struct drbd_nl_cfg_reply *reply;
struct drbd_conf *mdev;
int retcode, rr;
int reply_size = sizeof(struct cn_msg)
+ sizeof(struct drbd_nl_cfg_reply)
+ sizeof(short int);
if (!try_module_get(THIS_MODULE)) {
printk(KERN_ERR "drbd: try_module_get() failed!\n");
return;
}
#ifdef KERNEL_HAS_CN_SKB_PARMS
if (!cap_raised(nsp->eff_cap, CAP_SYS_ADMIN)) {
retcode = ERR_PERM;
goto fail;
}
#endif
mdev = ensure_mdev(nlp);
if (!mdev) {
retcode = ERR_MINOR_INVALID;
goto fail;
}
trace_drbd_netlink(req, 1);
if (nlp->packet_type >= P_nl_after_last_packet) {
retcode = ERR_PACKET_NR;
goto fail;
}
printk("packet_type is %d\n", nlp->packet_type);
cm = cnd_table + nlp->packet_type;
/* This may happen if packet number is 0: */
if (cm->function == NULL) {
retcode = ERR_PACKET_NR;
goto fail;
}
reply_size += cm->reply_body_size;
/* allocation not in the IO path, cqueue thread context */
cn_reply = kmalloc(reply_size, GFP_KERNEL);
if (!cn_reply) {
retcode = ERR_NOMEM;
goto fail;
}
reply = (struct drbd_nl_cfg_reply *) cn_reply->data;
reply->packet_type =
cm->reply_body_size ? nlp->packet_type : P_nl_after_last_packet;
reply->minor = nlp->drbd_minor;
reply->ret_code = NO_ERROR; /* Might by modified by cm->function. */
/* reply->tag_list; might be modified by cm->function. */
rr = cm->function(mdev, nlp, reply);
cn_reply->id = req->id;
cn_reply->seq = req->seq;
cn_reply->ack = req->ack + 1;
cn_reply->len = sizeof(struct drbd_nl_cfg_reply) + rr;
cn_reply->flags = 0;
trace_drbd_netlink(cn_reply, 0);
rr = cn_netlink_send(cn_reply, CN_IDX_DRBD, GFP_KERNEL);
if (rr && rr != -ESRCH)
printk(KERN_INFO "drbd: cn_netlink_send()=%d\n", rr);
kfree(cn_reply);
module_put(THIS_MODULE);
return;
fail:
drbd_nl_send_reply(req, retcode);
module_put(THIS_MODULE);
}
值得注意的是:
rr=cm->function(mdev,nlp,reply);
这一句,这里相当于是一个多态,function绑定到哪一个方法由消息包中携带的包类型决定:
cm=cnd_table+nlp->packet_type;
系统在初始化时级生成了一个全局的静态函数表,类似P_primary的标识符是在编译时动态生成的宏。表示其所在的元素的下标,同时也月包类型相对应。
static struct cn_handler_struct cnd_table[] = {
[ P_primary ] = { &drbd_nl_primary, 0 },
[ P_secondary ] = { &drbd_nl_secondary, 0 },
[ P_disk_conf ] = { &drbd_nl_disk_conf, 0 },
[ P_detach ] = { &drbd_nl_detach, 0 },
[ P_net_conf ] = { &drbd_nl_net_conf, 0 },
[ P_disconnect ] = { &drbd_nl_disconnect, 0 },
[ P_resize ] = { &drbd_nl_resize, 0 },
[ P_syncer_conf ] = { &drbd_nl_syncer_conf, 0 },
[ P_invalidate ] = { &drbd_nl_invalidate, 0 },
[ P_invalidate_peer ] = { &drbd_nl_invalidate_peer, 0 },
[ P_pause_sync ] = { &drbd_nl_pause_sync, 0 },
[ P_resume_sync ] = { &drbd_nl_resume_sync, 0 },
[ P_suspend_io ] = { &drbd_nl_suspend_io, 0 },
[ P_resume_io ] = { &drbd_nl_resume_io, 0 },
[ P_outdate ] = { &drbd_nl_outdate, 0 },
[ P_get_config ] = { &drbd_nl_get_config,
sizeof(struct syncer_conf_tag_len_struct) +
sizeof(struct disk_conf_tag_len_struct) +
sizeof(struct net_conf_tag_len_struct) },
[ P_get_state ] = { &drbd_nl_get_state,
sizeof(struct get_state_tag_len_struct) +
sizeof(struct sync_progress_tag_len_struct) },
[ P_get_uuids ] = { &drbd_nl_get_uuids,
sizeof(struct get_uuids_tag_len_struct) },
[ P_get_timeout_flag ] = { &drbd_nl_get_timeout_flag,
sizeof(struct get_timeout_flag_tag_len_struct)},
[ P_start_ov ] = { &drbd_nl_start_ov, 0 },
[ P_new_c_uuid ] = { &drbd_nl_new_c_uuid, 0 },
};
比如,在一次完整的用户态与内核态的交互中,用户态会多次发出P_get_state消息,该消息的包类型码为17。
类似cn_handler_struct这样的函数表,在drbd的代码中随处可见,无论是内核态还是用户态,这样一致的风格,应该非常利于扩展和维护。看代码的人也会觉得非常轻松,不至于无章可循。
DRBD的配置信息、虚拟设备、网络通信端口、对端信息等都是通过drbdsetup或者drbdadm工具以netlink消息包发送到内核态的。
在收到5号消息包时,drbd_nl_net_conf会被调用。在该函数中,会启动worker内核线程,该线程监控一个等待队列,当有事件到来时,即取出处理:
int drbd_worker(struct drbd_thread* thi)
{
...
w = NULL;
spin_lock_irq(&mdev->data.work.q_lock);
ERR_IF(list_empty(&mdev->data.work.q))
{
/* something terribly wrong in our logic.
* we were able to down() the semaphore,
* but the list is empty... doh.
*
* what is the best thing to do now?
* try again from scratch, restarting the receiver,
* asender, whatnot? could break even more ugly,
* e.g. when we are primary, but no good local data.
*
* I'll try to get away just starting over this loop.
*/
spin_unlock_irq(&mdev->data.work.q_lock);
continue;
}
w = list_entry(mdev->data.work.q.next, struct drbd_work, list);
list_del_init(&w->list);
spin_unlock_irq(&mdev->data.work.q_lock);
if (!w->cb(mdev, w, mdev->state.conn < C_CONNECTED))
{
/* dev_warn(DEV, "worker: a callback failed! \n"); */
if (mdev->state.conn >= C_CONNECTED)
drbd_force_state(mdev, NS(conn, C_NETWORK_FAILURE));
}
...
}
启动了worker线程之后,几乎所有的内核态的事务都会交给这个线程来处理。
继续回到drbd_nl_net_conf方法中,在初始化完worker线程后,会继续执行如下语句:
retcode=_drbd_request_state(mdev,NS(conn,C_UNCONNECTED),CS_VERBOSE);
这里既是与对端协商确定当前谁是主节点。在该方法中会向等待队列中放入一个事务,该事务为启动一个receiver线程,receiver线程会使用配置文件中指定的端口和IP信息建立tcp socket监听,等待对端的链接。此时,如果对端一直未有连接过来,本端尝试与对端连接也一直无法建立,则会根据配置等待指定的超时时间,之后会将本段置为Standalone状态。这也就是我们常见的两台服务器同时重启时,会发现一端的启动过程卡在drbd的等待上面。
迷宫营救公主算法
今天下班前在公司的技术题库中看到这道题目,思路很快就有了,递归遍历每一条可能的路径,然后找出最短的路径。回家把代码写出来了。发现算法效率实在是太低了,在矩阵较小的时候还好,当矩阵稍微大一点时根本算不过来,感觉复杂度像是O((N*M)^(N*M))这个数值太庞大了,也完全没有意义。公主等到花儿都谢啦。。
貌似这位同学的算法还不错,还没来得及研究,先做个链接:http://blog.csdn.net/joseph_1118/article/details/9390301
应付较小的矩阵下面的方法还是能蒙混过关的。先发到这里,后面再继续研究优化。题目和代码都在一起:
/**
* 公主被魔王抓走了,王子需要拯救出美丽的公主。他进入了魔王的城堡,魔王的城堡是一座很大的迷宫。
* 为了使问题简单化,我们假设这个迷宫是一个N*M的二维方格。迷宫里有一些墙,王子不能通过。王子只
* 能移动到相邻(上下左右四个方向)的方格内,并且一秒只能移动一步。地图由’S’,’P’,’.’,’*’
* 四种符号构成,’.’表示王子可以通过,’*’表示墙,王子不能通过;’S’表示王子的位置;’P’表示公主
* 的位置;T表示公主存活的剩余时间,王子必须在T秒内到达公主的位置,才能救活公主。如下图所示:
*/
彻底解决WordPress博客垃圾评论的问题
本来这里很少有评论,但是来自日语或者英语的垃圾评论却非常多,可以用wordpress自带的过滤功能禁止其中的完全英语的垃圾评论,但是没有办法禁止日语的,甚至其他各种语言的,在网络上面发现有人使用计算加法作校验的方式来屏蔽来解决。因为垃圾评论都是用“机器人”发出的,他们不可能去分析评论的额外接口。利用wordpress提供的comments的两个回调即可实现。
在网上下载了插件的代码,稍作了修改。
修改步骤:
1、将如下文件解压缩后放到wordpress的如下目录中:$wordpress-root-path\wp-content\plugins,形成$wordpress-root-path\wp-content\plugin\comment_capatcha.php的目录结构。粗体部分替换成你的wordpress安装的根目录。这个过程实际上相当于安装了一个新的插件。
下载链接:http://codefine.site/wp-content/uploads/2013/08/comment_capatcha.zip
这里也把代码贴出来:
<?php
/*
Plugin Name: 简单算术题评论验证码插件
Plugin URI: http://www.utubon.com/wordpress-plugin-comment-capatcha-figure/
Description: 提交评论之前必须写出简单的算术题答案
Version: 1.0
Author: 否子戈
Author URI: http://www.utubon.com
*/
if(!class_exists('comment_capatcha')) {
class comment_capatcha {
function __construct() {
add_action('comment_form', array(& $this, 'print_capatcha'));
add_filter('preprocess_comment', array(& $this, 'preprocess_comment'));
}
function print_capatcha() {
if(!is_user_logged_in()) {
global $post;
session_start();
$rand_1 = mt_rand(1,6);
$rand_2 = mt_rand(1,6);
$_SESSION['capatcha_'.$post->ID] = $rand_1 + $rand_2;
$str = '<div id="capatcha-area"><label>';
$str .= "{$rand_1} + {$rand_2} = ".'<input type="text" size="2" name="capatcha" id="capatcha" />';
$str .= 'Result<span class="required">*</span>';
$str .= '</label></div>';
$str .= '<br>';
echo $str;
}
}
function preprocess_comment($commentdata) {
if(!is_user_logged_in()) {
session_start();
$post_id = isset($_POST['comment_post_ID']) ? $_POST['comment_post_ID'] : 0;
if(!$post_id){
wp_die('数据来源非法。');
}
$capatcha = $_SESSION['capatcha_'.$post_id];
if($capatcha != $_POST['capatcha']){
wp_die( __('请返回后重新输入算数题答案!') );
}
unset($_SESSION['capatcha_'.$post_id]);
}
return $commentdata;
}
}
}
if( !isset($comment_capatcha) ) {
$comment_capatcha =& new comment_capatcha();
}
?>
2、修改算术计算行所放的位置
我的主题默认是放在提交按钮之上,评论内容之后,可以移动到评论内容之前,作如下修改即可:(图片较小,点击后可以在新窗口查看大图)
移动doaction函数的调用点即可修改算术框的位置。
3、到插件管理页面开启插件:简单算术题评论验证码插件
本站修改实现之后的效果为:
写过的真正意义上的第一个C++程序
博客的发布记录为:
记得当时刚过完年回到学校,看了一个寒假的书,迫不及待的想实践一下,于是参照《Exceptional C++》上面反复拿出来举例的Complex类,自己重新写了一个,虽然不是独创的。首先规划好了想要实现哪些接口,然后一条一条地到《C++ Primer》中去找相应的注意事项和一般约定。整个代码几乎是自己独立写出的。后来在面试过程中,还重新将这份代码又写了一遍提交给考官,呵呵,当时给他的感觉估计还好,但是他哪知道我只会这几行代码 :-)
感觉现在写代码的风格几乎都是那个时候定型的。喜欢比较整洁的风格,看到乱七八糟的代码总有重构的冲动。
让进程在后台可靠运行的几种方法
几年前在developerWorks上面看到的文章,感觉非常实用,又简单整理了一下,转到这里,希望给看到的人带来一些帮助。文中提到的nohup和subshell方式一直在使用。
我们经常会碰到这样的问题,用 telnet/ssh 登录了远程的 Linux 服务器,运行了一些耗时较长的任务, 结果却由于网络的不稳定导致任务中途失败。如何让命令提交后不受本地关闭终端窗口/网络断开连接的干扰呢?下面举了一些例子, 您可以针对不同的场景选择不同的方式来处理这个问题。
如果只是临时有一个命令需要长时间运行,什么方法能最简便的保证它在后台稳定运行呢?
解决方法:
1.nohup
我们知道,当用户注销(logout)或者网络断开时,终端会收到HUP(hangup)信号从而关闭其所有子进程。因此,我们的解决办法就有两种途径:要么让进程忽略 HUP 信号,要么让进程运行在新的会话里从而成为不属于此终端的子进程。
HANGUP名称的来由,在Unix的早期版本中,每个终端都会通过modem 和系统通讯。当用户 logout 时,modem 就会挂断(hang up)电话。 同理,当modem断开连接时,就会给终端发送hangup信号来通知其关闭所有子进程。
nohup 无疑是我们首先想到的办法。顾名思义,nohup 的用途就是让提交的命令忽略 hangup 信号。让我们先来看一下 nohup 的帮助信息:
NOHUP(1) User Commands NOHUP(1)
NAME
nohup – run a command immune to hangups, with output to a non-ttySYNOPSIS
nohup COMMAND [ARG]…
nohup OPTIONDESCRIPTION
Run COMMAND, ignoring hangup signals.
–help display this help and exit
–version output version information and exit
可见,nohup 的使用是十分方便的,只需在要处理的命令前加上 nohup 即可,标准输出和标准错误缺省会被重定向到 nohup.out 文件中。一般我们可在结尾加上“&”来将命令同时放入后台运行,也可用”>filename 2>&1″来更改缺省的重定向文件名。
nohup 示例
[root@pvcent107 ~]# nohup ping www.ibm.com &; [1] 3059 nohup: appending output to `nohup.out' [root@pvcent107 ~]# ps -ef |grep 3059 root 3059 984 0 21:06 pts/3 00:00:00 ping www.ibm.com root 3067 984 0 21:06 pts/3 00:00:00 grep 3059 [root@pvcent107 ~]#
2.setsid
nohup 无疑能通过忽略 HUP 信号来使我们的进程避免中途被中断,但如果我们换个角度思考,如果我们的进程不属于接受 HUP 信号的终端的子进程,那么自然也就不会受到 HUP 信号的影响了。setsid 就能帮助我们做到这一点。让我们先来看一下 setsid 的帮助信息:
SETSID(8) Linux Programmer’s Manual SETSID(8)
NAME
setsid – run a program in a new session
SYNOPSIS
setsid program [ arg … ]
DESCRIPTION
setsid runs a program in a new session.
可见 setsid 的使用也是非常方便的,也只需在要处理的命令前加上 setsid 即可。
setsid 示例
[root@pvcent107 ~]# setsid ping www.ibm.com [root@pvcent107 ~]# ps -ef |grep www.ibm.com root 31094 1 0 07:28 ? 00:00:00 ping www.ibm.com root 31102 29217 0 07:29 pts/4 00:00:00 grep www.ibm.com [root@pvcent107 ~]#
值得注意的是,上例中我们的进程 ID(PID)为31094,而它的父 ID(PPID)为1(即为 init 进程 ID),并不是当前终端的进程 ID。请将此例与nohup 例中的父 ID 做比较。
3.&符号(命令简单易记,推荐使用。)
这里还有一个关于 subshell 的小技巧。我们知道,将一个或多个命令包含在“()”中就能让这些命令在子 shell 中运行中,从而扩展出很多有趣的功能,我们现在要讨论的就是其中之一。
当我们将”&”也放入“()”内之后,我们就会发现所提交的作业并不在作业列表中,也就是说,是无法通过jobs来查看的。让我们来看看为什么这样就能躲过 HUP 信号的影响吧。
subshell 示例
[root@pvcent107 ~]# (ping www.ibm.com &;)
[root@pvcent107 ~]# ps -ef |grep www.ibm.com
root 16270 1 0 14:13 pts/4 00:00:00 ping www.ibm.com
root 16278 15362 0 14:13 pts/4 00:00:00 grep www.ibm.com
[root@pvcent107 ~]#
从上例中可以看出,新提交的进程的父 ID(PPID)为1(init 进程的 PID),并不是当前终端的进程 ID。因此并不属于当前终端的子进程,从而也就不会受到当前终端的 HUP 信号的影响了。
我们已经知道,如果事先在命令前加上 nohup 或者 setsid 就可以避免 HUP 信号的影响。但是如果我们未加任何处理就已经提交了命令,该如何补救才能让它避免 HUP 信号的影响呢?
解决方法:
12款强大的Chrome插件
Chrome功能强大,也得益于其拥有丰富的扩展资源库。Chrome Web Store里有各种各样的插件,可以满足你使用Chrome时的各种要求。和Firefox一样,Chrome的扩展非常容易安装,而且非常容易卸载。与Firefox不同,Chrome的扩展不需要重新启动,并且不会有扩展插件会减小你的网页面积。在这里我总结出2013年 Chrome 的12款非常强大的扩展程序,供大家挑选分享。这些插件能不同程度地提升效率。诸如Turn off the light这些非常常用的我就不介绍了。
Windows XP不能向VMware虚拟机传输大文件的问题分析和解决
不知道从什么时候开始,XP不能向虚拟机传送大文件了,包括借助于vmtools、sftp、samba和ftp都不行,无论哪一段作服务端,都是一样。samba最后的提示是“文件路径太深”。显然不是因为这个原因。使用sftp或者ftp,一般是文件名创建成功后,数据传不出去。如下图:
| 软件技术 | 归档目录: 标签: linux, VMware, Windows XP, 无法传输数据
DRBD源码分析(一)——内核模块初始化
本地安装的是drbd-8.3.5版本,下载相应的源码包。两个子目录涉及源代码,其中drbd目录为内核态的源码,user目录为用户态工具的源码。所有的业务都是在内核态完成,用户态只是提供工具安装、配置、维护内核模块的工作。
drbd架构图,在官方网站的主页上面就能看到,非常显眼,这是内核态的架构示意图:
可以很清晰的看到,drbd在文件系统之下,直接操纵物理磁盘(块设备),在网络模型中,基于传输层之上建立虚拟设备。通过TCP/IP协议与远端设备交互。
drbd内核模块的名字为:
[root@Shentar /opt/drbd-8.3.5]# modprobe -all|grep drbd
/lib/modules/2.6.25-14.fc9.i686/kernel/drivers/block/drbd.ko
[root@Shentar /opt/drbd-8.3.5]#
首先找代码的入口,内核模块的初始化定义:module_init宏定义。在drbd_main.c文件中。
module_init(drbd_init)
module_exit(drbd_cleanup)
下面贴出初始化函数:drbd_init(void):
整个初始化分如下几个步骤:
int __init drbd_init(void)
{
int err;
if (sizeof(struct p_handshake) != 80)
{
printk(KERN_ERR
"drbd: never change the size or layout "
"of the HandShake packet.\n");
return -EINVAL;
}
if (1 > minor_count || minor_count > 255)
{
printk(KERN_ERR
"drbd: invalid minor_count (%d)\n", minor_count);
#ifdef MODULE
return -EINVAL;
#else
minor_count = 8;
#endif
}
err = drbd_nl_init();
if (err)
return err;
err = register_blkdev(DRBD_MAJOR, "drbd");
if (err)
{
printk(KERN_ERR
"drbd: unable to register block device major %d\n", DRBD_MAJOR);
return err;
}
register_reboot_notifier(&drbd_notifier);
/*
* allocate all necessary structs
*/
err = -ENOMEM;
init_waitqueue_head(&drbd_pp_wait);
drbd_proc = NULL; /* play safe for drbd_cleanup */
minor_table = kzalloc(sizeof(struct drbd_conf *) * minor_count, GFP_KERNEL);
if (!minor_table)
goto Enomem;
err = drbd_create_mempools();
if (err)
goto Enomem;
drbd_proc = proc_create("drbd", S_IFREG | S_IRUGO, NULL, &drbd_proc_fops);
if (!drbd_proc)
{
printk(KERN_ERR "drbd: unable to register proc file\n");
goto Enomem;
}
rwlock_init(&global_state_lock);
printk(KERN_INFO "drbd: initialized. "
"Version: " REL_VERSION " (api:%d/proto:%d-%d)\n",
API_VERSION, PRO_VERSION_MIN, PRO_VERSION_MAX);
printk(KERN_INFO "drbd: %s\n", drbd_buildtag());
printk(KERN_INFO "drbd: registered as block device major %d\n", DRBD_MAJOR);
printk(KERN_INFO "drbd: minor_table @ 0x%p\n", minor_table);
return 0; /* Success! */
Enomem : drbd_cleanup();
if (err == -ENOMEM)
/* currently always the case */
printk(KERN_ERR "drbd: ran out of memory\n");
else
printk(KERN_ERR "drbd: initialization failure\n");
return err;
}
- 1、drbd_nl_init方法
初始化网络,在内核2.6.16之前的版本中,还没有内核连接器的封装,还是直接调用原始的netlink套接字,因此初始化时需要有一系列的初始化netlink的动作。新的内核版本中集成了connector的封装,相对来说网络初始化的过程就简单多了。在分析代码时,需要注意,drbd目录下也有一个connector.c,这是为老版本准备的。新版本中根本没有编译该文件,因此只需要知道cn_add_callback这个接口的作用,而不需要去看connector.c中该函数的定义。昨天误分析到connector.c中去了,发现cn_add_callback函数最终结束时会往自己生成的任务队列中注册一个任务,但是怎么也找不到之后谁来等待执行该任务,试图加日志查看该函数流程时,才发现原来并没有运行到该代码。这才发现上述connector封装的问题。
关于连接器,这里简要说明一下,连接器封装了内核态和用户态的通讯过程。提供了简单的几个接口:
int cn_add_callback(struct cb_id*, char*, void (*callback) (void*));
void cn_del_callback(struct cb_id*);
int cn_netlink_send(struct cn_msg*, u32, gfp_t);
int cn_queue_add_callback(struct cn_queue_dev* dev, char* name,
struct cb_id* id, void (*callback) (void*));
void cn_queue_del_callback(struct cn_queue_dev* dev, struct cb_id* id);
struct cn_queue_dev* cn_queue_alloc_dev(char* name, struct sock*);
void cn_queue_free_dev(struct cn_queue_dev* dev);
int cn_cb_equal(struct cb_id*, struct cb_id*);
void cn_queue_wrapper(void* data);
这里只分析cn_add_callback和cn_netlink_send两个,在内核创建连接器时,需要调用cn_add_callback方法,其中callback函数指针参数指定了连接器接收到数据时的回调函数,数据到达时将由该函数来处理。连接器既可以用于接收数据也可以用于发送数据。接收数据使用回调来处理。发送数据直接使用cn_netlink_send方法即可。该方法支持单播、广播和组播。一般单播和广播会用到。cn_add_callback方法无误返回,也就意味着网络初始化好了,比起之前版本中的复杂创建、绑定和监听套接字等流程简单多了。
- 2、register_blkdev方法
这个方法是内核系统调用,用于注册一个块设备,需要指定主设备号,如果指定的设备号为0,则会由系统自动分配一个。该方法调用之后,就可以在/proc/devices文件中看到drbd块设备。drbd设备的设备号在代码中写死,为147。需要注意的是,如果块设备号已被占用,会导致注册失败。
见左图。
- 3、register_reboot_notifier注册块设备重启时的回调函数
目前注册了一个空函数,委婉的说以后会实现,其实没有任何需要做的事情:
- 4、init_waitqueue_head初始化一个等待队列
等待队列可以是生产者和消费者之间的共享队列,后面的业务分析中应该会遇到该结构,到时会仔细分析该队列中所存放的内容。应该与具体的读写数据业务有关。
- 5、drbd_create_mempools创建内存池
这块还没有仔细分析,drbd中需要使用的内存池特别多,数据块的,接收缓冲区的,发送缓冲区的。
- 6、proc_create创建/proc/drbd文件
内核允许模块在/proc目录下创建自己的虚拟文件和虚拟文件夹。并且指定该文件的操作回调函数。其中,drbd指定了open, read, write和close方法的回调函数即/proc/drbd的四个回调函数:
struct file_operations drbd_proc_fops =
{
.owner = THIS_MODULE, .open = drbd_proc_open, .read = seq_read,
.llseek = seq_lseek, .release = single_release,
};
在打开该文件时,内核会调用drbd_proc_open函数来呈现相应的内容。也可以说这里是drbd内核服务向用户态反馈信息的一个通道。所有用户态的工具也是基于这个来判断当前drbd的状态。
- 7、rwlock_init初始化全局读写锁。
整个内核模块的初始化到这里就结束了,总结一下,主要涉及下面几个系统调用:cn_add_callback,cn_netlink_send,register_blkdev,register_reboot_notifier,init_waitqueue_head,mempool_create,create_proc_entry。随着linux 内核的不断壮大,驱动编程似乎也在越来越简单。