Chinaunix首页 | 论坛 | 博客

分类: BSD

2012-04-16 15:35:45

    上一篇讲了如何创建一个网络监控程序,通过这个实例介绍了如何进行Mac OS内核开发,这一篇将介绍一种在用户层控制内核扩展模块的方法,特殊的Socket通讯。利用socket控制内核模块确实是一个非常简介而且不错的方式,当然如果你以前学习过linux内核开发,那么你肯定最先想到的是写个字符驱动程序来实现这个功能,事实上在Mac OS上利用字符驱动也是可以实现的,但是我们今天介绍的这个方法似乎更简单。

1.内核控制和通知简介

    为了支持用socket的方式来达到内核和用户程序通讯,Mac OS X提供了一个全新的域 –  PF_SYSTEM域,通过这个域可以让用户去配置或者控制一个内核扩展程序。PF_SYSTEM域依次提供了两种协议:SYSPROTO_CONTROL和SYSPROTO_EVENT。Important Network kernel extensions cannot be controlled or programmatically loaded from sandboxed applications.

    内核控制API,使用的就是SYSPROTO_CONTROL协议,提供接口允许应用程序去配置和控制内核扩展程序;

    内核事件API,使用的就是SYSPROTO_EVENT协议,提供接口允许应用程序或者其他内核模块被告知当有确切的内核事件发生时。一般应用在有多客户端需要知道某个内核事件发生的情况下,其设计的意图不是为了针对点对点的通讯,通常当需要双向通讯的时候使用内核控制的方式更合适。

2.在内核扩展模块中支持内核控制API

    内核控制API是在用户空间应用程序和KEXT之间的双向通讯结构,让内核扩展模块支持内核控制相对来说是很简单的(relatively straightforward)。必须在KEXT的开始函数中调用ctl_register函数,注册一个kern_ctl_reg结构。ctl_register函数定义在中,原型如下


点击(此处)折叠或打开

  1. int ctl_register(struct kern_ctl_reg *userctl, kern_ctl_ref *ctlref);

函数说明如下:


Parameters

userkctl

    A structure defining the kernel control to be attached. The ctl_connect callback must be specified, the other callbacks are optional. If ctl_connect is set to zero, ctl_register fails with the error code EINVAL.
kctlref

    Upon successful return, the kctlref will contain a reference to the attached kernel control. This reference is used to unregister the kernel control. This reference will also be passed in to the callbacks each time they are called.

Return Value

0 - Kernel control was registered. EINVAL - The registration structure was not valid. ENOMEM - There was insufficient memory. EEXIST - A controller with that id/unit is already registered.
Discussion

Register a kernel control. This will enable clients to connect to the kernel control using a PF_SYSTEM socket.

 

struct kern_ctl_reg说明如下:

Fields ctl_name

A Bundle ID string of up to MAX_KCTL_NAME bytes (including the ending zero). This string should not be empty.

ctl_id

The control ID may be dynamically assigned or it can be a 32-bit creator code assigned by DTS. For a DTS assigned creator code the CTL_FLAG_REG_ID_UNIT flag must be set. For a dynamically assigned control ID, do not set the CTL_FLAG_REG_ID_UNIT flag. The value of the dynamically assigned control ID is set to this field when the registration succeeds.

ctl_unit

A separate unit number to register multiple units that share the same control ID with DTS assigned creator code when the CTL_FLAG_REG_ID_UNIT flag is set. This field is ignored for a dynamically assigned control ID.

ctl_flags

CTL_FLAG_PRIVILEGED and/or CTL_FLAG_REG_ID_UNIT.

ctl_sendsize

Override the default send size. If set to zero, the default send size will be used, and this default value is set to this field to be retrieved by the caller.

ctl_recvsize

Override the default receive size. If set to zero, the default receive size will be used, and this default value is set to this field to be retrieved by the caller.

ctl_connect

Specify the function to be called whenever a client connects to the kernel control. This field must be specified.

ctl_disconnect

Specify a function to be called whenever a client disconnects from the kernel control.

ctl_send

Specify a function to handle data send from the client to the kernel control.

ctl_setopt

Specify a function to handle set socket option operations for the kernel control.

ctl_getopt

Specify a function to handle get socket option operations for the kernel control.

Discussion

This structure defines the properties of a kernel control being registered.

需要特别说明的是,在kern_ctl_reg结构中用了ctl_name,ctl_id,ctl_unit三个字段来描述这个控件的唯一标识,其中前两者ctl_id, ctl_name 能够在多个控件中被共享使用.真正唯一的与相关控件一一对应的标识是ctl_unit。一个控件允许用同一个ctl_id多次注册,但是在不同的实例中必须使用不同的ctl_unit标识,对于自动分配的控件唯一标识,这个域的值将会自动填充。

Note:  You may use either a registered Creator ID (available from the Apple Developer Creator ID web page at http://developer.apple.com/dev/cftype/) or you may use a dynamically-assigned ID.

It is strongly recommended that you use a dynamically-assigned ID. This is the default behavior. In that case, the memory referenced by the ctl_id field will be overwritten with the dynamically-generated ID value when ctl_register returns.

If you need to use a registered ID, you must set the CTL_FLAG_REG_ID_UNIT flag in ctl_flags. If this flag is set, the value of ctl_name will be ignored.

当函数成功返回,第二个参数 ctlref将会包含一个引用指向这个已注册的内核控件.这个引用必须用于取消注册这个控件, 并且将作为参数贯穿在许多回调函数中.当内核控制器接受到来自用户空间进程的连接的时候,控制器的ctl_connect_func回调函数将会被调用,在这个函数中,你需要根据连接判断出关联的单元号码,这样,之后你才能发送数据回到连接过来的用户进程.因此你需要创建一个数据结构(任由你选择)去存储相关的连接信息数据, 并且把这个结构通过函数传入的参数void** handle返回出去,在其他的回调函数中将会使用到.

接下来,用户进程就可以用socket上的getsockopt,setsockopt,read/recv.write/send等函数来操作了,但是除了recv函数例外,每一个socket上的函数调用都会对应到内核控件的控制器上对应的回调函数上,ctl_getopt_func,ctl_setopt_func,以及ctl_send等.当用户进程关闭一个到内核控制器通讯socket时,ctl_disconnection_func回调函数将会被调用,此时应该释放连接相关连的分配的所有资源。

下面将会给出一个简单的例子,这个例子将会演示具体如何使用

4.内核代码片段


点击(此处)折叠或打开

  1. errno_t error;

  2. struct kern_ctl_reg ep_ctl; // Initialize control

  3. kern_ctl_ref kctlref;

  4. bzero(&ep_ctl, sizeof(ep_ctl)); // sets ctl_unit to 0

  5. ep_ctl.ctl_id = 0; /* OLD STYLE: ep_ctl.ctl_id = kEPCommID; */

  6. ep_ctl.ctl_unit = 0;

  7. strcpy(ep_ctl.ctl_name, "org.mklinux.nke.foo");

  8. ep_ctl.ctl_flags = CTL_FLAG_PRIVILEGED & CTL_FLAG_REG_ID_UNIT;

  9. ep_ctl.ctl_send = EPHandleWrite;

  10. ep_ctl.ctl_getopt = EPHandleGet;

  11. ep_ctl.ctl_setopt = EPHandleSet;

  12. ep_ctl.ctl_connect = EPHandleConnect;

  13. ep_ctl.ctl_disconnect = EPHandleDisconnect;

  14. error = ctl_register(&ep_ctl, &kctlref);

  15.  

  16. /* A simple setsockopt handler */

  17. errno_t EPHandleSet( kern_ctl_ref ctlref, unsigned int unit, void *userdata, int opt, void *data, size_t len )

  18. {

  19.     int error = EINVAL;

  20. #if DO_LOG

  21.     log(LOG_ERR, "EPHandleSet opt is %d\n", opt);

  22. #endif

  23.  

  24.     switch ( opt )

  25.     {

  26.         case kEPCommand1: // program defined symbol

  27.             error = Do_First_Thing();

  28.             break;

  29.  

  30.         case kEPCommand2: // program defined symbol

  31.             error = Do_Command2();

  32.             break;

  33.     }

  34.     return error;

  35. }

  36.  

  37. /* A simple A simple getsockopt handler */

  38. errno_t EPHandleGet(kern_ctl_ref ctlref, unsigned int unit, void *userdata, int opt, void *data, size_t *len)

  39. {

  40.     int error = EINVAL;

  41. #if DO_LOG

  42.     log(LOG_ERR, "EPHandleGet opt is %d *****************\n", opt);

  43. #endif

  44.     return error;

  45. }

  46.  

  47. /* A minimalist connect handler */

  48. errno_t

  49. EPHandleConnect(kern_ctl_ref ctlref, struct sockaddr_ctl *sac, void **unitinfo)

  50. {

  51. #if DO_LOG

  52.     log(LOG_ERR, "EPHandleConnect called\n");

  53. #endif

  54.     return (0);

  55. }

  56.  

  57. /* A minimalist disconnect handler */

  58. errno_t

  59. EPHandleDisconnect(kern_ctl_ref ctlref, unsigned int unit, void *unitinfo)

  60. {

  61. #if DO_LOG

  62.     log(LOG_ERR, "EPHandleDisconnect called\n");

  63. #endif

  64.     return;

  65. }

  66.  

  67. /* A minimalist write handler */

  68. errno_t EPHandleWrite(kern_ctl_ref ctlref, unsigned int unit, void *userdata, mbuf_t m, int flags)

  69. {

  70. #if DO_LOG

  71.     log(LOG_ERR, "EPHandleWrite called\n");

  72. #endif

  73.     return (0);

  74. }

5.客户端代码片段


点击(此处)折叠或打开

  1. struct sockaddr_ctl addr;

  2.     int ret = 1;

  3.  

  4.     fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);

  5.     if (fd != -1) {

  6.         bzero(&addr, sizeof(addr)); // sets the sc_unit field to 0

  7.         addr.sc_len = sizeof(addr);

  8.         addr.sc_family = AF_SYSTEM;

  9.         addr.ss_sysaddr = AF_SYS_CONTROL;

  10. #ifdef STATIC_ID

  11.         addr.sc_id = kEPCommID; // should be unique - use a registered Creator ID here

  12.         addr.sc_unit = kEPCommUnit; // should be unique.

  13. #else

  14.         {

  15.             struct ctl_info info;

  16.             memset(&info, 0, sizeof(info));

  17.             strncpy(info.ctl_name, MYCONTROLNAME, sizeof(info.ctl_name));

  18.             if (ioctl(fd, CTLIOCGINFO, &info)) {

  19.                 perror("Could not get ID for kernel control.\n");

  20.                 exit(-1);

  21.             }

  22.             addr.sc_id = info.ctl_id;

  23.             addr.sc_unit = 0;

  24.         }

  25. #endif

  26.  

  27.         result = connect(fd, (struct sockaddr *)&addr, sizeof(addr));

  28.         if (result) {

  29.            fprintf(stderr, "connect failed %d\n", result);

  30.         }

  31.     } else { /* no fd */

  32.             fprintf(stderr, "failed to open socket\n");

  33.     }

  34.  

  35.     if (!result) {

  36.         result = setsockopt( fd, SYSPROTO_CONTROL, kEPCommand1, NULL, 0);

  37.         if (result){

  38.             fprintf(stderr, "setsockopt failed on kEPCommand1 call - result was %d\n", result);

  39.         }

  40.     }

6.内核通知

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