C和内存中的资源保护

Posted

技术标签:

【中文标题】C和内存中的资源保护【英文标题】:C and resource protection in memory 【发布时间】:2016-01-20 19:12:07 【问题描述】:

当我们编译一个 C 程序时,它只是生成一些机器可以理解的代码。这段代码可以直接在硬件上运行,告诉this question。

所以我的问题是:

    如果一个C程序可以直接在硬件上运行,内核如何处理这个程序的资源分配?

    如果编译器生成的可执行文件是纯机器可理解的形式,那么特权模式和非特权模式如何工作?

    如果程序可以不通过内核直接在硬件上运行,那么内核如何管理硬件资源的权限?

【问题讨论】:

没有。假设一个非平凡的操作系统,它甚至无法在没有内核调用的情况下加载到内存中,更不用说获取执行了。 "当我们编译 C 程序时,它只是生成机器可以理解的代码。这些代码可以直接在硬件上运行。" - 该机器可理解的代码对内核进行系统调用以分配资源。如果您想使用裸机,您的应用程序将在启动时加载,然后,是的,您可以做任何您想做的事情。除此之外,您符合内核允许的任何内容。 【参考方案1】:

内核如何处理分配给这个程序的资源

内核提供函数和机制来分配内存、执行 I/O(写入屏幕、与网络/声卡交互)等,称为系统调用给用户程序。这些系统调用是内核和用户程序之间的接口,也是硬件和用户程序之间的接口。

特权模式和非特权模式如何工作?

用户程序处于非特权模式(用户空间),而内核运行在特权模式(内核空间)。用户不能被信任,所以如果他搞砸了(例如访问更高特权的内存或取消引用空指针),他就会被阻止(例如,由于分段错误和程序的以下终止)。

另一方面,内核在特权模式下运行。它可以为所欲为:写入用户空间程序,从用户程序中窃取数据(如密码),写入处理器的固件——一切。此外,还有不同种类的内核:单体内核和微内核是最重的(这个词是否存在?)。

Linux(由 Linus Torvalds 发起)是单片内核的一个示例。在这里,内核是一个大系统,每个内核代码都可以最终访问系统。

Minix(由 Andrew S. Tanenbaum 发起)是微内核的一个示例。可以访问所有内容的部分相当小。它仅包含必须具有特权的功能(管理 MMU、访问硬件)等。其他功能,如文件系统,在非特权模式下运行,在这种模式下,它们通过用户空间中采用的常用保护机制(非特权模式),如分段错误。

关于单体内核和微内核的优缺点的有趣读物是 Linus Torvalds(当时创建操作系统的某个人)和 Andrew S. Tanenbaum(当时是 CS 的知名教授)之间的debate;写了一些惊人的书,顺便说一句)。

程序可以不通过内核直接在硬件上运行

它确实直接在硬件上运行,由 CPU 执行。 它不能直接访问某些资源,但是,例如内存,并且为了访问这些资源,需要与内核交互。这是 DOS 等早期操作系统的主要改进之一(可能是虚拟处理器,即进程):用户空间程序不能直接在硬件上运行。如果可以的话,他们可能会因为无法弥补的原因(有意地 - 像病毒,或无意地)搞砸整个机器。相反,如本答案开头所述,使用系统调用。

在 DOS 中,您有 选项 来使用 OS 提供的例程(通常是在 IV(中断向量、实模式 IDT(中断描述符表)中的偏移量(和物理内存地址)处的陷阱) )) 0x21(通过int 0x21/int 21h 调用),而ax 包含一个标识系统调用的函数号1)。与现在可用严格执行的机制大致相同。可以覆盖整个操作系统,用自己的程序替换它并破坏机器(例如,将随机值加载到 CMOS 寄存器中)。也可以只使用 Bios 提供的例程,绕过操作系统。


1 我在这里故意使用“调用系统”而不是“系统调用”。在这里,系统调用只表示从用户空间到内核空间的请求,为它做点什么。由于 DOS(即实模式)没有真正区分用户空间和内核空间,因此它没有真正的系统调用。

【讨论】:

好答案!请注意,实际上有further rings of protection 针对固件更新,即使是特权进程也无法做到。但这对除了硬件设计师和真正可怕的攻击者之外的任何人都完全无关。 我不太同意“它不在实际硬件上运行”的底线,但是:当然程序实际上是由实际的 CPU 运行的(它没有被工具化,例如在模拟器中),但执行受到严格限制。 @MarcusMüller:我认为你错过了重点。物理 CPU 执行代码的事实不是问题。他显然是在谈论程序可以执行的不同模式。事实仍然是,在区分用户模式和内核模式的情况下,程序不能直接弄乱硬件资源和其他东西。 @ray:好的,我们能否就此达成一致:最重要的是,虽然程序确实直接在 CPU 上运行,但它的有限权限通常会禁止直接访问硬件和物理内存。跨度> @MarcusMüller:我们确实可以。 OP 应该很容易获得这种区别。【参考方案2】:

所以我的第一个问题是如果 C 程序可以直接在硬件上运行,内核如何处理该程序的资源分配。

CPU 在执行代码时带有特权的概念。例如,在 x86 上,有一个 Real Mode 允许代码访问任何资源,还有一个 Protected Mode ,其中代码以不同的security rings 执行。大多数操作系统将切换到保护模式,其中数字较低的环意味着更高的权限。

内核通常在 Ring 0 中执行,它可以直接访问硬件,而用户程序在 Ring 3 中运行,它限制访问。当用户程序需要访问特权资源时,CPU 会通过 system call 指令隐式或直接调用具有特权的操作系统(例如 x86-64 汇编中的 syscall) .

如果从 gcc 生成的可执行文件是纯机器可理解的形式,那么特权模式和非特权模式如何工作?

同样,CPU 会检查内存访问等内容。例如,如果一个程序试图访问它没有权限的virtual address,操作系统会捕获无效页面访问并通常向进程发出信号(即SIGSEGV)。

当程序可以不通过内核直接在硬件上运行时,内核如何管理硬件资源的权限?

CPU 必须通过特定的control registers 和表直接与操作系统交互。例如,虚拟地址页表的地址存储在 x86 的CR3 寄存器中。

【讨论】:

【参考方案3】:

如果一个 C 程序可以直接在 硬件内核如何处理对此的资源分配 程序。

内核负责管理整个计算机的资源,包括硬件等资源。这意味着,为了让用户级应用程序能够访问硬件设备、写入终端或读取文件,它们必须向内核请求许可。正如@Marcus 所提到的,这是通过使用操作系统公开的系统调用来完成的。

但是,我不会说程序直接在硬件上运行因为它不直接与硬件交互,作为内核模块/驱动程序将。客户端程序将为系统调用设置参数,然后中断内核并等待内核服务程序发出的中断请求。

这就是为什么今天的操作系统被称为在保护模式下运行的原因,而不是过去它们在真实模式下运行的原因,并且程序可以,例如,直接弄乱硬件资源——并可能搞砸。

如果您尝试在 x86 汇编中编写一个简单的“hello world”程序,这种区别就会变得非常明显。我几年前写过并记录了this one,转载如下:

;
; This program runs in 32-bit protected mode.
;  build: nasm -f elf -F stabs name.asm
;  link:  ld -o name name.o
;
; In 64-bit long mode you can use 64-bit registers (e.g. rax instead of eax, rbx instead of ebx, etc.)
; Also change "-f elf " for "-f elf64" in build command.
;
section .data                           ; section for initialized data
str:     db 'Hello world!', 0Ah         ; message string with new-line char at the end (10 decimal)
str_len: equ $ - str                    ; calcs length of string (bytes) by subtracting the str's start address
                                            ; from this address ($ symbol)

section .text                           ; this is the code section
global _start                           ; _start is the entry point and needs global scope to be 'seen' by the
                                            ; linker --equivalent to main() in C/C++
_start:                                 ; definition of _start procedure begins here
    mov eax, 4                   ; specify the sys_write function code (from OS vector table)
    mov ebx, 1                   ; specify file descriptor stdout --in gnu/linux, everything's treated as a file,
                                             ; even hardware devices
    mov ecx, str                 ; move start _address_ of string message to ecx register
    mov edx, str_len             ; move length of message (in bytes)
    int 80h                      ; interrupt kernel to perform the system call we just set up -
                                             ; in gnu/linux services are requested through the kernel
    mov eax, 1                   ; specify sys_exit function code (from OS vector table)
    mov ebx, 0                   ; specify return code for OS (zero tells OS everything went fine)
    int 80h                      ; interrupt kernel to perform system call (to exit)

注意程序如何设置写系统调用sys_write,然后指定写入位置的文件描述符stdout、要写入的字符串等等。

也就是说,程序本身并不执行写操作;它通过使用特殊的中断int 80h 来设置并要求内核代表它执行此操作。

一个可能的类比可能是你去一家餐馆。服务器将接受您的订单,但厨师将负责烹饪。在这个类比中,您是用户级应用程序,为您点餐的服务器是系统调用,厨房里的厨师是操作系统内核。

如果从 gcc 生成的可执行文件在纯机器中 可以理解的形式那么特权和非特权模式如何 工作吗?

从上一节开始,用户级程序总是在 user 模式下运行。当程序需要访问某些东西(例如终端、读取文件等)时,它会设置这些东西,就像上面的 sys_write 示例一样,并要求内核通过中断代表它完成。中断导致程序进入 kernel 模式并保持在那里,直到内核完成对客户端请求的服务——这可能包括完全拒绝它(例如,尝试读取用户无权访问的文件阅读)。

在内部,系统调用负责发出int 80h 指令。用户级应用只看到系统调用,这是客户端和操作系统之间的通用接口。

内核如何管理硬件资源的权限 程序可以不通过内核直接在硬件上运行吗?

如果您按照前面的说明进行操作,您现在可以看到内核充当看门人,并且程序通过使用int 80h 指令“敲门”。

【讨论】:

【参考方案4】:

虽然程序是机器码,但要在它自己的内存区域内做任何事情,它需要通过syscalls调用内核。

CPU 实际上有代码特权的概念。非特权代码不能直接访问物理内存等;它必须通过操作系统并要求它授予访问权限。

因此,每个程序都直接在 CPU 上运行,但这并不意味着它可以对硬件做任何事情 - 有针对此的硬件测量。您需要做某些事情的特权就是其中之一。

【讨论】:

我认为系统调用是内核可以理解的非二进制格式。因此可执行文件包含非机器可理解的组件(非二进制)或系统调用必须是二进制格式。哪个说法是对的? 没有。内核不“理解”任何这些。它被 cquestion 中的程序调用。你的论点没有一个是正确的,你将不得不更多地研究计算机的工作原理。 好的,你想告诉当程序编译时,所有信息都嵌入其中,关于资源,它可以使用多少 确切地说,编译器和链接器的工作是将您的源代码转换为机器代码,其中可能包含对符号的调用,例如来自用户态库,也可能包含对内核的调用。链接器获取被调用函数的名称,并将它们基本上转换为地址 - 并且在可执行文件中留下的内核函数的原始名称没有留下任何痕迹。

以上是关于C和内存中的资源保护的主要内容,如果未能解决你的问题,请参考以下文章

我的物联网之路—Linux中的C编程—多线程编程

C#.net如何手动释放内存资源

详解c++11多线程

托管堆和垃圾回收

为啥 io类的资源,在使用完后,需要进行释放

: 资源管理