作者归档: 童燕群

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的等待上面。

| 1 分2 分3 分4 分5 分 (4.83- 6票) Loading ... Loading ... | 归档目录:C/C++, DRBD | 标签: |

迷宫营救公主算法

今天下班前在公司的技术题库中看到这道题目,思路很快就有了,递归遍历每一条可能的路径,然后找出最短的路径。回家把代码写出来了。发现算法效率实在是太低了,在矩阵较小的时候还好,当矩阵稍微大一点时根本算不过来,感觉复杂度像是O((N*M)^(N*M))这个数值太庞大了,也完全没有意义。公主等到花儿都谢啦。。

貌似这位同学的算法还不错,还没来得及研究,先做个链接:http://blog.csdn.net/joseph_1118/article/details/9390301

应付较小的矩阵下面的方法还是能蒙混过关的。先发到这里,后面再继续研究优化。题目和代码都在一起:

/**
* 公主被魔王抓走了,王子需要拯救出美丽的公主。他进入了魔王的城堡,魔王的城堡是一座很大的迷宫。
* 为了使问题简单化,我们假设这个迷宫是一个N*M的二维方格。迷宫里有一些墙,王子不能通过。王子只
* 能移动到相邻(上下左右四个方向)的方格内,并且一秒只能移动一步。地图由’S’,’P’,’.’,’*’
* 四种符号构成,’.’表示王子可以通过,’*’表示墙,王子不能通过;’S’表示王子的位置;’P’表示公主
* 的位置;T表示公主存活的剩余时间,王子必须在T秒内到达公主的位置,才能救活公主。如下图所示:
*/

阅读全文 »

| 1 分2 分3 分4 分5 分 (5.00- 9票) Loading ... Loading ... | 归档目录:Java, 算法数据结构 | 标签: , |

彻底解决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、修改算术计算行所放的位置

我的主题默认是放在提交按钮之上,评论内容之后,可以移动到评论内容之前,作如下修改即可:(图片较小,点击后可以在新窗口查看大图)

compare

移动doaction函数的调用点即可修改算术框的位置。

3、到插件管理页面开启插件:简单算术题评论验证码插件

本站修改实现之后的效果为:

modify_posting_commeont

| 1 分2 分3 分4 分5 分 (5.00- 2票) Loading ... Loading ... | 归档目录:建站技术 | 标签: , |

写过的真正意义上的第一个C++程序

博客的发布记录为:
By YANQUN | Published: 二月 24, 2008 | Edit
 
记得当时刚过完年回到学校,看了一个寒假的书,迫不及待的想实践一下,于是参照《Exceptional C++》上面反复拿出来举例的Complex类,自己重新写了一个,虽然不是独创的。首先规划好了想要实现哪些接口,然后一条一条地到《C++ Primer》中去找相应的注意事项和一般约定。整个代码几乎是自己独立写出的。后来在面试过程中,还重新将这份代码又写了一遍提交给考官,呵呵,当时给他的感觉估计还好,但是他哪知道我只会这几行代码 :-)
 
感觉现在写代码的风格几乎都是那个时候定型的。喜欢比较整洁的风格,看到乱七八糟的代码总有重构的冲动。

阅读全文 »

| 1 分2 分3 分4 分5 分 (4.75- 4票) Loading ... Loading ... | 归档目录:C/C++, 生活札记, 软件技术 | 标签: , , |

不一样的童年,不一样的快乐

最近几乎把拍成电影的宫崎骏作品全部看完了,但是看过之后几乎都记不住具体的故事情节,只感觉每一部都很感人,都很美好。而且宫崎骏先生的作品大多有一个共同的特点,一个有着一般人没有的特殊能力的女主角,一段特殊的经历,引发观者对环境破坏问题或者人性贪婪的思考,大多套用这样的模式,现在对片名与故事情节的时候都会弄混淆,因此想写一点观后感就更困难了,必须得看第二遍才行。虽然模式相近,但是每一部都有不同的精彩。

就从最近看这一部《魔女宅急便》开始吧。看这部电影的时候,我更多的想到了自己的童年,大概有多少年没有回忆过自己的童年了?正好有机会写一下,不至于以后慢慢的都想不起来了。

魔女宅急便_1989_BD

生活在魔女家庭应该是一件非常幸福和令人羡慕的事情,然而却有“修行”这件事,在我现在看来这将是一段比较难熬的时光,但是剧中的主角琪琪却不这么想,她是很期待的。也许在我十三岁的时候也会无比期待能够自己一个人到外面独闯天下。十三岁刚好是小学毕业准备上初中的时候,我的初中是在一个离家比较远的地方上的,寄居在大伯父家里,虽然伯父伯母对我都很好,但是还是没有在自家自在,很多在农村的不好的生活习惯会与相对发达的城镇的生活习惯相冲突,从我自身的意义上面来说,也可以把这段经历比作是一次修行。在去上初中之前,我也同样怀着一种无比期待和想往的心情,同时也有一点小小的顾虑,担心自己能不能做好这段“修行”。现在回想起来,这段经历还是非常快乐和充实的。由于自己的性格的原因,期间有不少令人哭笑不得的尴尬事情。

我的童年有一小半的时间是在上学放学的路上度过的,家里离学校比较远,每天要翻过几座小山丘,走上1.5公里的山路、田埂路,那个时候就能体会到生活的艰辛,最艰难的时候就是春秋时节,必须穿着鞋子上学,但是田埂上的长草沾满了露水,鞋子会被湿透,在教室里等到鞋子快干的时候,又到了早读放学时间,这时路上的露水还没有完全干,又经受一次洗礼。不过也有很多乐趣,最高兴的时候是晚上放学,大概是下午5点多钟,一路小跑着赶回家中看《圣斗士》,看完动画片吃饭,写作业,每天最开心的时候就是这段时光了。遇到夏天,偶尔天气晴朗的时候,借着月光,还会有三五个小伙伴聚集在我家门前的空地上玩游戏。那时候农村还经常停电,一到晚上,没有电风扇根本睡不着,经常会在屋外用长凳、门板和蚊帐搭一个简易帐篷,躺在床上,听着蚊帐外面的蚊子嗡嗡声,数着天上的星星,不觉就睡着了,等到快转钟的时候,气温下降了一些,有点微凉了,被妈妈叫起来搬进屋里睡,美梦被中断了,这个时候是极不情愿的回到屋里。

魔女

小魔女出场的时候,因为自己是飞在天上的,引起了接上面的人群的羡慕,因为成为大家瞩目的焦点,不觉有一点得意忘形了,最后撞上了大巴车,还被警察逮住了,幸好有“蜻蜓”机智地解围,相信每个小孩都有过这样类似的闯祸经历。后面上街买生活必需品,对着橱窗里漂亮的鞋子久久凝视,不愿离去,可以体会到那份渴望而自己又非常懂事,知道钱应该花在应该花的地方的矛盾心情。小时候的我也总是非常羡慕其他小朋友,总渴望着有一天自己也能有一身帅气的衣服。把不同面额的纸币分堆放,以方便得出总数,自己似乎也这么做过。记得跟着妈妈上街卖自家养的母鸡生的蛋,帮着计算价格,算数学得好,反应也快,卖完了鸡蛋,在街上吃早点。回家了还要再计算一遍是不是有弄错的地方。

人生短暂,每一段时光都应该快乐度过,没有理由让任何阶段在煎熬中徘徊。希望自己能时时保持一颗童心,时时不忘微笑面对坎坷,找回那份本该属于自己的快乐生活。

| 1 分2 分3 分4 分5 分 (5.00- 4票) Loading ... Loading ... | 归档目录:生活札记, 观影随想 | 标签: , , , |

让进程在后台可靠运行的几种方法

几年前在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-tty

SYNOPSIS
nohup COMMAND [ARG]…
nohup OPTION

DESCRIPTION
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 信号的影响呢?

解决方法:

 

阅读全文 »

| 1 分2 分3 分4 分5 分 (5.00- 2票) Loading ... Loading ... | 归档目录:实用脚本, 软件技术 | 标签: , , , , |

12款强大的Chrome插件

Chrome功能强大,也得益于其拥有丰富的扩展资源库。Chrome Web Store里有各种各样的插件,Google Store可以满足你使用Chrome时的各种要求。和Firefox一样,Chrome的扩展非常容易安装,而且非常容易卸载。与Firefox不同,Chrome的扩展不需要重新启动,并且不会有扩展插件会减小你的网页面积。在这里我总结出2013年 Chrome 的12款非常强大的扩展程序,供大家挑选分享。这些插件能不同程度地提升效率。诸如Turn off the light这些非常常用的我就不介绍了。

阅读全文 »

| 1 分2 分3 分4 分5 分 (5.00- 2票) Loading ... Loading ... | 归档目录:移动互联 | 标签: , |

Windows XP不能向VMware虚拟机传输大文件的问题分析和解决

不知道从什么时候开始,XP不能向虚拟机传送大文件了,包括借助于vmtools、sftp、samba和ftp都不行,无论哪一段作服务端,都是一样。samba最后的提示是“文件路径太深”。显然不是因为这个原因。使用sftp或者ftp,一般是文件名创建成功后,数据传不出去。如下图:

阅读全文 »

| 1 分2 分3 分4 分5 分 (5.00- 4票) Loading ... Loading ... | 归档目录:软件技术 | 标签: , , , |

DRBD源码分析(一)——内核模块初始化

本地安装的是drbd-8.3.5版本,下载相应的源码包。两个子目录涉及源代码,其中drbd目录为内核态的源码,user目录为用户态工具的源码。所有的业务都是在内核态完成,用户态只是提供工具安装、配置、维护内核模块的工作。

drbd架构图,在官方网站的主页上面就能看到,非常显眼,这是内核态的架构示意图:

wps_clip_image-31087

可以很清晰的看到,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方法

wps_clip_image-6088

这个方法是内核系统调用,用于注册一个块设备,需要指定主设备号,如果指定的设备号为0,则会由系统自动分配一个。该方法调用之后,就可以在/proc/devices文件中看到drbd块设备。drbd设备的设备号在代码中写死,为147。需要注意的是,如果块设备号已被占用,会导致注册失败。

见左图。

 

 

 

 

  • 3、register_reboot_notifier注册块设备重启时的回调函数

目前注册了一个空函数,委婉的说以后会实现,其实没有任何需要做的事情:

reboot_callback

  • 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 内核的不断壮大,驱动编程似乎也在越来越简单。

| 1 分2 分3 分4 分5 分 (4.90- 10票) Loading ... Loading ... | 归档目录:C/C++, DRBD | 标签: |

WordPress博客添加“返回顶部”按钮

“返回顶部”在网页页面上非常实用。一般主题都没有自带该按钮。可以自己DIY一个。

添加步骤,打开博客的后台管理,依次进入“外观”,“编辑”,打开“footer.php”,在最后一个</div>与</body>之间添加如下代码,注意替换图片地址(斜体部分)以匹配特定主题。

<div id="full" style="width:88px; height:88px; position:fixed; right:0px; 
    bottom:0px; margin-left:0px; margin-bottom:0px; z-index:100; text-align:center; cursor:pointer;">
  <a>
    <img src="http://content.codefine.site/wp-content/themes/thematic/gallery/totop2.png"
    border=0 width="55px" width="55px" alt="返回顶部">
  </a>
</div>
<script type="text/javascript">
  var isie6 = window.XMLHttpRequest ? false: true;
  function newtoponload() {
    var c = document.getElementById("full");
    function b() {
      var a = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
      if (a > 0) {
        if (isie6) {
          c.style.display = "none";
          clearTimeout(window.show);
          window.show = setTimeout(function() {
            var d = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
            if (d > 0) {
              c.style.display = "block";
              c.style.top = (400 + d) + "px"
            }
          },
          300)
        } else {
          c.style.display = "block"
        }
      } else {
        c.style.display = "none"
      }
    }
    if (isie6) {
      c.style.position = "absolute"
    }
    window.onscroll = b;
    b()
  }
  if (window.attachEvent) {
    window.attachEvent("onload", newtoponload)
  } else {
    window.addEventListener("load", newtoponload, false)
  }
  document.getElementById("full").onclick = function() {
    window.scrollTo(0, 0)
  };
</script>

 

本站效果截图:

未命名23

| 1 分2 分3 分4 分5 分 (5.00- 4票) Loading ... Loading ... | 归档目录:建站技术, 移动互联, 软件技术 | 标签: , , |
返回顶部