获取和设置内核参数-sysctl

1.前言

sysctl是一种用户应用来设置和获得运行时内核的配置参数的一种有效方式,通过这种 方式,用户应用可以在内核运行的任何时刻来改变内核的配置参数,也可以在任何时候获得内核的配置参数,通常,内核的这些配置参数也出现在proc文件系统 的/proc/sys目录下,用户应用可以直接通过这个目录下的文件来实现内核配置的读写操作。

例如,用户可以通过使用sysctl -w修改内核参数,没有选项表示读内核配置参数,用户可以使用 sysctl -a 来读取所有的内核配置参数。

sysctl -w vm.block_dump = 1

设置在dmesg信息中打印进程写入块的数量。参数 vm.block_dump 实际被转换到对应的 proc 文件/proc/sys/vm/block_dump,等同于

echo 1 > /proc/sys/vm/block_dump

2. 内核模块中使用sysctl

内核模块在文件 sysctl-exam-kern.c 中实现,在该内核模块中,每一个 sysctl 条目对应一个 struct ctl_table 结构。Sysctl 条目也可以是目录,此时 mode 字段应当设置为 0555,否则通过 sysctl 系统调用将无法访问它下面的 sysctl 条目,child 则指向该目录条目下面的所有条目,对于在同一目录下的多个条目,不必一一注册,用户可以把它们组织成一个 struct ctl_table 类型的数组,然后一次注册就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// /include/linux/sysctl.h
struct ctl_table
{
int ctl_name; /* Binary ID */
const char *procname; /* Text ID for /proc/sys, or zero 表示在proc/sys/下显示的文件名称 */
void *data;//表示对应于内核中的变量名称
int maxlen;// 表示条目允许的最大长度
mode_t mode;//条目在proc文件系统下的访问权限
struct ctl_table *child;//子条目
struct ctl_table *parent; /* Automatically set 父级条目*/
proc_handler *proc_handler; /* Callback for text formatting 回调函数,对于整型内核变量,应当设置为&proc_dointvec,而对于字符串内核变量,则设置为 &proc_dostring*/
ctl_handler *strategy; /* Callback function for all r/w */
void *extra1;
void *extra2;
};

2.1 注册register_sysctl_table

注册sysctl条目使用函数register_sysctl_table,函数原型如下:

struct ctl_table_header *register_sysctl_table(struct ctl_table *table)

参数为定义的struct ctl_table结构的sysctl条目或条目数组指针;

2.2 卸载unregister_sysctl_table

当模块卸载时,需要使用函数unregister_sysctl_table,其原型:

void unregister_sysctl_table(struct ctl_table_header * header)

其中struct ctl_table_header是通过函数register_sysctl_table注册时返回的结构体指针。

2.3 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//sysctl-exam-kern.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sysctl.h>

static int sysctl_kerexam_data = 1024;

//处理函数
static int kerexam_callback(ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int rc;
int *data = table->data;

printk(KERN_INFO "before value = %d\n", *data);

rc = proc_dointvec(table, write, buffer, lenp, ppos);
if (write)
printk(KERN_INFO "write operation, current value = %d\n", *
data);
else
printk(KERN_INFO "read operation, current value = %d\n", *
data);
return rc;
}

static struct ctl_table kerexam_ctl_table[] = {
{
.procname = "kerexam",
.data = &sysctl_kerexam_data,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = kerexam_callback,
},
{
/* sub parameter */
},
};

static struct ctl_table_header *sysctl_header;

static int __init sysctl_example_init(void)
{
sysctl_header = register_sysctl_table(kerexam_ctl_table);
if (sysctl_header == NULL) {
printk(KERN_INFO "ERR: register_sysctl_table!");
return -1;
}

printk(KERN_INFO "sysctl register success.\n");
return 0;

}

static void __exit sysctl_example_exit(void)
{
unregister_sysctl_table(sysctl_header);
printk(KERN_INFO "sysctl unregister success.\n");
}

module_init(sysctl_example_init);
module_exit(sysctl_example_exit);
MODULE_LICENSE("GPL");

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

BASEINCLUDE ?= /usr/src/2.6.32-21-generic

oops-objs := sysctl-exam-kern.o
KBUILD_CFLAGS +=-g -O0


obj-m := sysctl-exam-kern.o
all :
$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(shell pwd) modules;

install:
$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(shell pwd) modules_install;



clean:
$(MAKE) -C $(BASEINCLUDE) SUBDIRS=$(shell pwd) clean;
rm -f *.ko;

2.4 运行模块

程序运行的内核版本是2.6.32-21-generic,要能够加载内核模块,需要开启系统的可加载模块支持Enable loadable module support,在内核源码根目录执行

make menuconfig

进行配置。

在这里插入图片描述

在insmod模块的时候遇到如下错误,no symbol version for module_layout。

在这里插入图片描述

原因是/usr/src/linux-source-[version]/目录下缺少文件Module.symvers,
查看 /usr/src/linux-headers-[version]-generic,发现里面有 Module.symvers,将其直接cp到/usr/src/linux-source-[version]/,之后重新make并且insmod sysctl-exam-kern.ko成功。

使用函数register_sysctl_table注册完成后,可以在/proc/sys/目录下看到用户定义的参数条目kerexam。

在这里插入图片描述

使用sysctl kerexam查看kerexam条目的默认值。

在这里插入图片描述

使用sysctl -w kerexam=2048 设置kerexam条目的值。查看dmesg打印日志如下。

在这里插入图片描述

3.内核函数分析

使用Ftrace跟踪sysctl kerexam命令,函数如下,vfs_read函数调用了proc文件系统的proc_sys_read,调用proc_sys_call_handler函数,该函数通过grab_header函数获取struct ctl_table_header,PROC_I(inode)->sysctl_entry;获取struct ctl_table,通过table->proc_handler调用自定义的处理函数kerexam_callback。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

0) | sys_read() {
0) 0.000 us | fget_light();
0) | vfs_read() {
0) | rw_verify_area() {
0) | security_file_permission() {
0) 0.000 us | apparmor_file_permission();
0) 0.000 us | }
0) 0.000 us | }
0) | proc_sys_read() {
0) | proc_sys_call_handler() {
0) | grab_header() {
0) | sysctl_head_grab() {
0) 0.000 us | _spin_lock();
0) 0.000 us | }
0) 0.000 us | }
0) | sysctl_perm() {
0) | security_sysctl() {
0) 0.000 us | apparmor_sysctl();
0) 0.000 us | }
0) 0.000 us | }
0) | kerexam_callback() {

参数含义

1. nr_requests

/sys/block/sda/queue
IO调度队列大小,
254

2. queue_depth

/sys/block/sda/device
磁盘队列深度,
32

3. read_ahead_kb

这个块设备上的文件系统预读的最大kb数。

128

4. max_sectors_kb

Block 层将允许文件系统请求的最大 kb 数。此值为读写。此值必须小于或等于max_hw_sectors_kb值。
1280

5. max_hw_sectors_kb

单个数据传输中支持的最大 kb 数。
4096

6. max_segments

设备的最大段数。
128

7. max_discard_segments

1

8. max_integrity_segments

0 当读取时,该文件显示由硬件控制器可以处理的块层设置的完整性段的最大限制。

9. max_segment_size

65536
设备的最大段大小。

10. scheduler

mq-deadline
当读取时,该文件将显示此块设备的当前和可用的IO调度程序。当前活动的IO调度器将被括在[]括号中。将IO调度器名称写入此文件将把此块设备的控制权切换到新的IO调度器。请注意,如果IO调度器模块还没有出现在系统中,那么向该文件写入IO调度器名称将尝试加载该IO调度器模块。

11. hw_sector_size

这是设备的硬件扇区大小,单位是字节。
512

12. logical_block_size

这是设备的逻辑块大小,以字节为单位。
512

13. physical_block_size

512
这是设备的物理块大小,以字节为单位。

14. chunk_sectors

0 根据块设备的类型,这具有不同的含义。
对于 RAID 设备(dm-raid),chunk_sectors 表示以 512B 扇区为单位的大小
RAID 卷条带段。 对于分区块设备,无论是主机感知
或主机管理,chunk_sectors 表示区域的 512B 扇区大小
设备的最后一个区域除外
可能更小。

15. minimum_io_size

512
这是设备报告的最小优选IO大小。

16. optimal_io_size

0 这是设备报告的最佳IO大小。

17. discard_granularity

0 如果该设备报告了内部分配的大小(以字节为单位)。值为“0”表示设备不支持丢弃功能。

18. discard_max_hw_bytes

0 Devices that support discard functionality may have internal limits on
the number of bytes that can be trimmed or unmapped in a single operation.
The discard_max_bytes parameter is set by the device driver to the maximum
number of bytes that can be discarded in a single operation. Discard
requests issued to the device must not exceed this limit. A discard_max_bytes
value of 0 means that the device does not support discard functionality.

支持丢弃功能的设备可能对单个操作中可删除或未映射的字节数有内部限制。discard max bytes参数由设备驱动程序设置为单个操作中可以丢弃的最大字节数。发送给设备的丢弃请求不能超过这个限制。丢弃最大字节值为0表示设备不支持丢弃功能。

19. discard_max_bytes

0 当丢弃最大hw字节是设备的硬件限制时,这个设置是软件限制。有些设备在发出大的丢弃时显示出较大的延迟,将此值设置得更低将使Linux发出更小的丢弃,并可能有助于减少由大的丢弃操作引起的延迟。

20. discard_zeroes_data

0

21. write_same_max_bytes

0 这是设备可以在单个写入中写入的字节数-相同
命令。 值 ‘0’ 表示不支持 write-same
设备。

22. write_zeroes_max_bytes

0

23. rotational

1 该文件用于统计设备是旋转类型还是非旋转类型。

24. zoned

none
这表明该设备是否是分区块设备,并且该设备的区域模型
设备,如果它确实被分区。 zoned 表示的可能值是
常规块设备为“无”,分区为“主机感知”或“主机管理”
块设备。 主机感知和主机管理的分区块的特征
ZBC(分区块命令)和 ZAC 中描述了设备
(分区设备 ATA 命令集)标准。 这些标准还定义了
“驱动器管理”区域模型。 但是,由于驱动器管理的分区块设备
不支持 zone 命令,它们将被视为常规块设备
和 zoned 将报告“无”。

25. nomerges

0 这使用户能够禁用与 IO 相关的查找逻辑
在块层合并请求。 默认情况下 (0) 所有合并都是
启用。 当设置为 1 时,只会尝试简单的一击合并。 什么时候
设置为 2 不会尝试合并算法(包括一击或多次
复杂的树/哈希查找)。

26. rq_affinity

1

27. iostats

1 此文件用于控制(开/关)iostats 统计功能。

28. add_random

1 这个参数允许关闭磁盘熵贡献。这个文件的默认值是’1’(开启)。

29. io_poll

0 读取时,该文件显示轮询是启用(1)还是禁用(0)。向该文件写入’0’将禁用该设备的轮询。写入任何非零值都将启用此特性。

30. io_poll_delay

-1
如果启用轮询,这将控制哪种轮询
执行。 它默认为 -1,这是经典轮询。 在这种模式下,
CPU 会反复要求完成而不放弃任何时间。
如果设置为 0,则使用混合轮询模式,内核将尝试
对 IO 何时完成进行有根据的猜测。 基于此
猜测,内核会让发出 IO 的进程休眠一段时间
在进入经典轮询循环之前的时间。 这种模式可能是
比纯经典轮询慢一点,但会更有效率。
如果设置为大于 0 的值,内核将把进程发出
IO 在进入经典之前休眠此微秒数
轮询。

31. write_cache

write through
当读取时,该文件将显示设备是否启用了回写缓存。对于前一种情况,它将返回“回写”,对于后一种情况,它将返回“透写”。写入此文件可以改变设备的内核视图,但不会改变设备状态。这意味着将设置从“回写”切换为“透写”可能不安全,因为这也会消除内核发出的缓存刷新。

32. dax

0 该文件表明设备是否支持直接访问(DAX),由cpu可寻址存储使用,以绕过页面缓存。如果为真,则显示’1’,否则显示’0’。

33. wbt_lat_usec

75000
如果设备已注册回写限制,则此文件显示
目标最小读取延迟。 如果在给定的时间内超过此延迟
时间窗口(参见 wb_window_usec),然后写回节流将开始
缩减写入。 向该文件写入值 ‘0’ 将禁用
特征。 将值“-1”写入此文件会将值重置为
默认设置。

相关代码:

读取或者设置sys目录下参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

//sysfs.go
type Sysfs struct {

Option string
}

func NewSysfs() *Sysfs {
return &Sysfs{
Option: "/sys",
}
}

func (this *Sysfs)Get(key string) string {

data,err :=ioutil.ReadFile(utils.Format("%s/%s",this.Option,key))
if err != nil {
fmt.Println("文件打开失败。")
return "-1"
}
pattern := regexp.MustCompile(".*\\[(.*)\\].*")
searchObj := pattern.FindStringSubmatch(string(data))

if searchObj != nil {
return searchObj[1]
}
return string(data)
}
func (this *Sysfs)Set(key string,value interface{}) {
var format string
switch value.(type){
case string:
format = "%s"
break
case int:
format = "%d"
break
default:
fmt.Println("pramater is vaild!")
return
}
fp,e := os.OpenFile(utils.Format("%s/%s",this.Option,key), os.O_RDWR|os.O_TRUNC, 0666) //打开文件
if e != nil {
fmt.Println("open file error!", e)
}

_, err := io.WriteString(fp, fmt.Sprintf(format, value))
if err != nil {
fmt.Println("set parm error!", err)
}
}

读取采样后的参数空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//collect.go


func ReadCsv() {

fileName := "./src/data/samples.csv"
fs, err := os.Open(fileName)
if err != nil {
log.Fatalf("can not open the file, err is %+v", err)
}
defer fs.Close()
r := csv.NewReader(fs)

for {
row, err := r.Read()
if err != nil && err != io.EOF {
log.Fatalf("can not read, err is %+v", err)
}
if err == io.EOF {
break
}
fmt.Println(row)
}

}

从数据库查询参数的配置路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//arg_dao.go

type Arg struct {
id int64
name string
value string
valType int8
path string
}

func (this * Arg)toString() string{

return "id:"+string(this.id)+",name:"+this.name+",value:"+this.value+",valType:"+string(this.valType)+",path:"+this.path
}


//查询操作
func Query() {
var arg Arg
rows, e := db.DB.Query("select id,name,value,val_type,path from args")
if e == nil {
errors.New("query incur error")
}
for rows.Next() {
e := rows.Scan(&arg.id,&arg.name, &arg.value,&arg.valType, &arg.path )
if e != nil {
fmt.Println(arg.toString())
}
}
rows.Close()
//db.DB.QueryRow("select * from arg where id=1").Scan(arg.age, arg.id, arg.name, arg.phone, arg.sex)
//
//stmt, e := db.DB.Prepare("select * from arg where id=?")
//query, e := stmt.Query(1)
//query.Scan()
}

参数数据表设计:

1
2
3
4
5
6
7
8
9
DROP TABLE IF EXISTS `args`;
CREATE TABLE `args` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '默认值',
`val_type` int(0) NULL DEFAULT NULL COMMENT '离散取值(0)或者连续取值(1)',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '设置参数的文件系统路径',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

PS:前往公众号“Linux工坊”,可以查看最新内容!关注即可免费领取面试&Linux技术书籍!

------ 本文结束------
  • 文章标题: 获取和设置内核参数-sysctl
  • 本文作者: 你是我的阳光
  • 发布时间: 2021年06月13日 - 20:21:35
  • 最后更新: 2022年11月07日 - 16:45:00
  • 本文链接: https://szp2016.github.io/Linux/获取和设置内核参数-sysctl/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
0%