简介
sysctl可以直接查询内核参数,获取重要的运行时诊断信息,在某种情况下,修改参数的值可以改变内核行为,但是在内核大量的参数中,只有少量的参数是可以通过sysctl修改的。
为什么sysctl可以修改内核参数呢?
sysctl命令对sysctl函数调用的封装,在用户空间调用sysctl()函数时,会触发一个软中断(trap),将控制权交给操作系统内核。
在内核中,有一些与sysctl()相关的处理函数,它们负责解析传递的参数,执行的相应的操作,并将结果返回给用户程序。
在C语言中,sysctl()函数的原型如下:
perl
/// 用于查询和修改系统参数
/// - Parameters:
/// - name: 一个整型数组,指定了要查询或修改的系统参数
/// - namelen: 指定了参数数组 name 的长度
/// - oldp: 用于存储查询到的参数值的缓冲区
/// - oldlenp: 指向一个整型变量的指针,用于传递 oldp 缓冲区的大小,并在调用结束后返回实际读取的参数值的大小
/// - newp: 用于传递新值的缓冲区,用于修改系统参数
/// - newlen: 指定了新值缓冲区 newp 的大小
如何在用户程序中使用sysctl功能呢
要想使用sysctl函数,首先就得了解name参数,它是一个整形数组,它通常有一系列的整数值组成,每个整数值代表一个特定的系统参数或者系统信息,这些整数值按照特定的规则构成一个层级结构,用于区分不同类型的参数和信息。
这些整数值一般被称为OID(Object Identifer),它们以特定的数字编码来标识系统中的不同信息。在BSD系统中,有一个OID树(Object Identifier Tree),其中的每个节点代表一个特定的系统参数或信息,通过沿着树的路径来访问和修改系统参数。
在 BSD 系统中,OID 一般由一系列数字组成,比如 1.3.6.1.2.1 这样的形式。这些数字代表着不同层级的节点,每个节点都有特定的含义和对应的系统参数。不同的操作系统版本可能有不同的 OID 命名规则和层级结构。 最顶级的名称空间如下:
名称空间 | 值 | 用途 |
---|---|---|
CTL_KERN | 1 | 用于访问关于内核的参数和信息,比如系统信息、内核配置、进程和调度等 |
CTL_VM | 2 | 用于访问虚拟内存子系统相关的参数和信息,比如内存使用、页表信息等。 |
CTL_VFS | 3 | 用于访问虚拟文件系统(VFS)的参数和信息,比如文件系统类型、挂载点等。 |
CTL_NET | 4 | 用于访问网络相关的参数和信息,比如网络接口、路由表等。 |
CTL_DEBUG | 5 | 用于访问调试相关的参数和信息,比如内核调试选项、调试日志等。 |
CTL_HW | 6 | 用于访问硬件相关的参数和信息,比如系统硬件信息、CPU信息等。 |
CTL_MACHDEP | 7 | 用于访问机器相关的参数和信息,根据不同的硬件平台可能有不同的子项。 |
CTL_USER | 8 | 用于用户定义的参数和信息 |
CTL_MAXID | 9 | 标识 sysctl 类型的数量,用于循环遍历所有的类型。 |
二级名称查看sys/sysctl.h文件里面的定义吧,不想写了!!举个代码例子吧
ini
static struct kinfo_proc* get_proc_list(size_t* proc_count) {
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
size_t size;
struct kinfo_proc* proc_list;
int st;
st = sysctl(mib, 4, NULL, &size, NULL, 0);
if (st == -1) {
return NULL;
}
proc_list = (struct kinfo_proc*)malloc(size);
st = sysctl(mib, 4, proc_list, &size, NULL, 0);
if (st == -1) {
free(proc_list);
return NULL;
}
*proc_count = size / sizeof(struct kinfo_proc);
return proc_list;
}
如何在内核中使用sysctl功能呢
在内核中sysctl是通过sysctl_oid数据维护的,这个数据结构定义在Kernel.framework中的<sys/sysctl.h>中
代码如下:
c
struct sysctl_oid {
struct sysctl_oid_list * OS_PTRAUTH_SIGNED_PTR("sysctl_oid.oid_parent") oid_parent;
SLIST_ENTRY(sysctl_oid) oid_link;
int oid_number;
int oid_kind;
void *oid_arg1;
int oid_arg2;
const char *oid_name;
int (*oid_handler)SYSCTL_HANDLER_ARGS;
const char *oid_fmt;
const char *oid_descr; /* offsetof() field / long description */
int oid_version;
int oid_refcnt;
};
的sysctl_oid可以通过宏定义SYSCTL_PROC创建,这个宏定义了新的sysctl_oid、初始化其字段、并且进行链接器相关的设置。还有了一些宏定义定义在这个宏之上,方便了一些操作如下表:
sysctl宏 | 用途 |
---|---|
SYSCTL_DECL | 声明一个顶级名称空间,跟CTL_KERN类似 |
SYSCTL_OID | 用于定义 sysctl 树结构中的节点,指定节点的名称、类型、访问权限等信息,原始OID。很少直接使用。根据这个表中的 SYSCTL_* 常量,可以将类型指定为 "N""A"、"T"、"TU"、"L"或"Q" |
SYSCTL_NODE | 用于创建 sysctl 节点,允许在 sysctl 树中添加新的节点。 |
SYSCTL_STRING | 用于创建一个表示字符串的 sysctl 变量,调用sysctl_handle_string()处理 |
SYSCTL_COMPAT_INT SYSCTL_INT | 用于创建一个兼容模式下的整数类型的 sysctl 参数。这个宏允许在不同系统版本之间保持参数的兼容性,并提供了一种方法来处理参数值的变化或不同实现之间的差异 |
SYSCTL_COMPAT_UINT SYSCTL_UINT | 用于创建一个兼容模式下的无符号整数类型的 sysctl 参数。这个宏允许在不同系统版本之间保持参数的兼容性,并提供了一种方法来处理参数值的变化或不同实现之间的差异 |
SYSCTL_LONG | 用于创建一个 long 类型的 sysctl 参数,允许读取和写入长整型数据。使用sysctl_handle_long()处理 |
SYSCTL_QUAD | 用于创建一个 quad 类型的 sysctl 参数,这是一个 64 位整数类型,调用sysctl_handle_quad()处理程序 |
SYSCTL_OPAQUE | 创建一个不透明的(opaque)sysctl 参数,可以用于存储不定长度的数据块,这些数据块的内容不会被 sysctl 解释。调用sysctl_handle_opaque()处理程序 |
SYSCTL_STRUCT | 创建一个结构体类型的 sysctl 参数,允许定义一个复杂的数据结构,可以包含多个字段和信息,调用sysctl_handle_opaque()处理程序 |
SYSCTL_PROC | 创建一个处理器(procedure)类型的 sysctl 参数,允许注册一个函数,用于处理特定的 sysctl 请求,调用者指定自定义的处理程序 |
SYSCTL_PROC宏的作用是声明叶子节点的处理程序,也就是用户发出sysctl请求时,内核调用的回调函数,定义自己的处理程序是一件非常简单的事,只需要以下的两个步骤:
1.定义调用处理程序的SYSCTL_NODE节点
scss
//parant:_kern、_debug或自己通过SYSCTL_DECL(myname)定义的顶层名称,
//nbr:OID_AUTP,请求内核进行oid分配
//name:自己提供的名称
//access:访问标识位:CTLFLAG *,按位OR
//handler,0 处理程序
//descr:描述文本
SYSCTL_NODE(parent, nbr, name, access, handler, descr)
2.定义处理程序要实现的实际sysctl叶子节点,这里有两种方法
- 使用表 14-5 中定义的一种类型。这样会安插一个默认的处理程序,开发者只需要指定保存sysctl 数据的变量即可。然而,使用这种方法的缺点在于:当变量值被读取或发生变化时收不到回调通知。这些固定类型的宏都差不多。例如,如果想要一个整型变量,可以使用以下代码:
scss
//parent:步骤(1)中创建或使用的节点
//nbr:OID_AUTO:不需要考虑编号,内核自动分配
//name:叶子节点的名称
//access:CTL_ *标志位,_RW、_ANYEODY
//ptr:保存这个数据的地址
//val:如果ptr为NULL则需要这个值,如果使用了这个值,则叶子节点为只读
//descr:描述性文本
SYSCTL_INT(parent, nbr, name, access, ptr, val, descr);
- b 将叶子节点定义为SYSCTL_PROC,指定自定义处理函数,如下
scss
//parent:步骤(1)中创建或使用的节点
//nbr:OID_AUTO:不需要考虑编号,内核自动分配
//name:叶子节点的名称
//access:CTL_*标志位:_RW、_AWXBODY 等
//ptr:指向变量数据的指针
//arg:处理程序的参数
//handler:指向自定义处理程序的指针
//fmt:"A"、"工"和"IU"等格式描述符
//descr:描述性文本
SYSCTL_PROC(parent, nbr, name, access, ptr, arg, handler, fmt, descr)
后一种方法的好处在于当用户针对 sysctl 进行任何操作时能够获得通知。这一点有点像 Linux,在Linux 中,pproc 和/sys 文件系统处理程序能监听这些导出数据的访问和变化情况,当这些数据被访问时可以执行一些操作。
最后调用sysctl_register_oid函数注册自定义的变量
举个例子如下:
objectivec
int sysctl_arg = 0;
//实现处理函数
static int sysctl_hideproc SYSCTL_HANDLER_ARGS {
}
//注册处理函数
SYSCTL_PROC(_hw, OID_AUTO, hideprocess,CTLTYPE_INT|CTLFLAG_ANYBODY|CTLFLAG_WR,
&sysctl_arg, 0, &sysctl_hideproc , "I", "Hide a process");
//注册了新的sysctl变量,hw.hideprocess
sysctl_register_oid(&sysctl__hw_hideprocess);