linux内核copy_{to, from}_user()的思考

目录
  • 一、什么是copy_{to,from}_user()
    • 1、copy_{to,from}_user()对比memcpy()
    • 2、函数定义
  • 二、CONFIG_ARM64_SW_TTBR0_PAN原理
  • 三、测试
  • 四、总结

一、什么是copy_{to,from}_user()

它是kernel space和user space沟通的桥梁。所有的数据交互都应该使用类似这种接口。但是他的作用究竟是什么呢?我们对下提出疑问:

  • 为什么需要copy_{to,from}_user(),它究竟在背后为我们做了什么?
  • copy_{to,from}_user()和memcpy()的区别是什么,直接使用memcpy()可以吗?
  • memcpy()替代copy_{to,from}_user()是不是一定会有问题?

温馨提示:文章代码分析基于Linux-4.18.0,部分架构相关代码以ARM64为代表。

1、copy_{to,from}_user()对比memcpy()

  • copy_{to,from}_user()比memcpy()多了传入地址合法性校验。例如是否属于用户空间地址范围。理论上说,内核空间可以直接使用用户空间传过来的指针,即使要做数据拷贝的动作,也可以直接使用memcpy(),事实上在没有MMU的体系架构上,copy_{to,from}_user()最终的实现就是利用了mencpy()。但是对于大多数有MMU的平台,情况就有了些变化:用户空间传过来的指针是在虚拟地址空间上的,它所指向的虚拟地址空间很可能还没有真正映射到实际的物理页面上。但是这又能怎样呢?缺页导致的异常会很透明地被内核予以修复(为缺页的地址空间提交新的物理页面),访问到缺页的指令会继续运行仿佛什么都没有发生一样。但这只是用户空间缺页异常的行为,在内核空间这种缺页异常必须被显式地修复,这是由内核提供的缺页异常处理函数的设计模式决定的。其背后的思想是:在内核态,如果程序试图访问一个尚未被提交物理页面的用户空间地址,内核必须对此保持警惕而不能像用户空间那样毫无察觉。
  • 如果我们确保用户态传递的指针的正确性,我们完全可以用memcpy()函数替代copy_{to,from}_user()。经过一些试验测试,发现使用memcpy(),程序的运行上并没有问题。因此在确保用户态指针安全的情况下,二者可以替换。

从各家博客上,观点主要集中在第一点。看起来第一点受到大家的广泛认可。但是,注重实践的人又得出了第二种观点,毕竟是实践出真知。真理究竟是是掌握在少数人手里呢?还是群众的眼睛是雪亮的呢?当然,我不否定以上任何一种观点。也不能向你保证哪种观点正确。因为,我相信即使是曾经无懈可击的理论,随着时间的推移或者特定情况的改变理论也可能不再正确。比如,牛顿的经典力学理论(好像扯得有点远)。如果要我说人话,就是:随着时间的推移,Linux的代码在不断的变化。或许以上的观点在曾经正确。当然,也可能现在还正确。下面的分析就是我的观点了。同样,大家也是需要保持怀疑的态度。

2、函数定义

首先我们看下memcpy()和copy_{to,from}_user()的函数定义。参数几乎没有差别,都包含目的地址,源地址和需要复制的字节size。

static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n);
static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n);
void *memcpy(void *dest, const void *src, size_t len);

但是,有一点我们肯定是知道的。那就是memcpy()没有传入地址合法性校验。而copy_{to,from}_user()针对传入地址进行类似下面的合法性校验(简单说点,更多校验详情可以参考代码)。

  • 如果从用户空间copy数据到内核空间,用户空间地址to及to加上copy的字节长度n必须位于用户空间地址空间。
  • 如果从内核空间copy数据到用户空间,当然也需要检查地址的合法性。例如,是否越界访问或者是不是代码段的数据等等。总之一切不合法地操作都需要立刻杜绝。

经过简单的对比之后,我们再看看其他的差异以及一起探讨下上面提出的2个观点。我们先从第2个观点说起。涉及实践,我还是有点相信实践出真知。从我测试的结果来说,实现结果分成两种情况。

第一种情况的结果是:使用memcpy()测试,没有出现问题,代码正常运行。测试代码如下(仅仅展示proc文件系统下file_operations对应的read接口函数):

static ssize_t test_read(struct file *file, char __user *buf,
                         size_t len, loff_t *offset)
{
        memcpy(buf, "test\n", 5);    /* copy_to_user(buf, "test\n", 5) */
        return 5;
}

我们使用cat命令读取文件内容,cat会通过系统调用read调用test_read,并且传递的buf大小是4k。测试很顺利,结果很喜人。成功地读到了“test”字符串。看起来,第2点观点是没毛病的。但是,我们还需要继续验证和探究下去。因为第1个观点提到,“在内核空间这种缺页异常必须被显式地修复”。因此我们还需要验证的情况是:如果buf在用户空间已经分配虚拟地址空间,但是并没有建立和物理内存的具体映射关系,这种情况下会出现内核态page fault。我们首先需要创建这种条件,找到符合的buf,然后测试。这里我当然没测啦。因为有测试结论(主要是因为我懒,构造这个条件我觉得比较麻烦)。这个测试是我的一个朋友,人称宋老师的“阿助教”阿克曼大牛。他曾经做个这个实验,并且得到的结论是:即使是没有建立和物理内存的具体映射关系的buf,代码也可以正常运行。在内核态发生page fault,并被其修复(分配具体物理内存,填充页表,建立映射关系)。同时,我从代码的角度分析,结论也是如此。

经过上面的分析,看起来好像是memcpy()也可以正常使用,鉴于安全地考虑建议使用copy_{to,from}_user()等接口。

第二种情况的结果是:以上的测试代码并没有正常运行,并且会触发kernel oops。当然本次测试和上次测试的kernel配置选项是不一样的。这个配置项是 CONFIG_ARM64_SW_TTBR0_PAN或者 CONFIG_ARM64_PAN(针对ARM64平台)。两个配置选项的功能都是阻止内核态直接访问用户地址空间。只不过CONFIG_ARM64_SW_TTBR0_PAN是软件仿真实现这种功能,而CONFIG_ARM64_PAN是硬件实现功能(ARMv8.1扩展功能)。我们以CONFIG_ARM64_SW_TTBR0_PAN作为分析对象(软件仿真才有代码提供分析)。BTW,如果硬件不支持,即使配置CONFIG_ARM64_PAN也没用,只能使用软件仿真的方法。如果需要访问用户空间地址需要通过类似copy_{to,from}_user()的接口,否则会导致kernel oops。

在打开CONFIG_ARM64_SW_TTBR0_PAN的选项后,测试以上代码就会导致kernel oops。原因就是内核态直接访问了用户空间地址。因此,在这种情况我们就不可以使用memcpy()。我们别无选择,只能使用copy_{to,from}_user()。

为什么我们需要PAN(Privileged Access Never)功能呢?原因可能是用户空间和内核空间数据交互上容易引入安全问题,所以我们就不让内核空间轻易访问用户空间,如果非要这么做,就必须通过特定的接口关闭PAN。另一方面,PAN功能可以更加规范化内核态和用户态数据交互的接口使用。在使能PAN功能的情况下,可以迫使内核或者驱动开发者使用copy_{to,from}_user()等安全接口,提升系统的安全性。类似memcpy()非规范操作,kernel就oops给你看。

由于编程的不规范而引入安全漏洞。例如:Linux内核漏洞CVE-2017-5123可以提升权限。该漏洞的引入原因就是是缺少access_ok()检查用户传递地址的合法性。因此,为了避免自己编写的代码引入安全问题,针对内核空间和用户空间数据交互上,我们要格外当心。

二、CONFIG_ARM64_SW_TTBR0_PAN原理

CONFIG_ARM64_SW_TTBR0_PAN原理背后设计的原理。由于ARM64的硬件特殊设计,我们使用两个页表基地址寄存器ttbr0_el1和ttbr1_el1。处理器根据64 bit地址的高16 bit判断访问的地址属于用户空间还是内核空间。如果是用户空间地址则使用ttbr0_el1,反之使用ttbr1_el1。因此,ARM64进程切换的时候,只需要改变ttbr0_el1的值即可。ttbr1_el1可以选择不需要改变,因为所有的进程共享相同的内核空间地址。

当进程切换到内核态(中断,异常,系统调用等)后,如何才能避免内核态访问用户态地址空间呢?其实不难想出,改变ttbr0_el1的值即可,指向一段非法的映射即可。因此,我们为此准备了一份特殊的页表,该页表大小4k内存,其值全是0。当进程切换到内核态后,修改ttbr0_el1的值为该页表的地址即可保证访问用户空间地址是非法访问。因为页表的值是非法的。这个特殊的页表内存通过链接脚本分配。

#define RESERVED_TTBR0_SIZE    (PAGE_SIZE)
SECTIONS
{
        reserved_ttbr0 = .;
        . += RESERVED_TTBR0_SIZE;
        swapper_pg_dir = .;
        . += SWAPPER_DIR_SIZE;
        swapper_pg_end = .;
}

这个特殊的页表和内核页表在一起。和swapper_pg_dir仅仅差4k大小。reserved_ttbr0地址开始的4k内存空间的内容会被清零。

当我们进入内核态后会通过__uaccess_ttbr0_disable切换ttbr0_el1以关闭用户空间地址访问,在需要访问的时候通过_uaccess_ttbr0_enable打开用户空间地址访问。这两个宏定义也不复杂,就以_uaccess_ttbr0_disable为例说明原理。其定义如下:

macro    __uaccess_ttbr0_disable, tmp1
    mrs    \tmp1, ttbr1_el1                        // swapper_pg_dir (1)
    bic    \tmp1, \tmp1, #TTBR_ASID_MASK
    sub    \tmp1, \tmp1, #RESERVED_TTBR0_SIZE      // reserved_ttbr0 just before
                                                // swapper_pg_dir (2)
    msr    ttbr0_el1, \tmp1                        // set reserved TTBR0_EL1 (3)
    isb
    add    \tmp1, \tmp1, #RESERVED_TTBR0_SIZE
    msr    ttbr1_el1, \tmp1                       // set reserved ASID
    isb
.endm
  • ttbr1_el1存储的是内核页表基地址,因此其值就是swapper_pg_dir。
  • swapper_pg_dir减去RESERVED_TTBR0_SIZE就是上面描述的特殊页表。
  • 将ttbr0_el1修改指向这个特殊的页表基地址,当然可以保证后续访问用户地址都是非法的。

__uaccess_ttbr0_disable对应的C语言实现可以参考这里。如何允许内核态访问用户空间地址呢?也很简单,就是__uaccess_ttbr0_disable的反操作,给ttbr0_el1赋予合法的页表基地址。这里就不必重复了。我们现在需要知道的事实就是,在配置CONFIG_ARM64_SW_TTBR0_PAN的情况下,copy_{to,from}_user()接口会在copy之前允许内核态访问用户空间,并在copy结束之后关闭内核态访问用户空间的能力。因此,使用copy_{to,from}_user()才是正统做法。主要体现在安全性检查及安全访问处理。这里是其比memcpy()多的第一个特性,后面还会介绍另一个重要特性。

现在我们可以解答上一节中遗留的问题。怎样才能继续使用memcpy()?现在就很简单了,在memcpy()调用之前通过uaccess_enable_not_uao()允许内核态访问用户空间地址,调用memcpy(),最后通过uaccess_disable_not_uao()关闭内核态访问用户空间的能力。

三、测试

以上的测试用例都是建立在用户空间传递合法地址的基础上测试的,何为合法的用户空间地址?用户空间通过系统调用申请的虚拟地址空间包含的地址范围,即是合法的地址(不论是否分配物理页面建立映射关系)。既然要写一个接口程序,当然也要考虑程序的健壮性,我们不能假设所有的用户传递的参数都是合法的。我们应该预判非法传参情况的发生,并提前做好准备,这就是未雨绸缪。

我们首先使用memcpy()的测试用例,随机传递一个非法的地址。经过测试发现:会触发kernel oops。继续使用copy_{to,from}_user()替代memcpy()测试。测试发现:read()仅仅是返回错误,但不会触发kernel oops。这才是我们想要的结果。毕竟,一个应用程序不应该触发kernel oops。这种机制的实现原理是什么呢?

我们以copy_to_user()为例分析。函数调用流程是:

copy_to_user()->_copy_to_user()->raw_copy_to_user()->__arch_copy_to_user()

_arch_copy_to_user()在ARM64平台是汇编代码实现,这部分代码很关键。

end    .req    x5
ENTRY(__arch_copy_to_user)
        uaccess_enable_not_uao x3, x4, x5
        add    end, x0, x2
#include "copy_template.S"
        uaccess_disable_not_uao x3, x4
        mov    x0, #0
        ret
ENDPROC(__arch_copy_to_user)
        .section .fixup,"ax"
        .align    2
9998:    sub x0, end, dst            // bytes not copied
        ret
        .previous
  • uaccess_enable_not_uao和uaccess_disable_not_uao是上面说到的内核态访问用户空间的开关。
  • copy_template.S文件是汇编实现的memcpy()的功能,稍后看看memcpy()的实现代码就清楚了。
  • .section.fixup,“ax”定义一个section,名为“.fixup”,权限是ax(‘a'可重定位的段,‘x'可执行段)。 9998标号处的指令就是善后处理工作。还记得copy_{to,from}_user()返回值的意义吗?返回0代表copy成功,否则返回剩余没有copy的字节数。这行代码就是计算剩余没有copy的字节数。当我们访问非法的用户空间地址的时候,就一定会触发page fault。这种情况下,内核态发生的page fault并返回的时候并没有修复异常,所以肯定不能返回发生异常的地址继续运行。所以,系统可以有2个选择:第1个选择是kernel oops,并给当前进程发送SIGSEGV信号;第2个选择是不返回出现异常的地址运行,而是选择一个已经修复的地址返回。如果使用的是memcpy()就只有第1个选择。但是copy_{to,from}_user()可以有第2个选择。 .fixup段就是为了实现这个修复功能。当copy过程中出现访问非法用户空间地址的时候,do_page_fault()返回的地址变成 9998标号处,此时可以计算剩余未copy的字节长度,程序还可以继续执行。

对比前面分析的结果,其实_arch_copy_to_user()可以近似等效如下关系。

uaccess_enable_not_uao();
memcpy(ubuf, kbuf, size);      ==     __arch_copy_to_user(ubuf, kbuf, size);
uaccess_disable_not_uao();

先插播一条消息,解释copy_template.S为何是memcpy()。memcpy()在ARM64平台是由汇编代码实现。其定义在arch/arm64/lib/memcpy.S文件。

.weak memcpy
ENTRY(__memcpy)
ENTRY(memcpy)
#include "copy_template.S"
        ret
ENDPIPROC(memcpy)
ENDPROC(__memcpy)

所以很明显,memcpy()和__memcpy()函数定义是一样的。并且memcpy()函数声明是weak,因此可以重写memcpy()函数(扯得有点远)。再扯一点,为何使用汇编呢?为何不使用lib/string.c文件的memcpy()函数呢?当然是为了优化memcpy() 的执行速度。lib/string.c文件的memcpy()函数是按照字节为单位进行copy(再好的硬件也会被粗糙的代码毁掉)。但是现在的处理器基本都是32或者64位,完全可以4 bytes或者8 bytes甚至16 bytes copy(考虑地址对齐的情况下)。可以明显提升执行速度。所以,ARM64平台使用汇编实现。这部分知识可以参考这篇博客《ARM64 的 memcpy 优化与实现》。

下面继续进入正题,再重复一遍:内核态访问用户空间地址,如果触发page fault,只要用户空间地址合法,内核态也会像什么也没有发生一样修复异常(分配物理内存,建立页表映射关系)。但是如果访问非法用户空间地址,就选择第2条路,尝试救赎自己。这条路就是利用 .fixup__ex_table段。如果无力回天只能给当前进程发送SIGSEGV信号。并且,轻则kernel oops,重则panic(取决于kernel配置选项CONFIG_PANIC_ON_OOPS)。在内核态访问非法用户空间地址的情况下,do_page_fault()最终会跳转 no_context标号处的do_kernel_fault()。

static void __do_kernel_fault(unsigned long addr, unsigned int esr,
                              struct pt_regs *regs)
{
        /*
         * Are we prepared to handle this kernel fault?
         * We are almost certainly not prepared to handle instruction faults.
         */
        if (!is_el1_instruction_abort(esr) && fixup_exception(regs))
                return;
        /* ... */
}

fixup_exception()继续调用search_exception_tables(),其通过查找_extable段。__extable段存储exception table,每个entry存储着异常地址及其对应修复的地址。例如上述的 9998:subx0,end,dst指令的地址就会被找到并修改do_page_fault()函数的返回地址,以达到跳转修复的功能。其实查找过程是根据出问题的地址addr,查找_extable段(exception table)是否有对应的exception table entry,如果有就代表可以被修复。由于32位处理器和64位处理器实现方式有差别,因此我们先从32位处理器异常表的实现原理说起。

_extable段的首尾地址分别是 __start___ex_table和 __stop___ex_table(定义在include/asm-generic/vmlinux.lds.h。这段内存可以看作是一个数组,数组的每个元素都是 struct exception_table_entry类型,其记录着异常发生地址及其对应的修复地址。

                        exception tables
__start___ex_table --> +---------------+
                       |     entry     |
                       +---------------+
                       |     entry     |
                       +---------------+
                       |      ...      |
                       +---------------+
                       |     entry     |
                       +---------------+
                       |     entry     |
__stop___ex_table  --> +---------------+

在32位处理器上,struct exception_table_entry定义如下:

struct exception_table_entry {
        unsigned long insn, fixup;
};

有一点需要明确,在32位处理器上,unsigned long是4 bytes。insn和fixup分别存储异常发生地址及其对应的修复地址。根据异常地址ex_addr查找对应的修复地址(未找到返回0),其示意代码如下:

unsigned long search_fixup_addr32(unsigned long ex_addr)
{
        const struct exception_table_entry *e;
        for (e = __start___ex_table; e < __stop___ex_table; e++)
                if (ex_addr == e->insn)
                        return e->fixup;
        return 0;
}

在32位处理器上,创建exception table entry相对简单。针对copy{to,from}user()汇编代码中每一处用户空间地址访问的指令都会创建一个entry,并且insn存储当前指令对应的地址,fixup存储修复指令对应的地址。

当64位处理器开始发展起来,如果我们继续使用这种方式,势必需要2倍于32位处理器的内存存储exception table(因为存储一个地址需要8 bytes)。所以,kernel换用另一种方式实现。在64处理器上,struct exception_table_entry定义如下:

struct exception_table_entry {
        int insn, fixup;
};

每个exception table entry占用的内存和32位处理器情况一样,因此内存占用不变。但是insn和fixup的意义发生变化。insn和fixup分别存储着异常发生地址及修复地址相对于当前结构体成员地址的偏移(有点拗口)。例如,根据异常地址ex_addr查找对应的修复地址(未找到返回0),其示意代码如下:

unsigned long search_fixup_addr64(unsigned long ex_addr)
{
        const struct exception_table_entry *e;
        for (e = __start___ex_table; e < __stop___ex_table; e++)
                if (ex_addr == (unsigned long)&e->insn + e->insn)
                        return (unsigned long)&e->fixup + e->fixup;
        return 0;
}

因此,我们的关注点就是如何去构建exception_table_entry。我们针对每个用户空间地址的内存访问都需要创建一个exception table entry,并插入_extable段。例如下面的汇编指令(汇编指令对应的地址是随意写的,不用纠结对错。理解原理才是王道)。

0xffff000000000000: ldr x1, [x0]
0xffff000000000004: add x1, x1, #0x10
0xffff000000000008: ldr x2, [x0, #0x10]
/* ... */
0xffff000040000000: mov x0, #0xfffffffffffffff2    // -14
0xffff000040000004: ret

假设x0寄存器保存着用户空间地址,因此我们需要对0xffff000000000000地址的汇编指令创建一个exception table entry,并且我们期望当x0是非法用户空间地址时,跳转返回的修复地址是0xffff000040000000。为了计算简单,假设这是创建第一个entry, __start___ex_table值是0xffff000080000000。那么第一个exception table entry的insn和fixup成员的值分别是:0x80000000和0xbffffffc(这两个值都是负数)。因此,针对copy{to,from}user()汇编代码中每一处用户空间地址访问的指令都会创建一个entry。所以0xffff000000000008地址处的汇编指令也需要创建一个exception table entry。

所以,如果内核态访问非法用户空间地址究竟发生了什么?上面的分析流程可以总结如下:

  • 0xffff000000000000:ldr x1,[x0]
  • MMU触发异常
  • CPU调用do_page_fault()
  • do_page_fault()调用search_exception_table()(regs->pc == 0xffff000000000000)
  • 查看_extable段,寻找0xffff000000000000 并且返回修复地址0xffff000040000000
  • do_page_fault()修改函数返回地址(regs->pc = 0xffff000040000000)并返回
  • 程序继续执行,处理出错情况
  • 修改函数返回值x0 = -EFAULT (-14) 并返回(ARM64通过x0传递函数返回值)

四、总结

到了回顾总结的时候,copy_{to,from}_user()的思考也到此结束。我们来个总结结束此文。

无论是内核态还是用户态访问合法的用户空间地址,当虚拟地址并未建立物理地址的映射关系的时候,page fault的流程几乎一样,都会帮助我们申请物理内存并创建映射关系。所以这种情况下memcpy()和copy_{to,from}_user()是类似的。

当内核态访问非法用户空间地址的时候,根据异常地址查找修复地址。这种修复异常的方法并不是建立地址映射关系,而是修改do_page_fault()返回地址。而memcpy()无法做到这点。

在使能 CONFIG_ARM64_SW_TTBR0_PAN或者 CONFIG_ARM64_PAN(硬件支持的情况下才有效)的时候,我们只能使用copy_{to,from}_user()这种接口,直接使用memcpy()是不行的。

最后,我想说,即使在某些情况下memcpy()可以正常工作。但是,这也是不推荐的,不是良好的编程习惯。在用户空间和内核空间数据交互上,我们必须使用类似copy_{to,from}_user()的接口。为什么类似呢?因为还有其他的接口用于内核空间和用户空间数据交互,只是没有copy_{to,from}_user()出名。例如:{get,put}_user()。

到此这篇关于copy_{to, from}_user()的思考的文章就介绍到这了,更多相关copy、user内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Linux内核中的container_of函数

    前言 在linux 内核中,container_of 函数使用非常广,例如 linux内核链表 list_head.工作队列work_struct中. 在linux内核中大名鼎鼎的宏container_of() ,其实它的语法很简单,只是一些指针的灵活应用,它分两步: 第一步,首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值. 第二步,用(char *)__mptr减去member在结构体

  • Linux内核宏Container_Of的详细解释

    目录 1. 结构体在内存中是如何存储的 2. container_of宏 3. typeof 4. (((type *)0)->member) 5. const typeof(((type * )0) ->member)*__mptr = (ptr); 6. offsetof(type, member)) 7. (type * )((char * )__mptr - offsetof(type, member)) 8. 举例 1. 结构体在内存中是如何存储的 int main() { Stud

  • Linux内核宏container_of的深度剖析

    1.前面说的 我在好几年前读linux 驱动代码的时候看到这个宏,百度了好久,知道怎么用了,但是对实现过程和原理还是一知半解. container_of宏 在linux内核代码里面使用次数非常非常多,对于喜欢linux编程的同学来说,了解其实现方法,对以后看内核代码,写内核驱动的帮助都非常大,当然,我不是说了解这个就可以为所欲为了,内核博大精深,先宏观再微观去学习,不积跬步何以致千里,不要想着一口就能吃成一个胖子,我这篇文章主要剖析一下这个函数的实现原理,希望对大家学习过程中有所帮助. andr

  • VMware Workstation安装(Linux内核)银河麒麟图文教程

    本文为大家分享了VMware Workstation安装银河麒麟,供大家参考,具体内容如下 1.下载软件:VMware Workstation Kylin-x86_64.iso(Linux内核)银河麒麟系统镜像包. 2.安装完成VMware Workstation并运行. 3.创建新的虚拟机. 4.选中"典型",下一步. 5.安装程序光盘映像文件(浏览--文件存放路径),下一步. 6.虚拟机中安装操作系统选择Linux,下一步. 7.输入虚拟机名称,下一步. 8.默认操作,下一步. 9

  • linux内核编程container of()函数介绍

    前言 在linux 内核编程中,会经常见到一个宏函数container_of(ptr,type,member), 但是当你通过追踪源码时,像我们这样的一般人就会绝望了(这一堆都是什么呀? 函数还可以这样定义??? 怎么还有0呢???  哎,算了,还是放弃吧...). 这就是内核大佬们厉害的地方,随便两行代码就让我们怀疑人生,凡是都需要一个过程,慢慢来吧. 其实,原理很简单:  已知结构体type的成员member的地址ptr,求解结构体type的起始地址. type的起始地址 = ptr - s

  • linux内核copy_{to, from}_user()的思考

    目录 一.什么是copy_{to,from}_user() 1.copy_{to,from}_user()对比memcpy() 2.函数定义 二.CONFIG_ARM64_SW_TTBR0_PAN原理 三.测试 四.总结 一.什么是copy_{to,from}_user() 它是kernel space和user space沟通的桥梁.所有的数据交互都应该使用类似这种接口.但是他的作用究竟是什么呢?我们对下提出疑问: 为什么需要copy_{to,from}_user(),它究竟在背后为我们做了什

  • Linux内核启动参数详解

    1.环境: Ubuntu 16.04 Linux linuxidc 4.4.0-89-generic #112-Ubuntu SMP Mon Jul 31 19:38:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 2.查看当前linux内核的启动参数: cat /proc/cmdline 笔者的输出内容如下: BOOT_IMAGE=/boot/vmlinuz-4.4.0-89-generic root=UUID=bef418fa-4202-4513-b39

  • 简单谈谈Linux内核定时器

    软件意义上的定时器最终依赖硬件定时器来实现, 内核在时钟中断发生后检测各定时器是否到期 , 到期后的定时器处理函数将作为软中断在底半部执行 .实质上,时钟中断处理程序会 换起TIMER_SOFTIRQ软中断 ,运行当前处理器上到期的所有定时器. 总结起来还是软中断的流程 a.注册软中断处理函数 /*/linux/kernel.timer.c*/ void __init init_timers(void) -->open_softirq(TIMER_SOFTIRQ, run_timer_softi

  • Linux内核漏洞浅析

    与Windows相比,Linux被认为具有更好的安全性和其他扩展性能.这些特性使得Linux在操作系统领域异军突起,得到越来越多的重视.随着Linux应用量的增加,其安全性也逐渐受到了公众甚或黑客的关注.那么,Linux是否真的如其支持厂商们所宣称的那样安全呢?本期我们请到了启明星辰信息技术有限公司积极防御实验室工程师赵伟,对Linux进行专业的漏洞技术分析. Linux内核精短.稳定性高.可扩展性好.硬件需求低.免费.网络功能丰富.适用于多种cpu等特性,使之在操作系统领域异军突起.其独特的魅

  • Linux内核链表实现过程

    关于双链表实现,一般教科书上定义一个双向链表节点的方法如下: 复制代码 代码如下: struct list_node{stuct list_node *pre;stuct list_node *next;ElemType data; } 即一个链表节点包含:一个指向前向节点的指针.一个指向后续节点的指针,以及数据域共三部分.但查看linux内核代码中的list实现时,会发现其与教科书上的方法有很大的差别.来看看linux是如何实现双链表.双链表节点定义 复制代码 代码如下: struct lis

  • 浅谈Linux内核创建新进程的全过程

    进程描述 进程描述符(task_struct) 用来描述进程的数据结构,可以理解为进程的属性.比如进程的状态.进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct 进程控制块(PCB) 是操作系统核心中一种数据结构,主要表示进程状态. 进程状态 fork() fork()在父.子进程各返回一次.在父进程中返回子进程的 pid,在子进程中返回0. fork一个子进程的代码 #include <stdio.h> #include <stdli

  • SYN Cookie在Linux内核中的实现

    概述 在目前以IPv4为支撑的网络协议上搭建的网络环境中,SYN Flood是一种非常危险而常见的DoS攻击方式.到目前为止,能够有效防范SYN Flood攻击的手段并不多,而SYN Cookie就是其中最著名的一种.SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明.在很多操作系统上都有各种各样的实现.其中包括Linux.本文就分别介绍一下SYN Flood攻击和SYN Cookie的原理,更重要的是介绍Linux内核中实现SYN Cookie的方式.最后,

  • 一张图看尽Linux内核运行原理

    众所周知的是,几乎整个互联网都运行在 Linux 上,从网络协议,到服务器,到你平常访问的绝大多数网站,都能看到它的身影.Linux 内核就是最复杂最流行的开源项目之一.如果你希望学习内核知识,在网上可以搜到无数的资料,但是 Linux 内核还是一个非常难弄明白的项目. 俗话说:一图胜千言,今天我们就为大家介绍一张完整的 Linux 内核运行原理图,通过这张图,你可以很方便地学习内核知识. 在 Linux 内核中,有许多层次.模块.功能调用和函数:要把其中的每一块儿都弄明白很不容易,不过 Mak

  • Linux内核中红黑树算法的实现详解

    一.简介 平衡二叉树(BalancedBinary Tree或Height-Balanced Tree) 又称AVL树.它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1.若将二叉树上结点的平衡因子BF(BalanceFactor)定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1.0和1.(此段定义来自严蔚敏的<数据结构(C语言版)>) 红黑树 R-B Tree,全称是Red-B

随机推荐