使用mac进行linux内核开发,4. Mac OS 内核开发 - 内核模块控制和通知

上一篇讲了如何创建一个网络监控程序,通过这个实例介绍了如何进行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函数定义在中,原型如下

点击(此处)折叠或打开

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.内核代码片段

点击(此处)折叠或打开

errno_t error;

struct kern_ctl_reg ep_ctl; // Initialize control

kern_ctl_ref kctlref;

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

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

ep_ctl.ctl_unit = 0;

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

ep_ctl.ctl_flags = CTL_FLAG_PRIVILEGED & CTL_FLAG_REG_ID_UNIT;

ep_ctl.ctl_send = EPHandleWrite;

ep_ctl.ctl_getopt = EPHandleGet;

ep_ctl.ctl_setopt = EPHandleSet;

ep_ctl.ctl_connect = EPHandleConnect;

ep_ctl.ctl_disconnect = EPHandleDisconnect;

error = ctl_register(&ep_ctl, &kctlref);

/* A simple setsockopt handler */

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

{

int error = EINVAL;

#if DO_LOG

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

#endif

switch ( opt )

{

case kEPCommand1: // program defined symbol

error = Do_First_Thing();

break;

case kEPCommand2: // program defined symbol

error = Do_Command2();

break;

}

return error;

}

/* A simple A simple getsockopt handler */

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

{

int error = EINVAL;

#if DO_LOG

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

#endif

return error;

}

/* A minimalist connect handler */

errno_t

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

{

#if DO_LOG

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

#endif

return (0);

}

/* A minimalist disconnect handler */

errno_t

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

{

#if DO_LOG

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

#endif

return;

}

/* A minimalist write handler */

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

{

#if DO_LOG

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

#endif

return (0);

}

5.客户端代码片段

点击(此处)折叠或打开

struct sockaddr_ctl addr;

int ret = 1;

fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);

if (fd != -1) {

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

addr.sc_len = sizeof(addr);

addr.sc_family = AF_SYSTEM;

addr.ss_sysaddr = AF_SYS_CONTROL;

#ifdef STATIC_ID

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

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

#else

{

struct ctl_info info;

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

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

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

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

exit(-1);

}

addr.sc_id = info.ctl_id;

addr.sc_unit = 0;

}

#endif

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

if (result) {

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

}

} else { /* no fd */

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

}

if (!result) {

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

if (result){

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

}

}

6.内核通知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值