对linux rcu机制的深入理解
- 格式:docx
- 大小:61.98 KB
- 文档页数:5
rcu stall 原理
RCU(Read-Copy-Update)是一种用于实现并发读取和写入的同步机制。
在多线程或多核处理器系统中,RCU可以提供高效的读取操作,同时保证写入操作的一致性。
然而,RCU也会出现一种现象,即RCU Stall。
RCU Stall是指当系统负载过高时,RCU的性能会受到影响,导致读取操作的延迟增加。
这是因为RCU Stall会导致读取操作的等待时间变长,从而降低了系统的响应速度。
RCU Stall的原因主要有两个方面。
首先,当系统负载过高时,CPU 资源会被占用,导致RCU上下文切换的频率增加。
这会导致读取操作的延迟增加,从而引发RCU Stall。
其次,当系统中存在大量的写入操作时,RCU需要等待所有的读取操作完成后才能进行写入操作。
这样一来,读取操作的延迟也会增加,进而导致RCU Stall的发生。
为了解决RCU Stall的问题,可以采取一些措施。
首先,可以通过增加CPU资源来缓解系统负载过高的情况。
这样可以减少RCU上下文切换的频率,从而提高读取操作的性能。
其次,可以通过优化写入操作的方式来减少RCU Stall的发生。
例如,可以采用批量写入的方式,将多个写入操作合并为一个批量操作,从而减少读取操作的等待时间。
RCU Stall是一种影响RCU性能的现象,它会导致读取操作的延迟增
加。
通过增加CPU资源和优化写入操作的方式,可以减少RCU Stall 的发生,提高系统的响应速度。
在设计和实现并发读写操作时,需要注意避免RCU Stall的问题,以确保系统的性能和可靠性。
Linux网络基础知识TCP/IP通讯协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。
这4层分别为:应用层:应用程序间沟通的层,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。
传输层:在此层中,它提供了节点间的数据传送服务,如传输控制协议(TCP)、用户数据报协议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。
网络层:负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收),如网际协议(IP)。
网络接口层(网络接口层例如以太网设备驱动程序):对实际的网络媒体的管理,定义如何使用实际网络(如Ethernet、Serial Line等)来传送数据。
网络接口层在发送端将上层的IP数据报封装成帧后发送到网络上;数据帧通过网络到达接收端时,该结点的网络接口层对数据帧拆封,并检查帧中包含的MAC地址。
如果该地址就是本机的MAC地址或者是广播地址,则上传到网络层,否则丢弃该帧。
网络接口层可细分为数据链路层和物理层,数据链路层实际上就是网卡的驱动程序,物理层实际上就是布线、光纤、网卡和其它用来把两台网络通信设备连接在一起的东西。
链路层,有时也称作数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。
它们一起处理与电缆(或其他任何传输媒介)的物理接口细节。
网卡驱动程序主要实现发送数据帧与接受数据帧的功能,发送数据帧采用内核函数hard_start_xmit();接收数据帧采用内核函数netif_rx();网卡驱动程序主要是分配设置及注册net_dev结构体;数据帧的载体采用sk-buff结构体。
用浏览网页为例:发送方:1.输入网址:,按了回车键,电脑使用应用层用IE浏览器将数据从80端口发出,给了下一层协议——传输层。
linux内核进程cpu调度基本原理Linux内核的CPU调度基本原理是通过多任务处理,将CPU 时间片分配给不同的进程或线程来实现。
1. 调度策略:Linux内核支持多种调度策略,包括先来先服务(FCFS)、时间片轮转、优先级调度等。
默认的调度策略是时间片轮转调度策略,即每个进程被分配一个时间片,在时间片用完之后,将CPU切换到下一个就绪状态的进程上。
2. 就绪队列:内核会维护一个就绪队列,存放所有准备好运行但还未分配CPU时间的进程。
根据进程的优先级和调度策略,内核会从就绪队列中选择一个合适的进程来执行。
3. 进程优先级:每个进程都有一个优先级值,表示其重要性和紧急程度。
较高优先级的进程在调度时会获得更多的CPU时间。
Linux内核使用动态优先级调度策略,根据进程的历史行为和资源使用情况动态调整进程的优先级。
4. 时间片和抢占:时间片是CPU分配给进程的最小单位,当一个进程的时间片用完后,如果它还未完成,内核会将其置于就绪队列末尾,并将CPU分配给下一个就绪进程。
此外,Linux 内核支持抢占式调度,即当一个优先级更高的进程出现时,可立
即抢占当前运行的进程,将CPU资源分配给新的进程。
5. 实时进程:除了普通进程,Linux内核还支持实时进程。
实时进程具有更高的优先级和较小的延迟要求,它们得到更快的响应时间。
实时进程的调度算法相对于普通进程更加严格,以满足实时性要求。
Linux内核的CPU调度基本原理是通过就绪队列、进程优先级和时间片轮转等策略,将CPU时间动态地分配给不同的进程或线程,以完成多任务处理。
linux tcp默认拥塞控制算法TCP(Transmission Control Protocol,传输控制协议)是一种可靠的、面向连接的网络协议,用于在IP网络上进行数据传输。
在Linux系统上,TCP默认使用的拥塞控制算法主要有Reno、Cubic和BBR。
1. Reno算法:Reno算法是TCP最早的拥塞控制算法之一,它是基于丢包的拥塞控制算法。
Reno算法使用了两个阈值来控制发送速率,分别是慢启动阈值和拥塞避免阈值。
在慢启动阶段,发送方每经过一个往返时间RTT (Round Trip Time),就将拥塞窗口大小加倍,这样就能快速适应网络带宽。
一旦出现拥塞,就会触发拥塞避免阶段,发送速率会缓慢增长。
当发生丢包时,发送方会认为发生了拥塞,将拥塞窗口大小减半。
2. Cubic算法:Cubic算法是在Reno算法的基础上进行改进的,主要解决了Reno算法的不足之处。
Cubic算法使用了一个拟合曲线来估计网络的拥塞程度,并根据该拟合曲线调整发送速率。
Cubic算法中的拥塞控制机制是基于时间的,通过跟踪拥塞窗口的快速增长速率来判断网络的拥塞程度。
当网络发生拥塞时,拥塞窗口的增长速率会变得缓慢,从而降低发送速率。
3. BBR算法:BBR(Bottleneck Bandwidth and RTT)算法是Google开发的一种最新的拥塞控制算法,主要用于提高网络的传输效率。
BBR算法通过测量网络的带宽和往返时间来估计网络的拥塞程度,并根据拥塞程度调整发送速率。
BBR算法的特点是能够更精确地估计网络的拥塞程度,从而避免了过度拥塞和欠拥塞的情况,提高了网络的传输速度和稳定性。
总结:Linux TCP默认的拥塞控制算法主要有Reno、Cubic和BBR。
Reno 算法是基于丢包的拥塞控制算法,使用了慢启动和拥塞避免两个阈值来控制发送速率。
Cubic算法在Reno算法的基础上进行改进,使用拟合曲线来估计网络的拥塞程度,并根据拥塞程度调整发送速率。
Android初始化语言包含了四种类型的声明:Actions(行动)、Commands(命令)、Services(服务)和Options(选项)。
所有这些都是以行为单位的,各种记号由空格来隔开。
C语言风格的反斜杠号可用于在记号间插入空格。
双引号也可用于防止字符串被空格分割成多个记号。
行末的反斜杠用于折行。
注释行以井号(#)开头(允许以空格开头)。
Actions和Services声明一个新的分组。
所有的命令或选项都属于最近申明的分组。
位于第一个分组之前的命令或选项将会被忽略。
Actions和Services有唯一的名字。
如果有重名的情况,第二个申明的将会被作为错误忽略。
(???我们是否应该以覆盖来代替忽略) Actions(行动) ---------- Actions其实就是一序列的Commands(命令)。
Actions都有一个trigger(触发器),它被用于决定action的执行时间。
当一个符合action触发条件的事件发生时,action会被加入到执行队列的末尾,除非它已经在队列里了。
队列中的每一个action都被依次提取出,而这个action中的每个command(命令)都将被依次执行。
Init在这些命令的执行期间还控制着其他的活动(设备节点的创建和注销、属性的设置、进程的重启)。
Actions的形式如下: on 《trigger》 《command》 《command》 《command》 Services(服务) ---------- Services(服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。
Services(服务)的形式如下: service 《name》《pathname》 [ 《argument》 ]* 《option》 《option》 ... Options(选项) ---------- Options(选项)是一个Services(服务)的修正者。
一:前言RCU机制出现的比较早,只是在linux kernel中一直到2.5版本的时候才被采用.关于RCU机制,这里就不做过多的介绍了,网上有很多有关RCU介绍和使用的文档.请自行查阅.本文主要是从linux kernel源代码的角度.来分析RCU的实现.在讨论RCU的实现之前.有必要重申以下几点:1:RCU使用在读者多而写者少的情况.RCU和读写锁相似.但RCU的读者占锁没有任何的系统开销.写者与写写者之间必须要保持同步,且写者必须要等它之前的读者全部都退出之后才能释放之前的资源.2:RCU保护的是指针.这一点尤其重要.因为指针赋值是一条单指令.也就是说是一个原子操作.因它更改指针指向没必要考虑它的同步.只需要考虑cache的影响.3:读者是可以嵌套的.也就是说rcu_read_lock()可以嵌套调用.4:读者在持有rcu_read_lock()的时候,不能发生进程上下文切换.否则,因为写者需要要等待读者完成,写者进程也会一直被阻塞.以下的代码是基于linux kernel 2.6.26二:使用RCU的实例Linux kernel中自己附带有详细的文档来介绍RCU,这些文档位于linux-2.6.26.3/Documentation/RCU. 这些文档值得多花点时间去仔细研读一下.下面以whatisRCU.txt中的例子作为今天分析的起点:struct foo {int a;char b;long c;};DEFINE_SPINLOCK(foo_mutex);struct foo *gbl_foo;void foo_update_a(int new_a){struct foo *new_fp;struct foo *old_fp;new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);spin_lock(&foo_mutex);old_fp = gbl_foo;*new_fp = *old_fp;new_fp->a = new_a;rcu_assign_pointer(gbl_foo, new_fp);spin_unlock(&foo_mutex);synchronize_rcu();kfree(old_fp);}int foo_get_a(void){int retval;rcu_read_lock();retval = rcu_dereference(gbl_foo)->a;rcu_read_unlock();return retval;}如上代码所示,RCU被用来保护全局指针struct foo *gbl_foo. foo_get_a()用来从RCU保护的结构中取得gbl_foo的值.而foo_update_a()用来更新被RCU保护的gbl_foo的值.另外,我们思考一下,为什么要在foo_update_a()中使用自旋锁foo_mutex呢?假设中间没有使用自旋锁.那foo_update_a()的代码如下:void foo_update_a(int new_a){struct foo *new_fp;struct foo *old_fp;new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL);old_fp = gbl_foo;1:-------------------------*new_fp = *old_fp;new_fp->a = new_a;rcu_assign_pointer(gbl_foo, new_fp);synchronize_rcu();kfree(old_fp);}假设A进程在上图----标识处被B进程抢点.B进程也执行了goo_ipdate_a().等B执行完后,再切换回A进程.此时,A进程所持的old_fd实际上已经被B进程给释放掉了.此后A进程对old_fd的操作都是非法的.另外,我们在上面也看到了几个有关RCU的核心API.它们为别是:rcu_read_lock()rcu_read_unlock()synchronize_rcu()rcu_assign_pointer()rcu_dereference()其中,rcu_read_lock()和rcu_read_unlock()用来保持一个读者的RCU临界区.在该临界区内不允许发生上下文切换.rcu_dereference():读者调用它来获得一个被RCU保护的指针.Rcu_assign_pointer():写者使用该函数来为被RCU保护的指针分配一个新的值.这样是为了安全从写者到读者更改其值.这个函数会返回一个新值三:RCU API实现分析Rcu_read_lock()和rcu_read_unlock()的实现如下:#define rcu_read_lock() __rcu_read_lock()#define rcu_read_unlock() __rcu_read_unlock()#define __rcu_read_lock() \do { \preempt_disable(); \__acquire(RCU); \rcu_read_acquire(); \} while (0)#define __rcu_read_unlock() \do { \rcu_read_release(); \__release(RCU); \preempt_enable(); \} while (0)其中__acquire(),rcu_read_read_acquire(),rcu_read_release(),rcu_read_release()都是一些选择编译函数,可以忽略不可看.因此可以得知.rcu_read_lock(),rcu_read_unlock()只是禁止和启用抢占.因为在读者临界区,不允许发生上下文切换.rcu_dereference()和rcu_assign_pointer()的实现如下:#define rcu_dereference(p) ({ \typeof(p) _________p1 = ACCESS_ONCE(p); \smp_read_barrier_depends(); \(_________p1); \})#define rcu_assign_pointer(p, v) \({ \if (!__builtin_constant_p(v) || \((v) != NULL)) \smp_wmb(); \(p) = (v); \})它们的实现也很简单.因为它们本身都是原子操作.因为只是为了cache一致性,插上了内存屏障.可以让其它的读者/写者可以看到保护指针的最新值.synchronize_rcu()在RCU中是一个最核心的函数,它用来等待之前的读者全部退出.我们后面的大部份分析也是围绕着它而进行.实现如下:void synchronize_rcu(void){struct rcu_synchronize rcu;init_completion(&pletion);/* Will wake me after RCU finished */call_rcu(&rcu.head, wakeme_after_rcu);/* Wait for it */wait_for_completion(&pletion);}我们可以看到,它初始化了一个本地变量,它的类型为struct rcu_synchronize.调用call_rcu()之后.一直等待条件变量petion的满足.在这里看到了RCU的另一个核心API,它就是call_run().它的定义如下:void call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu))它用来等待之前的读者操作完成之后,就会调用函数func.我们也可以看到,在synchronize_rcu()中,读者操作完了要调用的函数就是wakeme_after_rcu(). 另外,call_rcu()用在不可睡眠的条件中,如果中断环境,禁止抢占环境等.而synchronize_rcu()用在可睡眠的环境下.先跟踪看一下wakeme_after_rcu():static void wakeme_after_rcu(struct rcu_head *head){struct rcu_synchronize *rcu;rcu = container_of(head, struct rcu_synchronize, head);complete(&rcu->completion);}我们可以看到,该函数将条件变量置真,然后唤醒了在条件变量上等待的进程.看下call_rcu():void call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu)){unsigned long flags;struct rcu_data *rdp;head->func = func;head->next = NULL;local_irq_save(flags);rdp = &__get_cpu_var(rcu_data);*rdp->nxttail = head;rdp->nxttail = &head->next;if (unlikely(++rdp->qlen > qhimark)) {rdp->blimit = INT_MAX;force_quiescent_state(rdp, &rcu_ctrlblk);}local_irq_restore(flags);}该函数也很简单,就是将head加在了per_cpu变量rcu_data的tail链表上.Rcu_data定义如下:DEFINE_PER_CPU(struct rcu_data, rcu_data) = { 0L };由此,我们可以得知,每一个CPU都有一个rcu_data.每个调用call_rcu()/synchronize_rcu()进程所代表的head都会挂到rcu_data的tail链表上.那究竟怎么去判断当前的写者已经操作完了呢?我们在之前看到,不是读者在调用rcu_read_lock()的时候要禁止抢占么?因此,我们只需要判断如有的CPU都进过了一次上下文切换,就说明所有读者已经退出了.引用>( (/developerworks/cn/linux/l-rcu/)中有关这个过程的描述:“等待适当时机的这一时期称为grace period,而CPU发生了上下文切换称为经历一个quiescent state,grace period就是所有CPU都经历一次quiescent state所需要的等待的时间。
嵌入式linux面试题目在嵌入式系统领域,Linux操作系统的应用越来越广泛,因此掌握嵌入式Linux的知识和技能成为了许多公司对求职者的基本要求。
本文将介绍一些常见的嵌入式Linux面试题目,帮助读者更好地准备面试。
1. 请解释什么是嵌入式Linux系统?嵌入式Linux系统是指在嵌入式设备(如智能手机、工业自动化设备)上运行的基于Linux内核的操作系统。
它具有开源、可定制、高度可靠的特点,常用于资源有限、功耗有限的嵌入式设备中。
2. Linux内核与嵌入式Linux系统有什么区别?Linux内核是操作系统的核心,负责管理硬件和提供基本的操作系统服务。
嵌入式Linux系统则是在Linux内核基础上构建而成,包括了用户空间工具和应用程序,以满足特定嵌入式设备的需求。
3. 如何在嵌入式设备上引导Linux系统?在嵌入式设备上引导Linux系统通常包括以下步骤:a. 加载引导程序(如U-Boot)到设备的引导区域;b. 通过引导程序加载Linux内核镜像;c. 内核初始化,并加载根文件系统镜像;d. 启动用户空间工具和应用程序。
4. 请解释Linux设备树(Device Tree)的作用和原理。
Linux设备树是一种描述硬件设备及其连接关系的数据结构,用于在Linux内核启动时动态识别和配置硬件。
它将设备与驱动程序分离,使得内核可以在不重新编译的情况下适应不同的硬件配置。
5. 请列举一些常见的嵌入式Linux发行版。
常见的嵌入式Linux发行版包括:- Yocto Project- Buildroot- OpenWrt- Android Things- Ubuntu Core6. 如何在嵌入式Linux系统中进行驱动程序开发?在嵌入式Linux系统中进行驱动程序开发通常包括以下步骤:a. 确定驱动程序与设备的接口和通信方式;b. 编写设备驱动程序,并将其编译成内核模块;c. 在内核配置中启用该驱动程序;d. 将编译好的内核模块加载到目标系统中,并进行测试和调试。
linux操作系统原理Linux操作系统是一种开源的、多用户、多任务的操作系统,基于Unix的设计理念和技术,由芬兰的林纳斯·托瓦兹(Linus Torvalds)在1991年首次发布。
其原理主要包括以下几个方面:1. 内核与外壳:Linux操作系统的核心是Linux内核,负责管理计算机的资源并为用户程序提供服务。
外壳(Shell)则是用户与内核之间的接口,提供命令行或图形用户界面供用户操作系统。
2. 多用户和多任务:Linux支持多用户和多任务,可以同时运行多个用户程序,并为每个用户分配资源。
多任务由调度器负责,按照一定的算法将CPU时间片分配给各个任务,以提高系统的利用率。
3. 文件系统:Linux采用统一的文件系统作为数据的存储与管理方式。
文件系统将计算机中的存储设备抽象成为一个层次化的文件和目录结构,使用户可以方便地访问和管理文件。
4. 设备管理:Linux操作系统通过设备驱动程序管理计算机的外部设备,如键盘、鼠标、打印机等。
每个设备都有相应的驱动程序,将硬件操作转换成可供内核或用户程序调用的接口。
5. 系统调用:Linux操作系统提供了一组系统调用接口,允许用户程序通过调用这些接口来访问内核提供的功能。
常见的系统调用包括文件操作、进程管理、内存管理等,通过系统调用可以使用户程序与操作系统进行交互。
6. 网络支持:Linux操作系统具有强大的网络功能,支持网络协议栈和网络设备驱动程序。
Linux可以作为服务器提供各种网络服务,如Web服务器、数据库服务器等。
7. 安全性:Linux操作系统注重安全性,提供了许多安全机制来保护系统和数据。
例如,文件权限控制、访问控制列表、加密文件系统等可以保护文件的机密性和完整性;防火墙和入侵检测系统可以保护网络安全。
总之,Linux操作系统具有高度的可定制性、稳定性和安全性,适用于服务器、嵌入式设备和个人计算机等各种场景。
在开源社区的支持下,Linux不断发展壮大,成为当今最受欢迎的操作系统之一。
linux的qos机制-cgroup篇(4)下面来看各个子系统对cgroup的支持,第一篇先研究blkio子系统blkio子系统支持三种类型的QoS控制:1blkio.weight,blkio.weight_device:这些是基于设备权重值的控制方式2blkio.throttle.read_bps_device,blkio.throttle.write_bps_device:这些是基于带宽的控制方式3blkio.throttle.read_iops_device,blkio.throttle.write_iops_device:这些是基于iops 的控制方式其中基于权重的控制方式,必须依赖于CFQ调度器,而基于throttle的控制方式则只需要在通用块层实现就可以了1)基于blkio的cgroup_subsys的定义如下:struct cgroup_subsys blkio_subsys={.name="blkio",.create=blkiocg_create,.can_attach_task=blkiocg_can_attach_task,.attach_task=blkiocg_attach_task,.destroy=blkiocg_destroy,.populate=blkiocg_populate,#ifdef CONFIG_BLK_CGROUP/*note:blkio_subsys_id is otherwise defined in blk-cgroup.h*/.subsys_id=blkio_subsys_id,#endif.use_id=1,.module=THIS_MODULE,};blkiocg_create(struct cgroup_subsys*subsys,struct cgroup*cgroup):初始化一个blkio_cgroup结构,并初始化blkio_cgroup->policy_list,blkio_cgroup->blkg_listblkiocg_destroy(struct cgroup_subsys*subsys,struct cgroup*cgroup):略过blkiocg_populate(struct cgroup_subsys*subsys,struct cgroup*cgroup):初始化好blkio_files里所有的blkio_policy_node对应的cgroup文件系统的文件blkiocg_can_attach_task(struct cgroup*cgrp,struct task_struct*tsk):/**We cannot support shared io contexts,as we have no mean to support*two tasks with the same ioc in two different groups without major rework *of the main cic data structures.For now we allow a task to change*its cgroup only if it's the only owner of its ioc.*/2)基于blkio的policy的数据结构定义如下:struct blkio_policy_node{struct list_head node;dev_t dev;/*This node belongs to max bw policy or porportional weight policy*/ enum blkio_policy_id plid;/*cgroup file to which this rule belongs to*/int fileid;union{unsigned int weight;/**Rate read/write in terms of byptes per second*Whether this rate represents read or write is determined*by file type"fileid".*/u64bps;unsigned int iops;}val;};struct blkio_policy_ops{blkio_unlink_group_fn*blkio_unlink_group_fn;blkio_update_group_weight_fn*blkio_update_group_weight_fn;blkio_update_group_read_bps_fn*blkio_update_group_read_bps_fn; blkio_update_group_write_bps_fn*blkio_update_group_write_bps_fn; blkio_update_group_read_iops_fn*blkio_update_group_read_iops_fn; blkio_update_group_write_iops_fn*blkio_update_group_write_iops_fn; };enum blkio_policy_id{BLKIO_POLICY_PROP=0,/*Proportional Bandwidth division*/BLKIO_POLICY_THROTL,/*Throttling*/};struct blkio_policy_type{struct list_head list;struct blkio_policy_ops ops;enum blkio_policy_id plid;};blkio_policy_node,基本上可以看做一个cgroup文件系统下的一个配置文件对应一个blkio_policy_node,一个cgroup目录的所有的policy_node都会被链在一个blkio_cgroup->policy_list的链表中blkio_policy_type根据不同的blkio_policy_id有不同的blkio_policy_ops,blkio_policy_register在cfq_init,throtl_init时被调用,这两个初始化函数分别对应基于权重的控制和基于阀值的控制,目前有两个全局的blkio_policy_type的变量:static struct blkio_policy_type blkio_policy_cfq={.ops={.blkio_unlink_group_fn=cfq_unlink_blkio_group,.blkio_update_group_weight_fn=cfq_update_blkio_group_weight,},.plid=BLKIO_POLICY_PROP,};以及static struct blkio_policy_type blkio_policy_throtl={.ops={.blkio_unlink_group_fn=throtl_unlink_blkio_group,.blkio_update_group_read_bps_fn=throtl_update_blkio_group_read_bps,.blkio_update_group_write_bps_fn=throtl_update_blkio_group_write_bps,.blkio_update_group_read_iops_fn=throtl_update_blkio_group_read_iops,.blkio_update_group_write_iops_fn=throtl_update_blkio_group_write_iops,},.plid=BLKIO_POLICY_THROTL,};3)基于blkio的cgroup文件系统的数据结构如下:struct cftype blkio_files[]={{.name="weight_device",.private=BLKIOFILE_PRIVATE(BLKIO_POLICY_PROP,BLKIO_PROP_weight_device),.read_seq_string=blkiocg_file_read,.write_string=blkiocg_file_write,.max_write_len=256,},{.name="weight",.private=BLKIOFILE_PRIVATE(BLKIO_POLICY_PROP,BLKIO_PROP_weight),.read_u64=blkiocg_file_read_u64,.write_u64=blkiocg_file_write_u64,},{.name="throttle.read_bps_device",.private=BLKIOFILE_PRIVATE(BLKIO_POLICY_THROTL,BLKIO_THROTL_read_bps_device),.read_seq_string=blkiocg_file_read,.write_string=blkiocg_file_write,.max_write_len=256,},{.name="throttle.write_bps_device",.private=BLKIOFILE_PRIVATE(BLKIO_POLICY_THROTL,BLKIO_THROTL_write_bps_device),.read_seq_string=blkiocg_file_read,.write_string=blkiocg_file_write,.max_write_len=256,},{.name="throttle.read_iops_device",.private=BLKIOFILE_PRIVATE(BLKIO_POLICY_THROTL,BLKIO_THROTL_read_iops_device),.read_seq_string=blkiocg_file_read,.write_string=blkiocg_file_write,.max_write_len=256,},{.name="throttle.write_iops_device",.private=BLKIOFILE_PRIVATE(BLKIO_POLICY_THROTL,BLKIO_THROTL_write_iops_device),.read_seq_string=blkiocg_file_read,.write_string=blkiocg_file_write,.max_write_len=256,},基本上调用的都是blkiocg_file_read,blkiocg_file_writeblkio_files中的struct cftype有个private成员变量,通过BLKIOFILE_PRIVATE宏来赋值,e.g..private=BLKIOFILE_PRIVATE(BLKIO_POLICY_PROP,BLKIO_PROP_weight_device)之后可以通过BLKIOFILE_POLICY获取其policy类型:BLKIO_POLICY_THROTL或者BLKIO_POLICY_PROP,通过BLKIOFILE_ATTR获取其文件名,所有的配置文件都有如下定义:/*cgroup files owned by proportional weight policy*/enum blkcg_file_name_prop{BLKIO_PROP_weight=1,BLKIO_PROP_weight_device,BLKIO_PROP_io_service_bytes,BLKIO_PROP_io_serviced,BLKIO_PROP_time,BLKIO_PROP_sectors,BLKIO_PROP_unaccounted_time,BLKIO_PROP_io_service_time,BLKIO_PROP_io_wait_time,BLKIO_PROP_io_merged,BLKIO_PROP_io_queued,BLKIO_PROP_avg_queue_size,BLKIO_PROP_group_wait_time,BLKIO_PROP_idle_time,BLKIO_PROP_empty_time,BLKIO_PROP_dequeue,};/*cgroup files owned by throttle policy*/enum blkcg_file_name_throtl{BLKIO_THROTL_read_bps_device,BLKIO_THROTL_write_bps_device,BLKIO_THROTL_read_iops_device,BLKIO_THROTL_write_iops_device,BLKIO_THROTL_io_service_bytes,BLKIO_THROTL_io_serviced,};static int blkiocg_file_read(struct cgroup*cgrp,struct cftype*cft,struct seq_file*m):通过cftype得到POLICY_ID,POLICY_FILE_NAME,通过struct cgroup得到struct blkio_cgroup,然后调用blkio_read_policy_node_files,按照一定格式存到一个seq_file里面,可以参考blkio_print_policy_node函数static int blkiocg_file_write(struct cgroup*cgrp,struct cftype*cft,const char*buffer):先调用blkio_policy_parse_and_set生成一个新的blkio_policy_node,下面的步骤就是先删了已有的policy node,再把新的policy node插入到blkio_cgroup->policy_list 里面,最后调用blkio_update_policy_node_blkg,该函数对blkio_cgroup下面的所有blkio_group,都调用blkio_update_blkg_policy,该函数会根据blkio_policy_node的plid, fileid,调用不同的blkio_update_xxxxx函数,以weight为例,最终调用到blkio_update_group_weight,后者又调用cfq_update_blkio_group_weight(这是跟CFQ 紧耦合的一个函数,这里不做介绍了)4)几个关键的数据结构blkio_cgroup和blkio_groupstruct blkio_cgroup{struct cgroup_subsys_state css;unsigned int weight;spinlock_t lock;struct hlist_head blkg_list;struct list_head policy_list;/*list of blkio_policy_node*/};struct blkio_group{/*An rcu protected unique identifier for the group*/void*key;struct hlist_node blkcg_node;unsigned short blkcg_id;/*Store cgroup path*/char path[128];/*The device MKDEV(major,minor),this group has been created for*/dev_t dev;/*policy which owns this blk group*/enum blkio_policy_id plid;/*Need to serialize the stats in the case of reset/update*/spinlock_t stats_lock;struct blkio_group_stats stats;/*Per cpu stats pointer*/struct blkio_group_stats_cpu__percpu*stats_cpu;};blkio_cgroup代表了一个cgroup,但是这个cgroup里的进程有可能会读写多个块设备,所有通过一个cfq_data或者throtl_data的结构作为红黑树的key,把多个blkio_group关联到一个blkio_cgroup中。
uprobe 原理
uprobe,全称为uprobes,是Linux 内核中的一项性能工具,用于跟踪应用程序中的函数调用或指令执行,以便进行性能分析和调试。
uprobe 的原理是通过内核中的ftrace 和kprobes 机制来实现。
它会在应用程序的目标函数的入口和出口处插入一些特殊的代码,以便在函数调用或指令执行时触发事件,然后记录相关的信息,例如程序计数器(PC)的当前值、函数参数和返回值等。
这些信息可以通过系统调用或proc 文件系统进行访问,并用于分析和调试应用程序的性能瓶颈或错误。
uprobe 可以被用于各种性能分析和调试目的,例如:
- 统计函数调用次数、执行时间和耗时;
- 调试应用程序中的内存泄漏、死锁和其他错误;
- 分析应用程序中的性能瓶颈,例如CPU 占用率、IO 等待时间等;
- 追踪应用程序的系统调用和库函数调用等。
总的来说,uprobe 是一项非常强大的工具,可以帮助开发者和系统管理员快速定位和解决各种应用程序中的问题,提高应用程序的性能和稳定性。
对linux rcu机制的理解
RCU(Read-copy update)锁机制是kernel2.6的重大进步,使用rcu可以获得比使用rwlock更高的性能,而且代码简单,不易死锁。
Linux 文档如下描述:
So the typical RCU update sequence goes something like the following:
a. Remove pointers to a data structure, so that subsequent
readers cannot gain a reference to it.
b. Wait for all previous readers to complete their RCU read-side
critical sections.
c. At this point, there cannot be any readers who hold references
to the data structure, so it now may safely be reclaimed
(e.g., kfree()d).
如上所述,RCU原理其实很简单,现在假设你自己要实现一个基于rcu原理的模型。
假定cpu处理报文的过程是原子的,这样可以认为处理完一个报文就经过了一个quiescent state,RCU引用的过时数据就可以释放了。
接收报文开始进入一个新的周期,当发送报文后,标记一个周期的结束。
假设有8个cpu,轮流处理报文,而报文处理依赖的信息存储在共享内存中。
那末,现在的问题是:请用rcu机制实现共享内存的释放。
假设所有的cpu处理一个报文结束,就代表一个grace period (因为只有处理报文时才会使用共享内存),即每个cpu都经过了一个quiscent state .
那么现在就有三个问题:
1.如何确定所有的cpu都经过了一个quiscent state?
2.在rcu处理时,如何保证别的cpu不对要释放的数据操作?
3.在rcu处理后,如何开启和判断下一个rcu周期?
带着这三个问题,我们可以看一下linux是如何实现rcu机制的。
以下的分析是基于linux-2.6.27代码:
Rcu有两个重要的数据结构:
/* Global control variables for rcupdate callback mechanism. */
struct rcu_ctrlblk {
long cur; /* Current batch number. */
long completed; /* Number of the last completed batch */ int next_pending; /* Is the next batch already waiting? */
int signaled;
spinlock_t lock ____cacheline_internodealigned_in_smp;
cpumask_t cpumask; /* CPUs that need to switch in order */
/* for current batch to proceed. */
} ____cacheline_internodealigned_in_smp;
上述结构是rcu全局控制结构
struct rcu_data {
/* 1) quiescent state handling : */
long quiescbatch; /* Batch # for grace period */
int passed_quiesc; /* User-mode/idle loop etc. */
int qs_pending; /* core waits for quiesc state */
/* 2) batch handling */
long batch; /* Batch # for current RCU batch */ struct rcu_head *nxtlist;
struct rcu_head **nxttail;
long qlen; /* # of queued callbacks */ struct rcu_head *curlist;
struct rcu_head **curtail;
struct rcu_head *donelist;
struct rcu_head **donetail;
long blimit; /* Upper limit on a processed batch */ int cpu;
struct rcu_head barrier;
};
上述结构是每个cpu用的rcu数据结构。
DECLARE_PER_CPU(struct rcu_data, rcu_data);
static struct rcu_ctrlblk rcu_ctrlblk = {
.cur = -300,
.completed = -300,
.lock = __SPIN_LOCK_UNLOCKED(&rcu_ctrlblk.lock),
.cpumask = CPU_MASK_NONE,
};
Linux在rcu代码中声明了两个全局变量,
一个是rcu_data,每个cpu一个;一个是rcu_ctrlblk,全局一个。
Rcp(全局一个)
某一个cpu Grace period
某一个cpu进入
中并等待
所有cpu进入Grace period
rcp记录上次cur
Rcp 开启一个新
每个cpu 开启自己新grace
period。