MAP_DENYWRITE:被Linux内核屏蔽的flag

简介: 一 背景谈到MAP_DENYWRITE,可能有些陌生。这个flag很少被用户态开发者关注,其中没有被关注的理由主要是“this flag is ignored by os”,简而言之,操作系统(Linux内核)将会忽略掉用户传入的MAP_DENYWRITE标志。回到MAP_DENYWRITE是什么?与MAP_ANONYMOUS、MAP_SHARED、MAP_PRIVATE等一样,是系统调用mmap

一 背景

谈到MAP_DENYWRITE,可能有些陌生。这个flag很少被用户态开发者关注,其中没有被关注的理由主要是“this flag is ignored by os”,简而言之,操作系统(Linux内核)将会忽略掉用户传入的MAP_DENYWRITE标志。回到MAP_DENYWRITE是什么?与MAP_ANONYMOUS、MAP_SHARED、MAP_PRIVATE等一样,是系统调用mmap()函数为映射目标设置映射方式的一种flag。顾名思义,MAP_DENYWRITE表示这段映射的虚拟地址区间不允许写操作,例如,我们通过open()以可写的方式获取文件句柄,将会返回“ETXTBSY: text file is busy”错误。

本文主要介绍MAP_DENYWRITE相关的内容,例如使用了MAP_DENYWRITE的可执行二进制文件忽略MAP_DENYWRITE的动态共享库。为了描述简单,后面直接使用EXEC和DSO(Dynamic Shared Object)分别表示可执行二进制文件和动态共享库。为了更加清晰的展示MAP_DENYWRITE作用,设计了实验1和实验2来显示用户态可见的差异。同时,借助实验1和实验2也为引出两个疑问:(a)为什么i_writecount会导致“ETXTBSY: text file is busy”;(b)为什么vim“写-存”DSO触发程序“Segment fault”;其答案分别可以在第三节和第四节获取。最后,第五节设计了最后一个实验对实验3进行补充。

二 MAP_DENYWRITE是什么

关于MAP_DENYWRITE是什么?这里先直接引用一下man2上的描述:

图1:MAP_DENYWRITE官方描述

看样子,Linux内核会忽略用户态传入的MAP_DENYWRITE标志(参考:https://man7.org/linux/man-pages/man2/mmap.2.html),主要是为了防止DDoS攻击。

早期的社区讨论

2001年这个问题就已经被讨论了,如下图:

图2:Linus关于解释Linux内核屏蔽MAP_DENYWRITE的邮件

图2中,Linus以mmap("/etc/passwd")为例,说明了内核为什么忽略MAP_DENYWRITE。另外,更明显的例子便是普通的log日志文件,如果有用户恶意使用MAP_DENYWRITE的方式map了该文件,那么对该日志文件存在写操作的所有进程都将会崩溃,这是一个严重的漏洞。

尽管Linux内核屏蔽的用户态传入的MAP_DENYWRITE,但是,还是有那么一个“固执”的程序至今为止仍坚持在使用mmap()映射DSO时使用MAP_DENYWRITE,即ld.so。既然DSO在映射过程中实际传入到内核的MAP_DENYWRITE被忽略了,那么为何不提交一个补丁在glibc中去掉这一块相关的代码?考古发现,其实也有人多年前提交过,我们可以找到当年的邮件讨论,下面是截取的其中一个Maintainer的观点:

图3:glibc社区对是否保留MAP_DENYWRITE的讨论

图中的意见基本表示glibc会保留在映射DSO时加入MAP_DENYWRITE标志,认为这种问题应该Linux内核去解决,详细可以参考链接1。

写到这里似乎这篇文章就可以全剧终了。

当然还有剧情,男女主角还没出现!“作者还能继续编”!尽管Linux内核忽略了从用户态传入的该参数,但是这个参数内核自己还是可以用:内核在加载初始化ELF可执行文件时,整个过程都在内核态完成,所以内核直接可以加上这个flag,而不会引入DDoS攻击。

本小节接下来将会设计两个与MAP_DENYWRITE有关联的实验,这两个实验可以帮助我们更清晰的解读MAP_DENYWRITE的故事:

  • 实验1:EXEC文件本身在执行过程中,操作系统将会在其代码段的映射区间印上MAP_DENYWRITE,那么如何可以看出来?同时正如前面的提到的,对其代码进行写,如何触发“ ETXTBSY: text file  is  busy ”错误。
  • 实验2:为了防止DDoS攻击,操作系统忽略掉了用户(例如ld.so)传入的MAP_DENYWRITE。那么对其进行写打开又有哪些现象?与EXEC又有哪些不同?

实验1:对EXEC的代码段进行测试

下面是一个进程的可执行文件本身的代码段在被映射后的截图:

图4:EXEC代码段smaps信息

在图2中dw是该进程的可执行文件本身,并且最后VmFlags一行可以包含dw的标志,表示内核在映射dw的代码段过程中,带上了MAP_DENYWRITE。下面一个很简单的用例来表示如何复现“ETXTBSY: text file is busy”。

图5:一个发生“ETXTBSY: text file is busy”错误的用例演示

图5中,演示了一个程序在执行过程中,对自己的EXEC文件dw_txtbsy以可写的方式进行打开,其中使用的测试用例如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
        int fd;

        fd = open("/mnt/nvme2/map_denywrite/dw_txtbsy", O_WRONLY);
        if (fd < 0)
                perror("dw_txtbsy:");

        return 0;
}

实验2:对DSO的代码段进行测试

与实验1相同,首先展示DSO的代码段在建立映射后的smaps信息,如下图所示。

图6:DSO代码段smap信息

图6选择几乎所有的程序都依赖的libc.so为例。在其VmFlags中并没有发现dw标志,所以从逻辑上,我们可以想到如果对其进行EXEC同样的实验,并不会发生“ETXTBSY: text file is busy”错误。这里给出测试的用例,感兴趣的小伙伴可以自行验证。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
        int fd;

        fd = open("/usr/lib64/libc-2.32.so", O_WRONLY);
        if (fd < 0)
                perror("dw_txtbsy:");

        return 0;
}

其实在这个实验中,我们更关系的是下面的实验:

实验设计:既然前面提到DSO可以以写的方式open,那么我们自己写一个简单的DSO文件,并让另外一个可执行文件中链接调用其中的函数,保持进程运行状态,同时通过vim对该DSO进行修改,看看会出现什么现象。

代码1:load.c

#include <stdio.h>

int foo(void);

int foo(void)
{
        printf("foo: 1\n");
        return 0;
}

代码2:dso_w.c

#include <stdio.h>
#include <unistd.h>

extern int foo(void);

int main(void)
{
        while(1) {
                foo();
                sleep(3);
        }

        return 0;
}

编译过程:

$ gcc -O2 -fPIC -c -o load.o load.c
$ gcc -shared -o libload.so load.o
$ gcc -O2 -o dso_w dso_w.c libload.so -Wl,-R,.
$ ./dso_w

下面的实验的截图:

图7:对正在使用的DSO进行修改发生Segmentation fault

图7中,最开始程序正常打印“foo: 1”,但是当我们直接使用vim对libload.so进行修改成“foo: 2”后,发生了“Segmentation fault”。当我们重新执行该程序时,我们的修改起效了。由此,本实验基本可以得出结论:对于DSO,虽然由于没有MAP_DENYWRITE,可以允许用户对其进行修改,但是通过vim“修改-保存”的操作,将会导致正常依赖该DSO的程序直接异常退出。

写到这里,DSO的第二个实验基本完成。

小结

前面两个实验主要为证明在存在或没有MAP_DENYWRITE时,程序行为有何差异。当然除以上实验外,与MAP_DENYWRITE相关的还有install、cp和mv等命令。对于已经被MAP_DENYWRITE映射的文件,可以使用install可以替换inode,但是无法使用cp或者mv。其中详细读者可以自行调研。

此外,前面的两个实验也为引出下面的问题:

  • 对EXEC代码段进行写时,发生“ ETXTBSY: text file  is  busy ”的底层内核逻辑是什么?
  • 对正常使用的DSO写时,为什么会发生“Segmentation fault”?以及背后的符号重定向逻辑细节是什么?

下面一节将尝试一一解释。

三 为什么i_writecount会导致“ETXTBSY: text file is busy”

紧跟上文,前面提到EXEC的代码段和数据段的映射过程基本都在内核态完成,因此加入MAP_DENYWRITE是可信任的。因此,本节基于此,想要理清楚对EXEC代码段进行写时,发生“ETXTBSY: text file is busy”的底层内核逻辑是什么?下面会首先给出导致ETXTBSY错误的根本变量:inode->i_writecount值,该值表示当前有多少写者正在占用该file。最后简单给出open()、mmap()和exec()与其相关联的Linux内核调用链,以供读者复现。

下面是内核中与i_writecount相关的四个函数:

static inline int get_write_access(struct inode *inode)
{
  return atomic_inc_unless_negative(&inode->i_writecount) ? 0 : -ETXTBSY;
}
static inline int deny_write_access(struct file *file)
{
  struct inode *inode = file_inode(file);
  return atomic_dec_unless_positive(&inode->i_writecount) ? 0 : -ETXTBSY;
}
static inline void put_write_access(struct inode * inode)
{
	atomic_dec(&inode->i_writecount);
}
static inline void allow_write_access(struct file *file)
{
	if (file)
		atomic_inc(&file_inode(file)->i_writecount);
}

其中put_write_access()函数会在建立映射的过程中,检测是否存在MAP_DENYWRITE,如果存在,则调用put_write_access()将i_writecount值减1,函数调用链路为:

exec()->load_elf_binary()->vm_mmap_pgoff()->do_mmap()->mmap_region()
      ->{deny | put }_write_access

而当程序通过open(file, O_WRONLY)的方式打开某文件时,内核为做一些列检查,其中包括调用get_write_access()获取写的权限,当该值为负值,get_write_access()将会返回-ETXTBSY,即用户看见的“ETXTBSY: text file is busy”,调用路径大致为:

open()->path_openat->do_o_path->vfs_open->do_dentry_open->get_write_access

如果用一张图总结上面的描述,大致可以画成:

图8:i_writecount与“ETXTBSY: text file is busy”框图

第一个问题到此基本结束。总结一句话:对EXEC而言,最终MAP_DENYWRITE落实到inode->i_writecount发挥作用。相对问题2,简单很多。

四 为什么vim“写-存”DSO触发程序“Segment fault”?

在开始一堆概念解释前,首先先给出标题的答案:在实验2中,我们通过vim修改二进制的方式,将“printf("foo: 1\n")”改为“printf("foo: 2\n")”,该操作将会触发“清空libload.so pagecache--重新读取到pagecache”等操作【】,该操作完成以后GOT和PLT中的数据没并用根据当前实际映射情况进行更新(正常情况下ld.so会在映射DSO完成后,调用_dl_runtime_resolve函数初始化库中成员函数的实际映射地址)。简而言之,便是再次执行foo函数时,由于DSO实际映射的数据段中GOT和PLT数据未被初始化,为异常地址,直接执行这些异常地址便会产生段错误。

注:

(1)使用vim修改DSO并保存到磁盘时,vim使用了open(O_WRONLY | O_TRUNC),其中O_TRUNC会截断整个文件,执行“truncate pagecache”操作,清空文件在内存中的pagecache和页表。当下次访问DSO中的成员函数时,重新触发缺页异常,初始化pagecache等。这属于vim行为,若使用open(O_WRONLY)不会影响在内存DSO的pagecache。

(2)正常情况下,DSO被映射后,其内存中的GOT和PLT数据与磁盘中DSO文件的GOT和PLT段数据是不同的。

GOT和PLT

在讲GOT和PLT的功能前,可以先思考这样一个问题:一个DSO会被不同的进程依赖,并且在不同进程地址空间中,所才的地址空间也不同,因此这种差异是如何解决的?并且让不同的进程可以和谐运行下去。

答案就是GOT,不同的进程对不同的DSO映射地址的保存都是私有的数据,这些私有的GOT数据就保存在各自DSO内存中的数据段中

链接器ld.so在执行重定向时会用到的部分, 先来看他们的定义。

(1).got

这是我们常说的GOT, 即Global Offset Table, 全局偏移表. 这是链接器在执行链接时,实际上要填充的部分, 保存了所有外部符号的地址信息。GOT表项还保留了3个公共表项, 每项32位(4字节), 保存在前三个位置, 分别是:

  • got[0]:本ELF动态段(.dynamic段)的装载地址;
  • got[1]:本ELF的link_map数据结构描述符地址;
  • got[2]:_dl_runtime_resolve函数的地址。 对于一个运行的进程,其本身的GOT以及依赖库的GOT皆是由ld.so来填充

(2).plt

这也是我们常说的PLT(Procedure Linkage Table),即进程链接表. 这个表里包含了一些代码,主要有两个作用:

  • 调用链接器来解析某个外部函数的地址, 并填充到.got.plt中, 然后跳转到该函数;
  • 直接在.got.plt中查找并跳转到对应外部函数(如果已经填充过);

(3).got.plt

.got.plt相当于.plt的GOT全局偏移表, 其内容有两种情况, 1)如果在之前查找过该符号,内容为外部函数的具体地址. 2)如果没查找过,则内容为跳转回.plt的代码, 并执行查找。至于为什么要这么绕, 后面会说明具体原因。

(4).plt.got

略。

这里就以实验2中dso_w.c和load.c为例,探究调用foo函数的过程中涉及的PLT和GOT访问。详细的流程如下图所示:

图9:函数foo重定向流程框图

图9中展示了第一次访问foo函数时发生跳转以及访问GOT和PLE表的四个重要过程。四个过程如图中标记,其简介大致为:

  • 过程1:主要是访问foo函数,此时会跳转到foo@plt,该地址处于PLT表中;
  • 过程2:在foo@plt中,需要访问.got.plt表,获取0x420000+32处存在值,即0x400520。并跳转到该位置;
  • 过程3:经过过程2的跳转,此时处于PLT表的最开始位置(0x400520),此处会访问0x41f00+4088处( 位于GOT表 )所存值,并跳转,即执行_dl_runtime_resolve函数;
  • 过程4,在该函数中,将会修改过程2中访问的0x400520为真实的函数地址。当下次访问foo函数时,在执行过程1以后就会执行DSO中真正的foo函数;

看完这四个过程,不禁让人疑惑_dl_runtime_resolve函数是在何时被写入到GOT表中以及它如何计算出foo函数实际的映射地址。关于第一个问题,前面有简单的提到(“对于一个运行的进程,其本身的GOT以及依赖库的GOT皆是由ld.so来填充”),对于本文比较关心的DSO而言,其GOT便是在ld.so将其mmap到进程的地址空间以后初始的。作者在此大胆推测:前面所写DSO说发生的段错误,应该是由于重新缺页读取的DSO后,其GOT和PLT各项为非法地址,例如过程2访问.got.plt表时读取的异常地址发生错误跳转后便触发段错误。

一段插曲:在实验验证前,原文此处为“大胆推测:前面所写DSO说发生的段错误,应该是由于重新缺页读取的DSO后,其GOT各项为非法地址,例如过程3执行_dl_runtime_resolve函数就发生的段错误。”。但是实验验证后发现此推测忽略了DSO的PLT也属于代码段,映射后地址也发生了变化。

基于该推测性结论,我们再大胆猜测是否存在另一个结论:如果DSO中的foo函数,没有再调用其他库函数,即不涉及访问GOT/PLT行为,那么对该DSO任意修改(看上去像所谓的DSO热升级),应该不会导致“Segmentation fault”。关于该结论的验证,留到本节最后“最后一个实验”进行验证。

实验3:问题解剖

经过上一节分析,本节开始验证前面的推测:“前面所写DSO说发生的段错误,应该是由于重新缺页读取的DSO后,其GOT和PLT各项为非法地址,例如过程2访问.got.plt表时读取的异常地址发生错误跳转后便触发段错误”。为验证该推测,任然选择前面实验2的代码:dso_w.c和load.c。本实验其实是实验2的后续,实验步骤完全相同,但是本实验中需要分析生成的core.pid文件,以及找到出现段错误的根本原因。

在实验前,首先需要对环境进行配置,确保可以生成core dump文件。

$ sysctl -w kernel.pattern="core.$p"

此外,实验过程中借助objdump -DTR dso_w命令获取GOT/PLT表地址信息,例如获取GOT的地址为0x41ffd8,如下所示。同时,利用gdb查看GOT表中内容:

gef➤  disassemble 0x41ffd8
Dump of assembler code for function _GLOBAL_OFFSET_TABLE_:
   0x000000000041ffd8:  .inst   0x0041fde8 ; undefined
   0x000000000041ffdc:  .inst   0x00000000 ; undefined
   0x000000000041ffe0:  .inst   0x00000000 ; undefined
   0x000000000041ffe4:  .inst   0x00000000 ; undefined
End of assembler dump.

注意,在上面的gdb中,我们查看的是非运行时的dso_w数据,因此看到的GOT表中各项基本为零值。

下面是定位原因的过程。

$ gdb dso_w core.262672

gef➤  bt # 第一步
#0  0x00000000000004a0 in ?? ()
#1  0x0000ffff8f48c5ec in foo () at load.c:9
#2  0x0000000000400670 in main () at dso_w.c:9
gef➤  disassemble 0x0000ffff8f48c5ec # 第二步
Dump of assembler code for function foo:
   0x0000ffff8f48c5d4 <+0>:     stp     x29, x30, [sp, #-32]!
   0x0000ffff8f48c5d8 <+4>:     mov     x29, sp
   0x0000ffff8f48c5dc <+8>:     str     wzr, [sp, #28]
   0x0000ffff8f48c5e0 <+12>:    adrp    x0, 0xffff8f48c000
   0x0000ffff8f48c5e4 <+16>:    add     x0, x0, #0x610
   0x0000ffff8f48c5e8 <+20>:    bl      0xffff8f48c4e0 <puts@plt>
   0x0000ffff8f48c5ec <+24>:    ldr     w0, [sp, #28]
   0x0000ffff8f48c5f0 <+28>:    ldp     x29, x30, [sp], #32
   0x0000ffff8f48c5f4 <+32>:    ret
End of assembler dump.
gef➤  disassemble 0xffff8f48c4e0 # 第三步
Dump of assembler code for function puts@plt:
   0x0000ffff8f48c4e0 <+0>:     adrp    x16, 0xffff8f4ac000 <__cxa_finalize@got.plt>
   0x0000ffff8f48c4e4 <+4>:     ldr     x17, [x16, #16]
   0x0000ffff8f48c4e8 <+8>:     add     x16, x16, #0x10
   0x0000ffff8f48c4ec <+12>:    br      x17
End of assembler dump.
gef➤  x /x 0xffff8f4ac010 # 最后一步
0xffff8f4ac010 <puts@got.plt>:  0x000004a0

上面标出了四个步骤:

  • 第一步:通过core dump文件,可以看到发生错误的原因是pc异常值:0x4a0;
  • 第二步:查看foo函数的汇编代码,确定跳转到0xffff8f48c4e0;
  • 第三步:查看0xffff8f48c4e0处的汇编代码,发现接下来将会从.got.plt中读取0xffff8f4ac010处的值;
  • 最后一步:查看0xffff8f4ac010地址上所存值为0x4a0,导致原因找到;

上面四步其实就是将图9中展示的几个过程在gdb中复现了一遍。最后一步读取的值“0x000004a0”直接指明了发生段错误就是因为.got.plt表各项没有被初始化,还是错误的地址。

五 最后一个实验

与前面实验2相似,如下为load.c源码,在这个实验中,通过二进制的方式将“int i = 0”这行代码修改为"int i = 1"的代码。根据前面的GOT和PLT分析,该操作不会导致程序段错误。

#include <stdio.h>

int foo(void);
int foo(void)
{
        int i = 0;
        return i;
}

本实验需要的步骤与实验2完全相同,仅仅是修改的数据不同。在这个实验中,首先在libload.so中定位到“int i = 0”的机器码,对机器码直接修改。在vim中修改时,通过借助xxd和xxd -r进行十六进制转换以及恢复。

如前面猜测的那样,foo函数不依赖DSO的GOT/PLT内容,随意修改成合法的指令照样运行。因此本实验并不会触发程序段错误。

六 总结

前面主要通过四个实验来介绍MAP_DENYWRITE,以及解释与MAP_DENYWRITE有关的两个“为什么”。其实本文最初目的是搞清楚“为什么vim“写-存”DSO时会触发程序Segment fault?”,其他内容主要铺垫和引出该问题。文中大量的概念并没有完全阐述清楚,例如GOT/PLT,其内容并不止于文中提到的部分,涉及到许多其他复杂的概念和过程。

本文所描述的问题主要是代码加速项目中对DSO使用大页实现过程中遗留的疑问,项目成员包括弃余,据德,钟江。

参考

  • 有人尝试去掉libc中的MAP_DENYWRITE,但是最终没有去掉: 点我
  • 内核社区的讨论: 点我
  • 知乎-so覆盖coredump带来的思考: 点我
  • ELF中PLT和GOT工作机制简介-可还原: 点我
  • 聊聊动态链接和dl_runtime_resolve: 点我
相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
相关文章
|
4天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
24 4
|
8天前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
29 6
|
7天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
27 9
|
6天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
23 6
|
7天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
25 5
|
7天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
8天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
7天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
9天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
26 6
|
10天前
|
机器学习/深度学习 负载均衡 算法
深入探索Linux内核调度机制的优化策略###
本文旨在为读者揭开Linux操作系统中至关重要的一环——CPU调度机制的神秘面纱。通过深入浅出地解析其工作原理,并探讨一系列创新优化策略,本文不仅增强了技术爱好者的理论知识,更为系统管理员和软件开发者提供了实用的性能调优指南,旨在促进系统的高效运行与资源利用最大化。 ###