Chinaunix首页 | 论坛 | 博客
  • 博客访问: 188929
  • 博文数量: 20
  • 博客积分: 543
  • 博客等级: 下士
  • 技术积分: 411
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-06 20:38
文章存档

2012年(12)

2009年(8)

分类: LINUX

2012-03-17 13:53:53

1. Generic Netlink 的数据结构
 
 
首先, Generic Netlink是按照Family管理的,内核的其它模块或子系统可以向它注册自己的Family. 所有的Family会存放在一张Hash链表上。
 
注册Family:
  1. #define NLMSG_MIN_TYPE        0x10    /* < 0x10: reserved control messages */

  2. #define GENL_ID_GENERATE    0
  3. #define GENL_ID_CTRL        NLMSG_MIN_TYPE

  4. #define GENL_MIN_ID    NLMSG_MIN_TYPE
  5. #define GENL_MAX_ID    1023
如果Family ID是GENL_ID_GENERATE(被定义为0),则表示由系统自动分配ID,否则表示指定固定ID。 指定的ID必须在大于0x10并小于等于1023.   0x10被预留给CTRL_FAMILY.
自动分配ID的方法是从0x10开始找到第一个未被分配的ID。可见Family ID是全局唯一的。
Family在Hash表上的位置由ID决定, ID & 0x0F, 也就是ID的低四Bit决定
Family name最好也是全局唯一的, 在genl_family_find_byname(char *name)函数中可见它是找到第一个匹配的name就返回的。
Family的maxattr:根据maxattr的值给struct nlattr ** attrbuf; 分配相应大小的内存。nlattr的作用后面会讲到。
 
每个Family都会有一个operations的列表,可以调用genl_register_ops()函数把各个ops添加到famliy 的ops_list;链表中。每个ops都有一个cmd和相应的Handler.
每个Family通常也都会有一个mc_group的列表。
每个mc_group都隶属于一个Famliy,  同一个Family内的mc_group不能有相同的name,
Name是由用户指定的,但是ID是由系统分配的,在genl_register_mc_group()函数里面会分配ID
分配ID的方法是:在起始地址为mc_groups的内存地方设置一个bitmap, bit0初始化为1, 然后寻找第一个为零的Bit,该Bit的位置即为mc_group的ID, 并把该Bit置为1
这个Bitmap初始化时只有32Bit空间,随着Group数目的增多会动态增大。
可见:mc_group的ID是全局唯一的。
2. The Control Family
 
ctrl_family是一个特殊的Family, 它是由Generic Netlink自己注册和实现,并用来查询Family列表、管理各个Family的添加、删除等事件的。这里以ctrl_family为例看一下注册famly、注册ops、注册mcgroup的方法。

  1. static struct genl_family genl_ctrl = {
  2.     .id = GENL_ID_CTRL,
  3.     .name = "nlctrl",
  4.     .version = 0x2,
  5.     .maxattr = CTRL_ATTR_MAX,
  6.     .netnsok = true,
  7. };
ID是0x10, 名字是nlctrl
它只有一个ops:

  1. static struct genl_ops genl_ctrl_ops = {
  2.     .cmd        = CTRL_CMD_GETFAMILY,
  3.     .doit        = ctrl_getfamily,
  4.     .dumpit        = ctrl_dumpfamily,
  5.     .policy        = ctrl_policy,
  6. };
只有一个mc_group,  名字为notify:

  1. static struct genl_multicast_group notify_grp = {
  2.     .name        = "notify",
  3. };
这个group的ID被预留为0x10,而不是按照通用的规则分配的。
所谓policy, 是用来验证接收到的数据是否合适。 接收到数据由一个属性header(struct nlattr)和真正的数据构成, 对一种属性的数据, 定义一个policy,  如下代码表示如果数据CTRL_ATTR_FAMILY_ID属性的,那么其类型必须是U16.

  1. static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
  2.     [CTRL_ATTR_FAMILY_ID]    = { .type = NLA_U16 },
  3.     [CTRL_ATTR_FAMILY_NAME]    = { .type = NLA_NUL_STRING,
  4.                  .len = GENL_NAMSIZ - 1 },
  5. };
3. 用户空间程序查询Family列表 

分析用户空间程序向ctrl_family发送CTRL_CMD_GETFAMILY命令后,内核的执行流程和向用户程序反送结果的流程。 libmnl-1.0.2里面的genl-family-get.c文件:

  1. nlh = mnl_nlmsg_put_header(buf);
  2.     nlh->nlmsg_type    = GENL_ID_CTRL;
  3.     nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
  4.     nlh->nlmsg_seq = seq = time(NULL);

  5.     genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
  6.     genl->cmd = CTRL_CMD_GETFAMILY;
  7.     genl->version = 1;
可见做法是先构造nlmsghdr再构造genlmsghdr。
Nlmsghdr的nlmsg_type赋值为要接收消息的Family ID,
Genlmsghdr的cmd 赋值为要发送的CMD

  1. mnl_attr_put_u32(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL);
  2.     if (argc >= 2)
  3.         mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, argv[1]);
  4.     else
  5.         nlh->nlmsg_flags |= NLM_F_DUMP;
所谓attr, 意思就是标记要传送的数据是什么数据。
这里填充了两种属性的数据:CTRL_ATTR_FAMILY_ID和CTRL_ATTR_FAMILY_NAME
每种数据都是一个header紧跟真正的数据。如下图:
 
 
发送数据:

  1. if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
  2.         perror("mnl_socket_send");
  3.         exit(EXIT_FAILURE);
  4.     }
内核接收到消息后会交由genl_rcv_msg 函数处理, 该函数找到相应cmd对应的doit函数并执行之。对于CTRL_CMD_GETFAMILY来说,就是ctrl_dumpfamily函数。这个函数构造好回复信息后调用genlmsg_reply发送回复到用户程序。
用户空间程序怎样监听某一个mc_group: 用setsockopt
  1. mnl_socket_setsockopt(nl, NETLINK_ADD_MEMBERSHIP, &grp, sizeof(grp));
需要知道mc_group的ID才行。
阅读(8337) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~