Chinaunix首页 | 论坛 | 博客
  • 博客访问: 51811
  • 博文数量: 9
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 87
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-04 23:05
文章分类

全部博文(9)

文章存档

2015年(7)

2014年(2)

我的朋友

分类: LINUX

2015-02-28 22:26:37

该文章转载博客
注:作者:
  阅读次数(4,385) |  
本地安装的是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注册块设备重启时的回调函数

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

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

内核链接器的说明:
连接器(Netlink Connector)及其应用 - IBM(转载)
一、引言
连接器是一种新的用户态与内核态的通信方式,它使用起来非常方便。本质上,连接器是一种netlink,它的 netlink 协议号为 NETLINK_CONNECTOR,与一般的 netlink 相比,它提供了更容易的使用接口,使用起来更方便。目前,最新稳定内核有两个连接器应用实例,一个是进程事件连接器,另一个是 CIFS 文件系统。连接器核心实现代码在内核源码树的driver/connector/connector.c 和 drivers/connector/cn_queue.c 文件中,文件 drivers/connector/cn_proc.c 是进程事件连接器的实现代码,而 CIFS 连接器的实现则在该文件系统的实现代码中。连接器是一个可选模块,用户可以在配置内核时在设备驱动(Device drivers)菜单中选择或不选它。
任何内核模块要想使用连接器,必须先注册一个标识 ID 和回调函数,当连接器收到 netlink 消息后,会根据消息对应的标识 ID 调用相应该 ID 的回调函数。
对用户态而言,连接器的使用跟普通的 netlink 没有差别,只要指定 netlink 协议类型为NETLINK_CONNECTOR 就可以了。
回页首
二、连接器相关数据结构和 API
下面是连接器的 API 以及相关的数据结构

struct cb_id
{
__u32 idx;
__u32 val;
};
struct cn_msg
{
struct cb_id id;
__u32 seq;
__u32 ack;
__u32 len; /* Length of the following data */
__u8 data[0];
};
int cn_add_callback(struct cb_id *id, char *name, void (*callback) (void *));
void cn_del_callback(struct cb_id *id);
void cn_netlink_send(struct cn_msg *msg, u32 __group, int gfp_mask);
结构 cb_id 是连接器实例的标识 ID,它用于确定 netlink 消息与回调函数的对应关系。当连接器接收到标识 ID 为 {idx,val} 的 netlink 消息时,注册的回调函数 void (*callback) (void *) 将被调用。该回调函数的参数为结构 struct cn_msg 的指针。
接口函数 cn_add_callback 用于向连接器注册新的连接器实例以及相应的回调函数,参数 id 指定注册的标识 ID,参数 name 指定连接器回调函数的符号名,参数 callback 为回调函数。
接口函数 cn_del_callback 用于卸载回调函数,参数 id 为注册函数 cn_add_callback 注册的连接器标识 ID。
接口函数 cn_netlink_send 用于向给定的组发送消息,它可以在任何上下文安全地调用。但是,如果内存不足,可能会发送失败。在具体的连接器实例中,该函数用于向用户态发送 netlink 消息。
参数 msg 为发送的 netlink 消息的消息头。参数 __group 为接收消息的组,如果它为 0,那么连接器将搜索所有注册的连接器用户,最终将发送给用户 ID 与在 msg 中的 ID 相同的组,但如果 __group 不为 0,消息将发送给 __group 指定的组。参数 gfp_mask 指定页分配标志。
注意:当注册新的回调函数时,连接器将指定它的组为 id.idx。
cn_msg 是连接器定义的消息头,字段 seq 和 ack 用于确保消息的可靠传输,刚才已经提到,netlink 在内存紧张的情况下可能丢失消息,因此该头使用顺序号和响应号来满足要求可靠传输用户的需求。当发送消息时,用户需要设置独一无二的顺序号和随机的响应号,顺序号也应当设置到 nlmsghdr->nlmsg_seq。注意 nlmsghdr 是类型为结构 struct nlmsghdr 的变量,它用于设置或保存 netlink 的消息头。每发送一个消息,顺序号应当加 1,如果需要发送响应消息,那么响应消息 的顺序号应当与被响应的消息的顺序号相同,同时响应消息的响应号应当为被响应消息的顺序号加1。如果接收到的消息的顺序号不是期望的顺序号,那表明该消息是一个新的消息,如果接收到的消息的顺序号是期望的顺序号,但它的响应号不等于上次发送消息的顺序号加1,那么它也是新消息。
回页首
三、用户态如何使用连接器
内核 2.6.14 对 netlink 套接字有新的实现,它缺省情况下不允许用户态应用发送给组号非 1 的netlink 组,因此用户态应用要想使用非1的组,必须先加入到该组,这可以通过如下代码实现:
#ifndef  SOL_NETLINK
#define  SOL_NETLINK 270
#endif
#ifndef  NETLINK_DROP_MEMBERSHIP
#define  NETLINK_DROP_MEMBERSHIP 0
#endif
#ifndef  NETLINK_ADD_MEMBERSHIP
#define  NETLINK_ADD_MEMBERSHIP 1
#endif
int group = 5;
s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
l_local.nl_family = AF_NETLINK;
l_local.nl_groups = group;
l_local.nl_pid = getpid();
if (bind(s, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) == -1) {
perror("bind");
close(s);
return -1;
}
setsockopt(s, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group));
在不需要使用该连接器时使用语句
setsockopt(s, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group));
退出NETLINK_CONNECTOR的group组。
宏 SOL_NETLINK、NETLINK_ADD_MEMBERSHIP 和 NETLINK_DROP_MEMBERSHIP 在旧的系统中并没有定义,因此需要用户显式定义。
内核 2.6.14 的 netlink 代码只允许选择一个小于或等于最大组号的组,对于连接器,最大的组号为CN_NETLINK_USERS + 0xf, 即16,因此如果想使用更大的组号,必须修改CN_NETLINK_USERS 到该大值。增加的 0xf 个号码供非内核态用户使用。因此,组 0xffffffff目前不能使用。

阅读(2397) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~