本站提倡有节制游戏,合理安排游戏时间,注意劳逸结合。

【手机版算命源码】【618京东叠蛋糕源码】【倍量过左峰源码】linuxregister源码

2024-11-20 14:43:29 来源:热点 分类:热点

1.linux内核通信核心技术:Netlink源码分析和实例分析
2.Linux字符设备驱动编写基本流程
3.Linux内核通知链机制的原理及实现
4.linux 5.15 ncsi源码分析
5.linux e1000 网卡驱动代码分析
6.主次设备号应用

linuxregister源码

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非常实用。618京东叠蛋糕源码

       链接:内核通信之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-rc

       1. 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回调,实现调频流程。

相关推荐
一周热点