1.linux内核通信核心技术:Netlink源码分析和实例分析
2.Linux字符设备驱动编写基本流程
3.Linux内核通知链机制的原理及实现
4.linux 5.15 ncsi源码分析
5.linux e1000 网卡驱动代码分析
6.主次设备号应用
linux内核通信核心技术:Netlink源码分析和实例分析
Linux内核通信核心技术:Netlink源码分析和实例分析
什么是netlink?Linux内核中一个用于解决内核态和用户态交互问题的机制。相比其他方法,netlink提供了更安全高效的交互方式。它广泛应用于多种场景,例如路由、用户态socket协议、源码测试与审计防火墙、netfilter子系统等。
Netlink内核代码走读:内核代码位于net/netlink/目录下,包括头文件和实现文件。头文件在include目录,提供了辅助函数、宏定义和数据结构,对理解消息结构非常有帮助。关键文件如af_netlink.c,其中netlink_proto_init函数注册了netlink协议族,使内核支持netlink。
在客户端创建netlink socket时,使用PF_NETLINK表示协议族,SOCK_RAW表示原始协议包,NETLINK_USER表示自定义协议字段。sock_register函数注册协议到内核中,以便在创建socket时使用。
Netlink用户态和内核交互过程:主要通过socket通信实现,包括server端和client端。netlink操作基于sockaddr_nl协议套接字,nl_family制定协议族,nl_pid表示进程pid,nl_groups用于多播。消息体由nlmsghdr和msghdr组成,用于发送和接收消息。内核创建socket并监听,用户态创建连接并收发信息。
Netlink关键数据结构和函数:sockaddr_nl用于表示地址,nlmsghdr作为消息头部,msghdr用于用户态发送消息。内核函数如netlink_kernel_create用于创建内核socket,netlink_unicast和netlink_broadcast用于单播和多播。
Netlink用户态建立连接和收发信息:提供测试例子代码,代码在github仓库中,可自行测试。核心代码包括接收函数打印接收到的消息。
总结:Netlink是一个强大的内核和用户空间交互方式,适用于主动交互场景,如内核数据审计、安全触发等。早期iptables使用netlink下发配置指令,但在iptables后期代码中,使用了iptc库,核心思路是使用setsockops和copy_from_user。对于配置下发场景,netlink非常实用。量柱过前三天源码
链接:内核通信之Netlink源码分析和实例分析
Linux字符设备驱动编写基本流程
---简介
Linux下的MISC简单字符设备驱动虽然使用简单,但却不灵活。
只能建立主设备号为的设备文件。字符设备比较容易理解,同时也能够满足大多数简单的硬件设备,字符设备通过文件系 统中的名字来读取。这些名字就是文件系统中的特殊文件或者称为设备文件、文件系统的简单结点,一般位于/dev/目录下 使用ls进行查看会显示以C开头证明这是字符设备文件crw--w---- 1 root tty 4, 0 4月 : tty0。第一个数字是主设备 号,第二个数字是次设备号。
---分配和释放设备编号
1)在建立字符设备驱动时首先要获取设备号,为此目的的必要的函数是register_chrdev_region,在linux/fs.h中声明:int register_chrdev_region(dev_t first, unsigned int count, char *name);first是你想要分配的起始设备编号,first的次编号通 常是0,count是你请求的连续设备编号的总数。count如果太大会溢出到下一个主设备号中。name是设备的名字,他会出 现在/proc/devices 和sysfs中。操作成功返回0,如果失败会返回一个负的错误码。
2)如果明确知道设备号可用那么上一个方法可行,否则我们可以使用内核动态分配的设备号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name);dev是个只输出的参数,firstminor请求的第一个要用的次 编号,count和name的作用如上1)对于新驱动,最好的方法是进行动态分配
3)释放设备号,void unregister_chrdev_region(dev_t first unsigned int count);
---文件操作file_operations结构体,内部连接了多个设备具体操作函数。该变量内部的函数指针指向驱动程序中的具体操 作,没有对应动作的指针设置为NULL。
1)fops的第一个成员是struct module *owner 通常都是设置成THIS_MODULE。
linux/module.h中定义的宏。用来在他的操作还在被使用时阻止模块被卸载。
2)loff_t (*llseek) (struct file *, loff_t, int);该方法用以改变文件中的当前读/写位置
返回新位置。
3)ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);该函数用以从设备文件
中读取数据,读取成功返回读取的字节数。
4)ssize_t (*write) (struct file *, const char __user *,size_t , loff_t *);该函数用以向设备
写入数据,如果成功返回写入的字节数。
5)int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);ioctl系统调用提供
发出设备特定命令的方法。
6)int (*open) (struct inode *, struct file *);设备文件进行的第一个操作,打开设备文件。
7)int (*release) (struct inode *, struct file *);释放文件结构函数指针。
一般初始化该结构体如下:
struct file_operations fops = {
.owner = THIS_MODULE, .llseek = xxx_llseek, .read = xxx_read, .write = xxx_write,
.ioctl = xxx_ioctl, .open = xxx_open, .release = xxx_release };
PS:以上的文件操作函数指针并不是全部,只是介绍了几个常用的操作。
---文件结构
struct file定义在linux/fs.h中,是设备驱动中第二个最重要的数据结构,此处的file和
用户空间程序中的FILE指针没有关系。前者位于内核空间,后者位于用户控件。
文件结构代表一个打开的文件。(他不特定给设备驱动;系统中每个打开的文件
有一个关联的struct file在内核空间)。它由内核在open时创建,源码编辑器简单的小游戏并可以传递给文件件
操作函数,文件关闭之后,内核释放数据结构。
1)mode_t f_mode。确定文件读写模式
2)loff_t f_ops。当前读写位置
3)unsigned int f_flags 。文件标志,O_RDONLY、O_NONBLOCK,
4)struct file_operations *f_op。关联文件相关操作
5)void *private_data。open系统调用设置该指针NULL,指向分配的数据。
6)struct dentry *f_dentry。关联到文件的目录入口dentry结构。
---inode结构
inode结构由内核在内部用来表示文件。它和代表打开文件描述符的文件结构是不
同的。inode结构包含大量关于文件的信息。作为通用规则,这个结构只有两个成
员对驱动代码有作用。
dev_t i_rdev。对于代表设备文件的节点,这个成员包含实际的设备编号。
struct cdev *i_cdev。内核内部结构,代表字符设备。
---字符设备注册
在内核调用你的设备操作前,你编写分配并注册一个或几个struct cdev.
struct cdev *my_cdev = cdev_alloc(); my_cdev-ops = my_fops;
或者定义成static均可。
对定义的cdev变量进行初始化,可以使用专门的函数,或者使用如上的方法。
cdev_init( my_cdev, my_fops); 其实上边的两行代码就是做了这个函数的工作。
最后告诉内核该cdev。
cdev_add(struct cdev *dev, dev_t num, unsigned int count);
/*上述总结,到此关于设备文件相关的结构数据以及如何注册销毁等操作相关的
函数基本上都已经介绍完毕。主要的还是要设计具体操作的函数来实现具体的
逻辑操作*/
以下代码整理、摘录自《Android深度探索HAL与驱动开发-李宁》LED驱动篇
#include
#include
#include
#include
#include
#include
#include
#deifne DEVICE_NAME "s3c_leds"
#define DEVICE_COUNT 1
#define S3C_LEDS_MAJOR 0
#define S3C_LEDS_MINOR
#define PARAM_SIZE 3
static int major = S3C_LEDS_MAJOR;
static int minor = S3C_LEDS_MINOR;
static dev_t dev_number;
static int leds_state = 1;
static char *params[] = { "string1","string2","string3"};
static iint param_size = PARAM_SIZE;
static struct class *leds_class = NULL;
static int s3c_leds_ioctl (struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
unsigned tmp;
case 0:
case 1:
if (arg 4)
return -EINVAL;
tmp = ioread (S3CXX_GPMDAT);
if (cmd == 1)
tmp = (~(1 arg));
else
tmp |= (1 arg);
iowrite (tmp, S3CXX_GPMDAT);
return 0;
default : return -EINVAL;
}
}
static ssize_t s3c_leds_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned tmp = count;
unsigned long i = 0;
memset(mem, 0, 4);
if (count 4)
tmp = 4;
if (copy_from_user (mem, buf, tmp) )
return -EFAULT;
else{
for( i=0; i4; i++)
{
tmp = ioread(S3CXX_GPMDAT);
if (mem[i] == '1')
tmp = (~(1 i));
else
tmp |= (1 i);
iowrite(tmp, S3CXX_GPMDAT);
}
return count;
}
}
static struct file_operations dev_fops =
{ .owner = THIS_MODULE, .unlocked_ioctl = s3c_leds_ioctl, .write = s3c_leds_write};
static struct cdev leds_cdev;
static int leds_create_device(void)
{
int ret = 0;
int err = 0;
cdev_init (leds_cdev, dev_fops);
leds_cdev.owner = THIS_MODULE;
if (major 0)
{
dev_number = MKDEV(major,minor);
err = register_chrdev_region(dev_number, DEVICE_COUNT, DEVICE_NAME);
if (err 0)
{
printk(KERN_WANRING "register_chrdev_region errorn");
return err
}
}
else{
err = alloc_chrdev_region(leds_cdev.dev, , DEVICE_COUNT, DEVICE_NAME);
if(err 0)
{
printk (KERN_WARNING "alloc_chrdev_region errorn");
return err;
}
major = MAJOR(leds_cdev.dev);
major = MINOR(leds_cdev.dev);
dev_number = leds_cdev.dev;
}
ret = cdev_add(leds_cdev,dev_number, DEVICE_COUNT);
leds_class = class_create (THIS_MODULE, DEVICE_NAME);
device_create (leds_class, NULL, dev_number, NULL, DEVICE_NAME);
return ret;
}
static void leds_init_gpm(int leds_default){
int tmp = 0;
tmp = ioread(S3CXX_GPMCON);
tmp = (~0xffff);
tmp |= 0x;
iowrite(tmp,S3CXX_GPMCON);
tmp = ioread(S3CXX_GPMPUD);
tmp = (~0XFF);
tmp |= 0xaa;
iowrite(tmp,S3CXX_GPMPUD);
tmp = ioread(S3CXX_GPMDAT);
tmp = (~0xf);
tmp |= leds_default;
iowrite(tmp, S3CXX_GPMDAT);
}
static leds_init( void)
{
int ret;
ret = leds_create_device();
leds_init_gpm (~leds_state);
printk(DEVICE_NAME"tinitializedn");
return ret;
}
static void leds_destroy_device(void)
{
device_destroy(leds_class, dev_number);
if(leds_class)
class_destroy(leds_class);
unregister_chrdev_region(dev_number, DEVICE_NAME);
}
static void leds_exit(void)
{
leds_destroy_device();
printk(DEVICE_NAME"texitn");
}
module_init(leds_init);
module_exit(leds_exit);
module_param(leds_state, int, S_IRUGO|S_IWUSR);
module_param_array(params, charp, ?m_size, S_IRUGO|S_IWUSR);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lining");
Linux内核通知链机制的原理及实现
Linux内核的通信机制——通知链详解
为了实现内核子系统之间的事件传递,Linux设计了一种通知链机制。它允许子系统在发生特定事件时,通过调用预先注册的函数,通知其他相关子系统。通知链是一种内核内部的函数链,每个节点代表一个注册的回调函数,当事件触发时,所有节点的回调都会被执行。
通知链的核心数据结构包括notifier_call函数指针,next用于链接节点,priority决定事件执行的顺序,而notifier_block{ }则按照优先级排序。变量名通常采用xxx_chain和xxx_nofitier_chain的形式。虽然有额外资源分享,用源码编辑器编游戏视频如群组提供的学习资料,但这里专注于核心机制讲解。
运作机制包含两个角色:被通知者(通过notifier_chain_register注册回调)和通知者(notifier_call_chain发送事件)。被通知者在事件发生时调用相关函数,通知者则遍历链并执行回调。注销函数为notifier_chain_unregister,用于从链中移除回调。参数val表示事件类型,v传递事件相关的数据,nr_to_call控制通知范围。
举例来说,通过编写代码实现通知链,首先定义头节点和注册函数,接着在regchain.c中添加节点并注册函数,最后在notify.c中发送事件以激活链中的回调。Makefile用于编译和加载模块,运行时即可观察到通知链的工作效果。
总结,Linux内核的通知链机制是实现子系统间事件通信的关键工具,通过注册、触发和回调,确保了内核事件的高效传递和处理。
linux 5. ncsi源码分析
深入剖析Linux 5. NCSI源码:构建笔记本与BMC通信桥梁 NCSI(Network Configuration and Status Interface),在5.版本的Linux内核中,为笔记本与BMC(Baseboard Management Controller)以及服务器操作系统之间的同网段通信提供了强大支持。让我们一起探索关键的NCSI网口初始化流程,以及其中的关键结构体和函数。1. NCSI网口初始化:驱动注册
驱动程序初始化始于ftgmac_probe,这是关键步骤,它会加载并初始化struct ncsi_dev_priv,包含了驱动的核心信息,如NCSI_DEV_PROBED表示最终的拓扑结构,NCSI_DEV_HWA则启用硬件仲裁机制。关键结构体剖析
struct ncsi_dev_priv包含如下重要字段:
request表,记录NCSI命令的执行状态;
active_package,存储活跃的package信息;
NCSI_DEV_PROBED,表示连接状态的最终拓扑;
NCSI_DEV_HWA,启用硬件资源的仲裁功能。
命令与响应的承载者
struct ncsi_request是NCSI命令和结果的核心容器,包含请求ID、待处理请求数、channel队列以及package白名单等。每个请求都包含一个唯一的ID,用于跟踪和管理。数据包管理与通道控制
从struct ncsi_package到struct ncsi_channel,每个通道都有其特定状态和过滤器设置。multi_channel标志允许多通道通信,channel_num则记录总通道数量。例如,struct ncsi_channel_mode用于设置通道的工作模式,如NCSI_MODE_LINK表示连接状态。商品橱窗小程序源码怎么弄发送与接收操作
struct ncsi_cmd_arg是发送NCSI命令的关键结构,包括驱动私有信息、命令类型、ID等。在ncsi_request中,每个请求记录了请求ID、使用状态、标志,以及与网络链接相关的详细信息。ncsi_dev_work函数:工作队列注册与状态处理
在行的ncsi_register_dev函数中,初始化ncsi工作队列,根据网卡状态执行通道初始化、暂停或配置。ncsi_rcv_rsp处理NCSI报文,包括网线事件和命令响应,确保通信的稳定和高效。扩展阅读与资源
深入理解NCSI功能和驱动probe过程,可以参考以下文章和资源:Linux内核ncsi驱动源码分析(一)
Linux内核ncsi驱动源码分析(二)
华为Linux下NCSI功能切换指南
NCSI概述与性能笔记
浅谈NCSI在Linux的实现和应用
驱动probe执行过程详解
更多技术讨论:OpenBMC邮件列表和CSDN博客
通过以上分析,NCSI源码揭示了如何构建笔记本与BMC的高效通信网络,为开发者提供了深入理解Linux内核NCSI模块的关键信息。继续探索这些资源,你将能更好地运用NCSI技术来优化你的系统架构。
linux e 网卡驱动代码分析
深入分析Linux e 网卡驱动代码,以Linux 4.版本的e_main.c为参考。
首先,e网卡作为PCI设备,通过pci_register_driver进行注册。
e_probe函数是关键,它实现了一系列操作,如配置netdev操作函数e_netdev_ops。
注册中断流程包括:e_request_irq注册中断,e_intr执行中断处理,__napi_schedule将poll_list挂到CPU,__raise_softirq_irqoff触发接收软中断。
网卡up配置操作则涉及:e_open打开设备,e_setup_all_tx_resources配置tx资源,e_power_up_phy使能phy芯片,e_configure进行网卡配置,e_set_rx_mode设置接收模式,e_alloc_rx_buffers分配接收缓冲区。
环形缓冲区(ring buff)用于存放数据,描述符(desc)存储指向缓冲区的指针,count表示大小,可动态调整。
收包流程:申请skb,DMA接收并触发软中断,处理并释放DMA映射区,重新申请skb。
发包流程:用户态发送数据,内核生成skb,e_xmit_frame进行发送,申请DMA映射并送入发送队列,发送完毕触发中断解除映射。
网卡初始化时,内核会进行一系列打印操作,用于监控初始化状态。
主次设备号应用
在Linux系统中,未使用devfs时,驱动程序的添加通常需要为其分配一个主设备号。这一过程应在驱动程序初始化阶段完成,具体通过`register_chrdev`函数实现,该函数定义在`fs.h>`中,如下所示:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
该函数返回值指示操作结果:负值表示错误,零或正值表示成功。major参数是所需的主设备号,name是设备的名称,会在/proc/devices中显示,fops是一个指向函数队列的指针,用于设备操作函数的调用。
主设备号是一个整数,用于标识静态字符设备组,选择合适的主设备号会在后续章节详细讨论。早期的2.0内核支持个设备驱动,而2.2和2.4内核扩展到了个(保留0和给未来),但次版本号(8位)不在register_chrdev函数中传递,由驱动程序自用。随着内核扩展,2.5版本的目标是至少支持位设备号。
设备驱动注册后,其操作与分配的主设备号紧密关联。内核通过file_operations结构体,根据设备的主设备号查找并调用相应的驱动函数。因此,传递给register_chrdev的指针应该是指向全局驱动结构体,而非局部模块初始化函数。
为了请求设备驱动,程序需要一个名字,这个名字会与设备的主设备号和次设备号组合形成/dev目录下的节点。创建设备节点的命令是mknod,需以超级用户权限执行,例如:
mknod /dev/scull0 c 0
这创建了一个字符设备,主设备号为,次设备号为0。次设备号通常在0-范围内,但由于历史原因,目前仍存在8位限制。
值得注意的是,一旦使用mknod创建的设备文件,除非明确删除,否则会永久保留在硬盘上。要删除如上例子中的设备,可以使用rm命令:
rm /dev/scull0
linux加解密框架(二)算法注册
本文基于以下软硬件假定:
架构:AARCH
内核版本:5..0-rc5
内核对加解密算法的管理
加解密算法可分为对称算法、非对称算法、hash算法、mac算法以及aead算法等不同的种类。对称算法包括aes、des、3des、sm4、rc4、blowfish等,hash算法包括md5、sha1、sha、sha、sha3、sm3等。
同一种算法可以有多种不同的实现,如aes算法可以由纯软件实现,可以由cpu架构的加解密指令加速,还可以通过独立的加解密硬件加速等。此外,有些算法还有不同的加解密模式,基础加解密算法可以与相关的模式进一步组合,形成新的算法实例。
为了支持以上需求,内核分别为其抽象出了算法类型、普通算法、基础算法以及算法模板。其中算法类型用于表示某一类算法,如对称算法使用crypto_skcipher结构体表示,hash算法用crypto_ahash表示等。内核统一使用crypto_alg结构体表示所有的算法实现,并用一个全局链表来管理所有注册到crypto core中的crypto_alg,以下为其示例结构:
内核使用算法名(cra_name)来表示某一特定的算法,而使用算法驱动名(cra_driver_name)来表示算法的特定实现。不同的crypto_alg可以含有相同的cra_name,但其cra_driver_name是唯一的。用户可以使用cra_name或cra_driver_name来调用相关算法,为了在用户使用cra_name参数时选择最优的算法实现,内核对每种crypto_alg都定义了一个优先级。当给定cra_name还有多种不同实现时,则选择其中优先级最高的算法实现。
内核还支持基础对称加解密算法(指该算法在加解密时只操作单个block,而不涉及与block数据组合相关的加解密模式)。它们可通过算法模板(template),与不同的算法模式组合成完整的加解密算法。如aes算法可与ecb、cbc、ctr模式分别组合成ecb(aes)、cbc(aes)和ctr(aes)算法,des算法也可与ecb、cbc、ctr模式组合为ecb(des)、cbc(des)和ctr(des)算法等。内核通过特定的标志CRYPTO_ALG_TYPE_CIPHER来标识这类算法。
最后,为了保证注册算法的正确性,当新算法被注册到crypto core之前,内核会先对其进行自测。为此在自测成功之前,这些算法会由crypto_larval管理,它包含了larval算法和adult算法,当自测成功后,才将adult算法标记为tested,并销毁其对应的larval算法。
数据结构
2.1 crypto_alg结构体
该结构体是加解密算法的基础结构体,主要用于描述算法的通用属性,如算法名称、算法驱动名称、分组大小、数据对齐值等。当一个算法被注册到加解密核心中时,crypto_alg将会通过该节点挂到全局的算法链表crypto_alg_list上。此后,加解密核心就可以通过遍历该链表查找该算法。
2.2 skcipher_alg结构体
以下为该结构体的定义:
设置密钥回调函数、加密回调函数、解密回调函数、算法初始化回调函数、算法退出回调函数、算法支持的最短密钥长度、算法支持的最长密钥长度、算法支持的iv长度、对于块密码算法,该值等于block size、若算法在并行处理多个块时效率更高,则为chunksize的整数倍,否则等于chunksize、基础算法结构体。
2.3 crypto_larval结构体
该结构体在算法注册时用于其自测流程,其结构体定义如下:
算法注册时的临时算法结构体、算法注册时最终使用的实际算法结构体、用于算法自测同步操作的完成量。
加解密算法注册流程
加解密算法注册的主要工作为将对应算法加入全局算法链表中,并触发算法自测流程。由于在自测完成之前,该算法还不能被调用,因此会为其分配一个crypto_larval结构体,用于其在自测时的管理。
3.1 算法校验
crypto_check_alg函数用于校验待注册算法的合法性,如其对齐值、block size等是否位于合理范围。
3.2 __crypto_register_alg实现
该函数首先检查该算法是否已注册,且已注册算法链表中是否有重名的算法,若检查通过则为其分配并初始化一个算法自测使用的crypto_larval结构体。然后将该算法与larval临时算法都添加到全局算法链表中。
3.3 crypto_wait_for_test流程
该函数会通过通知方式触发该算法的自测流程,然后等待自测完成。
3.4 算法自测流程
加解密核心通过cryptomgr_init函数注册加解密算法相关的通知,该通知主要用于触发算法自测和模板示例创建工作。
4 特定算法类型的注册接口
由于不同算法类型会在crypto_alg基础上封装其自身的算法结构体,因此它们也相应地封装了其对应的算法注册接口,我们在注册某特定算法时,通常都是使用该算法类型对应的注册接口完成。
5 基础对称加解密算法注册
基础对称加解密算法指单次加密或解密操作固定为一个block的加解密算法,该算法一般会通过加解密模板,与特定的加解密模式功能生成组合算法。
以内核中的通用aes加解密基础算法aes-generic为例,其注册流程如下:
指示该算法为基础cipher算法、设置基础cipher算法的属性和回调函数。其中crypto_aes_encrypt和crypto_aes_decrypt在单次调用中只能加解密一个block。
linux cpu管理(七) cpu动态调频实现
本文基于以下软硬件假定: 架构:AARCH 内核版本:5..0-rc1. cpufreq设备注册流程
注册cpufreq设备需使用platform_device_register_data()函数。以cpufreq-dt设备为例,其注册流程需满足条件:soc名称位于allowlist中或cpu节点包含operating-points-v2属性,且soc名称不在blocklist中。 若需为新平台添加cpufreq设备,只需将soc名添加至allowlist列表,或在dts中正确配置cpu频点,避免soc名入blocklist。 若设备需实现intermediate或suspend、resume回调,则需通过platform设备的data成员传递私有数据,该数据格式需预先定义。2. cpufreq驱动初始化
驱动初始化流程包括:对每个cpu执行初始化流程,根据dts中cpu的opp配置初始化频点数据;解析私有数据并设置至驱动结构体;注册dt-cpufreq驱动。2.1 subsys_interface机制介绍
内核支持子系统动态添加或移除特定功能,subsys_interface机制支持此特性。其注册流程包括获取接口对应的subsys、加入接口至interface链表、初始化iter遍历设备、调用add_dev回调。 add_dev回调定义在subsys_interface结构体中,包含所属subsys、add_dev和remove_dev回调函数。2.2 cpufreq_interface添加流程
cpufreq作为cpu子功能,其interface通过cpufreq_register_driver()-->subsys_interface_register()注册。添加流程涉及遍历cpu节点,执行cpufreq_add_dev函数,初始化cpufreq_policy结构体和sysfs属性文件。 cpufreq_online流程根据policy状态和cpu当前在线状态,执行初始化和添加操作,包括政策结构体初始化、调用相关回调函数、sysfs文件创建等。 新policy初始化包括分配结构体、cpu添加至mask、设置私有数据、计算调频范围、初始化相关参数、创建sysfs目录、发送相关通知、读取当前频率、创建sysfs属性文件、初始化统计数据等。3. cpufreq governor
cpufreq governor根据特定算法计算待切换频率,支持performance、powersave、userspace、ondemand、conservative和schedutil六种。performance直接设置最高频率,powersave最低,userspace设置用户指定频率。 其他governor动态调节频率,特点包括:ondemand快速响应负载变化,保守调整;conservative平稳调整频率,减少频繁升降频;schedutil利用调度器负载值实现更准确、高效的调频。3.1 governor初始化流程
初始化cpufreq_governor和dbs_governor结构体,实现特定governor的回调函数和私有数据管理。以ondemand为例,初始化包括分配结构体、初始化工作队列、设置相关参数等。3.2 governor启动流程
启动governor包括初始化负载计算参数、注册调频回调至调度子系统。以ondemand为例,涉及计算相关参数、调用启动回调函数、注册调频回调至调度子系统。3.3 governor调频触发流程
governor调频触发由调度子系统触发,如cfs调度器在进程入队时调用cpufreq_update_util函数,最终执行特定governor的gov_dbs_update回调,实现调频流程。