mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-09-24 22:43:42 +08:00
update 20190102
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
https://blog.csdn.net/onetwothreef/article/details/49932579
|
||||
|
||||
```cpp
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
|
||||
pcb_tmp = find_task_by_pid(pid);
|
||||
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
|
||||
pcb_tmp = find_task_by_vpid(pid);
|
||||
#else
|
||||
pcb_tmp = pid_task(find_vpid(pid), PIDTYPE_PID);
|
||||
#endif
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
cmd_/home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.ko := aarch64-linux-gnu-ld -EL -r -T /usr/src/linux-headers-3.14.0-20150821.kylin.3.desktop/scripts/module-common.lds --build-id -o /home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.ko /home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.o /home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.mod.o
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
/home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.ko
|
||||
/home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.o
|
||||
@@ -1,30 +0,0 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/vermagic.h>
|
||||
#include <linux/compiler.h>
|
||||
|
||||
MODULE_INFO(vermagic, VERMAGIC_STRING);
|
||||
|
||||
__visible struct module __this_module
|
||||
__attribute__((section(".gnu.linkonce.this_module"))) = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.init = init_module,
|
||||
#ifdef CONFIG_MODULE_UNLOAD
|
||||
.exit = cleanup_module,
|
||||
#endif
|
||||
.arch = MODULE_ARCH_INIT,
|
||||
};
|
||||
|
||||
static const struct modversion_info ____versions[]
|
||||
__used
|
||||
__attribute__((section("__versions"))) = {
|
||||
{ 0x146be775, __VMLINUX_SYMBOL_STR(module_layout) },
|
||||
{ 0x47c8baf4, __VMLINUX_SYMBOL_STR(param_ops_uint) },
|
||||
{ 0xa5cc83f6, __VMLINUX_SYMBOL_STR(init_task) },
|
||||
{ 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
|
||||
};
|
||||
|
||||
static const char __module_depends[]
|
||||
__used
|
||||
__attribute__((section(".modinfo"))) =
|
||||
"depends=";
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
kernel//home/gatieme/Work/GitHub/LDD-LinuxDeviceDrivers/study/kernel/01-process/01-task/list_process/list_process.ko
|
||||
@@ -2194,6 +2194,30 @@ static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
|
||||
|
||||
* `xxxx_buddy` 的清除
|
||||
|
||||
[`clear_buddies`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L3764) 用来清除内核对调度实体 `se` 的 `buddy` 标记信息.
|
||||
|
||||
```cpp
|
||||
static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se)
|
||||
{
|
||||
if (cfs_rq->last == se)
|
||||
__clear_buddies_last(se);
|
||||
|
||||
if (cfs_rq->next == se)
|
||||
__clear_buddies_next(se);
|
||||
if (cfs_rq->skip == se)
|
||||
__clear_buddies_skip(se);
|
||||
}
|
||||
```
|
||||
|
||||
* 当内核 [`pick_next_entity`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L3958) 将调度实体 `se` 优选出来作为下一个进程的时候, 就需要清除之前对该实体的 `buddy` 标记.
|
||||
因为该进程很有可能是因为自己是 `last-buddy` 或者 `next-buudy` 而优选出来的. 这时候清除 `buddy` 信息. 从而不会对下次优选
|
||||
在进行影响. 有利于调度器的公平性和正常运作.参见 [`pick_next_entity`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L3958)
|
||||
|
||||
* [`yield_task_fair`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L6395) 会强制将当前运行的调度实体 `curr->se` 让出 `CPU`.
|
||||
这个是通过将其设置为 `skip_buddy` 而实现的. 但是在设置之前很有可能当前实体被设置了其它 `buddy` 标记. 因此也需要清楚. 参见 [`yield_task_fair`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L6395)
|
||||
|
||||
* [`dequeue_entity`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L3799) 当进程入队的时候, 也是需要将进程的 `buddy` 清除掉的.
|
||||
因此进程很有可能之前是带着 `buddy` 标记睡眠或者让出 `CPU`,不清除 `buddy` 标记必然对下次调度的行为造成影响. 参见 [`dequeue_entity`](https://elixir.bootlin.com/linux/v4.14.4/source/kernel/sched/fair.c#L3799)
|
||||
|
||||
|
||||
###11.4.3 `NEXT_BUDDY` && `LAST_BUDDY` 接口
|
||||
@@ -2267,6 +2291,16 @@ static void yield_task_fair(struct rq *rq)
|
||||
}
|
||||
```
|
||||
|
||||
##11.5 `CACHE_HOT_BUDDY`
|
||||
-------
|
||||
|
||||
###11.5.1 `CACHE_HOT_BUDDY`
|
||||
-------
|
||||
|
||||
调度器调度和选核的时候有一项重要的参考就是 `cache-hot`
|
||||
|
||||
|
||||
|
||||
# 参考资料
|
||||
-------
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
target=sp
|
||||
|
||||
all:$(target)
|
||||
|
||||
sp : sp.o
|
||||
|
||||
$(target) :
|
||||
$(CC) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
%.o : %.c
|
||||
$(CC) -c $^ -o $@ $(CFLAGS) $(DEFINES)
|
||||
|
||||
clean :
|
||||
rm -rf *.o
|
||||
rm -rf $(target)
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
|
||||
title: qemu中使用 9p virtio, 支持 host 和 guest 中共享目录
|
||||
date: 2018-09-02 18:40
|
||||
author: gatieme
|
||||
tags: qemu
|
||||
categories:
|
||||
- qemu
|
||||
thumbnail:
|
||||
blogexcerpt: 在使用qemu调试内核的时候, 如果没有网络,想要部署点驱动或者程序上去都需要重新制作文件系统,本文讲解了如何通过 9p virtio fs 实现在 qemu 和 host 机器上共享文件和目录。
|
||||
|
||||
---
|
||||
|
||||
| CSDN | GitHub | Hexo |
|
||||
|:----:|:------:|:----:|
|
||||
| [qemu中使用 9p virtio, 支持 host 和 guest 中共享目录](https://blog.csdn.net/gatieme/article/details/82912921) | [`AderXCoding/system/tools/qemu/0001-9p_virtio`](https://github.com/gatieme/AderXCoding/tree/master/system/tools/qemu/0001-9p_virtio) | [KernelShow(gatieme.github.io)](https://gatieme.github.io/2018/09/30/2018/09/0003-qemu_use_9pnet_virtio_fs_to_share_folder/index) |
|
||||
<br>
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a>
|
||||
|
||||
本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可, 转载请注明出处, 谢谢合作
|
||||
|
||||
因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 也欢迎大家提供一些其他好的调试工具以供收录, 鄙人在此谢谢啦
|
||||
|
||||
<br>
|
||||
|
||||
#1 Linux 地址随机化机制
|
||||
-------
|
||||
|
||||
##1.1 地址随机化机制
|
||||
-------
|
||||
|
||||
* ASLR(Address space layout randomization)
|
||||
|
||||
地址空间布局随机化, 是参与保护缓冲区溢出问题的一个计算机安全技术. 是为了防止攻击者在内存中能够可靠地对跳转到特定利用函数.
|
||||
|
||||
`ASLR` 包括随机排列程序的关键数据区域的位置, 包括可执行的部分、堆、栈及共享库的位置.
|
||||
|
||||
* 历史
|
||||
|
||||
在 `1997` 年, `Memco` 软件公司实现了一个有限的堆栈随机化作为 `SeOS` 访问控制产品的一部分.
|
||||
`Linux Pax` 项目第一次创建了 `ASLR` 这个术语. `ASLR` 的第一次设计实现是在 `2001` 年7月. 从 `2002` 年 `10`月开始提供内核栈随机化的实现.
|
||||
|
||||
* 作用
|
||||
|
||||
`ASLR` 通过制造更多让攻击者预测目标地址的困难以阻碍一些类型的安装攻击. 例如 : 攻击者试图执行返回到 `libc` 的攻击必须要找到要执行的代码, 而其他攻击者试图执行 `shellcode` 注入栈上则必须首先到栈. 在这两种情况下, 系统将模糊攻击者相关的存储器地址. 这些值被猜中,并且错误的猜测由于应用程序崩溃通常是不可恢复的.
|
||||
|
||||
* 有效性
|
||||
|
||||
地址空间布局随机化是基于攻击者猜测随机化空间位置的可能性降低. 安全是通过增加搜索空间的方式来实现的. 因此, `ASLR` 提供更多的熵存在于随机偏移中时是更有效的. 熵增加或许提高了其随机出现虚拟内存区域的空间量或减少了其随机发生的时期. 该期间通常被实现尽可能小, 因此, 大多数系统必须增加 `VMA` 空间随机化.
|
||||
|
||||
要打败随机化, 攻击者必须成功猜出所有他们想要攻击的区域的位置. 为数据区, 如堆和栈, 定制代码或者有用的数据可以被加载, 一个以上的状态可以通过使用NOP滑动代码或数据的重复拷贝被攻击. 如果一个区域被分配到少数值中的一个将被允许攻击成功. 与此相反, 代码区域例如: 基础库, 主要的可执行的需要准确地发现. 通常这些区域被混合, 例如堆栈桢被注入到栈和动态库中.
|
||||
|
||||
|
||||
参考 [维基百科](http://en.wikipedia.org/wiki/Address_space_layout_randomization#Linux)
|
||||
|
||||
##1.2 Linux 地址随机化
|
||||
-------
|
||||
|
||||
###1.2.1 用户态地址随机化
|
||||
-------
|
||||
|
||||
`ASLR(Address Space Layout Randomization)` 在 `2005` 年被引入到 `Linux` 的内核 `kernel 2.6.12` 中(参见[`Address space randomization in 2.6`](https://lwn.net/Articles/121845), 当然早在 `2004` 年就以 `patch` 的形式被引入. 随着内存地址的随机化, 使得响应的应用变得随机. 这意味着同一应用多次执行所使用内存空间完全不同, 也意味着简单的缓冲区溢出攻击无法达到目的.
|
||||
|
||||
GDB从版本7开始,第一次在Ubuntu 9.10(Karmic)上,被调试的程序可以被关闭ASLR(通过标记位ADDR_NO_RANDOMIZE )。
|
||||
|
||||
此处有坑,笔者有一个Ubuntu 9.10的虚拟机,用了下面将要介绍的全部姿势,死活关闭不了ASLR,后来换成Ubuntu 10.04就没问题了,说明Ubuntu 9.10的版本控制ASLR的方法还不成熟,需要重源码层面确认是否可以关闭开启,真是坑到家了。
|
||||
|
||||
可以将进程的 `mmap` 的基址, `stack` 和 `vdso` 页面地址固定下来.
|
||||
可以通过设置 `kernel.randomize_va_space` 内核参数来设置内存地址随机化的行为.
|
||||
目前 `randomize_va_space` 的值有三种, 分别是 `[0, 1, 2]`
|
||||
|
||||
* 0 表示关闭进程地址空间随机化.
|
||||
|
||||
* 1 表示将 `mmap` 的基址, `stack` 和 `vdso` 页面随机化。
|
||||
|
||||
* 2 表示在 `1` 的基础上增加栈(`heap`)的随机化。
|
||||
|
||||
|
||||
```cpp
|
||||
echo 0 >/proc/sys/kernel/randomize_va_space
|
||||
```
|
||||
|
||||
###1.2.3 KASLR 内核态地址随机化
|
||||
-------
|
||||
|
||||
`Linux` 内核对用户态地址随机化的支持在 `2005` 年的 `2.6.12` 版本就合并到了 `mainline`, 但是内核态的地址随机化却很长一段时间没有动静. `2011` 年的时候, `Dan Rosenberg` 提议增加内核 `ASLR` 的功能但后来一直没有实施下去, 最近Kees Cook向社区提交内核地址随机化的补丁,社区可
|
||||
能会在最近把这个补丁合并到mainline upstream repo里去。
|
||||
|
||||
绕过ASLR并不是一门新的技术,早在2002年的Phrack Issue 59中就已经有一篇论文详细的
|
||||
描述了原理和细节,个人认为内核空间的ASLR是非常有必要的,在内存和硬盘上隐藏内核空
|
||||
间地址是一个暂时的方案,比如:
|
||||
|
||||
|
||||
通过用下面这个程序,可以检查是否修改成功(x86_64):
|
||||
// gcc -g stack.c -o stack
|
||||
//
|
||||
unsigned long sp(void){ asm("mov %rsp, %rax");}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
unsigned long esp = sp();
|
||||
printf("Stack pointer (ESP : 0x%lx)\n",esp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
关闭前运行结果
|
||||
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fff50162e50)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fff5d023730)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7ffff9982180)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffb23612a0)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7ffffd5a4980)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffbac61bf0)
|
||||
|
||||
关闭后运行结果
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffffffeaf0)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffffffeaf0)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffffffeaf0)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffffffeaf0)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffffffeaf0)
|
||||
-bash-4.1# ./stack
|
||||
Stack pointer (ESP : 0x7fffffffeaf0)
|
||||
|
||||
参考:
|
||||
http://en.wikipedia.org/wiki/Address_space_layout_randomization
|
||||
http://xorl.wordpress.com/2011/01/16/linux-kernel-aslr-implementation/
|
||||
---------------------
|
||||
作者:功名半纸
|
||||
来源:CSDN
|
||||
原文:https://blog.csdn.net/force_eagle/article/details/8024502
|
||||
版权声明:本文为博主原创文章,转载请附上博文链接!
|
||||
|
||||
<br>
|
||||
|
||||
* 本作品/博文 ( [AderStep-紫夜阑珊-青伶巷草 Copyright ©2013-2017](http://blog.csdn.net/gatieme) ), 由 [成坚(gatieme)](http://blog.csdn.net/gatieme) 创作.
|
||||
|
||||
* 采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可. 欢迎转载、使用、重新发布, 但务必保留文章署名[成坚gatieme](http://blog.csdn.net/gatieme) ( 包含链接: http://blog.csdn.net/gatieme ), 不得用于商业目的.
|
||||
|
||||
* 基于本文修改后的作品务必以相同的许可发布. 如有任何疑问,请与我联系.
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// gcc -g stack.c -o stack
|
||||
//
|
||||
unsigned long sp(void){ asm("mov %rsp, %rax");}
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
unsigned long esp = sp();
|
||||
printf("Stack pointer (ESP : 0x%lx)\n",esp);
|
||||
return 0;
|
||||
}
|
||||
@@ -6,7 +6,10 @@
|
||||
#include <linux/list.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mm_types.h>
|
||||
|
||||
//#include <asm/pgtable_32_types.h>
|
||||
//#include <asm-generic/sections.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
// [《深入理解Linux内核》内存寻址学习心得](http://blog.chinaunix.net/uid-27776472-id-4304663.html)
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
@@ -16,29 +19,29 @@ MODULE_LICENSE("Dual BSD/GPL");
|
||||
int print_bit(char *addr, int size)
|
||||
{
|
||||
|
||||
unsigned char *ptr = (unsigned char *)addr;
|
||||
int print_bytes = 0;
|
||||
int print_bits = 7;
|
||||
unsigned char *ptr = (unsigned char *)addr;
|
||||
int print_bytes = 0;
|
||||
int print_bits = 7;
|
||||
|
||||
if(ptr == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if(ptr == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for(print_bytes = 0;
|
||||
print_bytes < size;
|
||||
print_bytes++, ptr++)
|
||||
{
|
||||
for(print_bits = 7;
|
||||
print_bits >= 0;
|
||||
print_bits--)
|
||||
{
|
||||
printk("%d", ((*ptr >> print_bits) & 1));
|
||||
}
|
||||
for(print_bytes = 0;
|
||||
print_bytes < size;
|
||||
print_bytes++, ptr++)
|
||||
{
|
||||
for(print_bits = 7;
|
||||
print_bits >= 0;
|
||||
print_bits--)
|
||||
{
|
||||
printk("%d", ((*ptr >> print_bits) & 1));
|
||||
}
|
||||
|
||||
}
|
||||
printk("\n");
|
||||
return print_bytes;
|
||||
}
|
||||
printk("\n");
|
||||
return print_bytes;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,87 +51,268 @@ int print_bit(char *addr, int size)
|
||||
*/
|
||||
static void print_module(void)
|
||||
{
|
||||
struct module *mod;
|
||||
struct module *mod;
|
||||
|
||||
printk(KERN_ALERT "this module: %p==%p\n", &__this_module, THIS_MODULE);
|
||||
printk(KERN_ALERT "module state: %d\n", THIS_MODULE->state);
|
||||
printk(KERN_ALERT "module name: %s\n", THIS_MODULE->name);
|
||||
printk(KERN_ALERT "this module: %p==%p\n", &__this_module, THIS_MODULE);
|
||||
printk(KERN_ALERT "module state: %d\n", THIS_MODULE->state);
|
||||
printk(KERN_ALERT "module name: %s\n", THIS_MODULE->name);
|
||||
|
||||
list_for_each_entry(mod, *(&THIS_MODULE->list.prev), list);
|
||||
printk(KERN_ALERT "module name: %s\n", mod->name);
|
||||
printk(KERN_ALERT "module state: %d\n", THIS_MODULE->state);
|
||||
list_for_each_entry(mod, *(&THIS_MODULE->list.prev), list);
|
||||
printk(KERN_ALERT "module name: %s\n", mod->name);
|
||||
printk(KERN_ALERT "module state: %d\n", THIS_MODULE->state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
static void print_virtual_kernel_memoty_layout(void)
|
||||
{
|
||||
printk(KERN_INFO "virtual kernel memory layout:\n"
|
||||
" fixmap : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
" cpu_entry : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
" pkmap : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
#endif
|
||||
" vmalloc : 0x%08lx - 0x%08lx (%4ld MB)\n"
|
||||
" lowmem : 0x%08lx - 0x%08lx (%4ld MB)\n",
|
||||
//" .init : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
//" .data : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
//" .text : 0x%08lx - 0x%08lx (%4ld kB)\n",
|
||||
FIXADDR_START, FIXADDR_TOP,
|
||||
(FIXADDR_TOP - FIXADDR_START) >> 10,
|
||||
|
||||
CPU_ENTRY_AREA_BASE,
|
||||
CPU_ENTRY_AREA_BASE + CPU_ENTRY_AREA_MAP_SIZE,
|
||||
CPU_ENTRY_AREA_MAP_SIZE >> 10,
|
||||
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
PKMAP_BASE, PKMAP_BASE+LAST_PKMAP*PAGE_SIZE,
|
||||
(LAST_PKMAP*PAGE_SIZE) >> 10,
|
||||
#endif
|
||||
|
||||
VMALLOC_START, VMALLOC_END,
|
||||
(VMALLOC_END - VMALLOC_START) >> 20,
|
||||
|
||||
(unsigned long)__va(0), (unsigned long)high_memory,
|
||||
((unsigned long)high_memory - (unsigned long)__va(0)) >> 20);
|
||||
|
||||
//(unsigned long)&__init_begin, (unsigned long)&__init_end,
|
||||
//((unsigned long)&__init_end -
|
||||
// (unsigned long)&__init_begin) >> 10,
|
||||
|
||||
//(unsigned long)&_etext, (unsigned long)&_edata,
|
||||
//((unsigned long)&_edata - (unsigned long)&_etext) >> 10,
|
||||
|
||||
//.autorelabel(unsigned long)&_text, (unsigned long)&_etext,
|
||||
//((unsigned long)&_etext - (unsigned long)&_text) >> 10);
|
||||
|
||||
/*
|
||||
* Check boundaries twice: Some fundamental inconsistencies can
|
||||
* be detected at build time already.
|
||||
*/
|
||||
#define __FIXADDR_TOP (-PAGE_SIZE)
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
BUILD_BUG_ON(PKMAP_BASE + LAST_PKMAP*PAGE_SIZE > FIXADDR_START);
|
||||
BUILD_BUG_ON(VMALLOC_END > PKMAP_BASE);
|
||||
#endif
|
||||
#define high_memory (-128UL << 20)
|
||||
BUILD_BUG_ON(VMALLOC_START >= VMALLOC_END);
|
||||
#undef high_memory
|
||||
#undef __FIXADDR_TOP
|
||||
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
BUG_ON(PKMAP_BASE + LAST_PKMAP*PAGE_SIZE > FIXADDR_START);
|
||||
BUG_ON(VMALLOC_END > PKMAP_BASE);
|
||||
#endif
|
||||
BUG_ON(VMALLOC_START >= VMALLOC_END);
|
||||
BUG_ON((unsigned long)high_memory > VMALLOC_START);
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_ARM)
|
||||
|
||||
static void print_virtual_kernel_memoty_layout(void)
|
||||
{
|
||||
#define MLK(b, t) b, t, ((t) - (b)) >> 10
|
||||
#define MLM(b, t) b, t, ((t) - (b)) >> 20
|
||||
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K)
|
||||
|
||||
printk("Virtual kernel memory layout:\n"
|
||||
" vector : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
#ifdef CONFIG_HAVE_TCM
|
||||
" DTCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
" ITCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
#endif
|
||||
" fixmap : 0x%08lx - 0x%08lx (%4ld kB)\n"
|
||||
" vmalloc : 0x%08lx - 0x%08lx (%4ld MB)\n"
|
||||
" lowmem : 0x%08lx - 0x%08lx (%4ld MB)\n"
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
" pkmap : 0x%08lx - 0x%08lx (%4ld MB)\n"
|
||||
#endif
|
||||
#ifdef CONFIG_MODULES
|
||||
" modules : 0x%08lx - 0x%08lx (%4ld MB)\n"
|
||||
#endif
|
||||
" .text : 0x%p" " - 0x%p" " (%4td kB)\n"
|
||||
" .init : 0x%p" " - 0x%p" " (%4td kB)\n"
|
||||
" .data : 0x%p" " - 0x%p" " (%4td kB)\n",
|
||||
//" .bss : 0x%p" " - 0x%p" " (%4td kB)\n",
|
||||
|
||||
MLK(VECTORS_BASE, VECTORS_BASE + PAGE_SIZE),
|
||||
#ifdef CONFIG_HAVE_TCM
|
||||
MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
|
||||
MLK(ITCM_OFFSET, (unsigned long) itcm_end),
|
||||
#endif
|
||||
MLK(FIXADDR_START, FIXADDR_END),
|
||||
MLM(VMALLOC_START, VMALLOC_END),
|
||||
MLM(PAGE_OFFSET, (unsigned long)high_memory),
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
|
||||
(PAGE_SIZE)),
|
||||
#endif
|
||||
#ifdef CONFIG_MODULES
|
||||
MLM(MODULES_VADDR, MODULES_END),
|
||||
#endif
|
||||
|
||||
MLK_ROUNDUP(_text, _etext),
|
||||
MLK_ROUNDUP(__init_begin, __init_end),
|
||||
MLK_ROUNDUP(_sdata, _edata));
|
||||
//MLK_ROUNDUP(__bss_start, __bss_stop));
|
||||
|
||||
#undef MLK
|
||||
#undef MLM
|
||||
#undef MLK_ROUNDUP
|
||||
|
||||
/*
|
||||
* Check boundaries twice: Some fundamental inconsistencies can
|
||||
* be detected at build time already.
|
||||
*/
|
||||
#ifdef CONFIG_MMU
|
||||
BUILD_BUG_ON(TASK_SIZE > MODULES_VADDR);
|
||||
BUG_ON(TASK_SIZE > MODULES_VADDR);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HIGHMEM
|
||||
BUILD_BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > PAGE_OFFSET);
|
||||
BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > PAGE_OFFSET);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static void test_virtual_kernel_memoty_layout(void)
|
||||
{
|
||||
#define TEST_KMALLOC_SIZE 10
|
||||
char *test_kmalloc = NULL;
|
||||
test_kmalloc = kmalloc(sizeof(char) * TEST_KMALLOC_SIZE, GFP_KERNEL);
|
||||
if (test_kmalloc)
|
||||
printk("[%s %d] test_kmalloc : addr = 0x%0lx, size = %d\n", __func__, __LINE__, test_kmalloc, TEST_KMALLOC_SIZE);
|
||||
kfree(test_kmalloc);
|
||||
test_kmalloc = NULL;
|
||||
|
||||
#define TEST_VMALLOC_SIZE (108 * 1024 * 1024)
|
||||
char *test_vmalloc = NULL;
|
||||
test_vmalloc = vmalloc(sizeof(char) * TEST_VMALLOC_SIZE);
|
||||
if (test_vmalloc)
|
||||
printk("[%s %d] test_vmalloc : addr = 0x%0lx, size = %d\n", __func__, __LINE__, test_vmalloc, TEST_VMALLOC_SIZE);
|
||||
vfree(test_vmalloc);
|
||||
test_vmalloc = NULL;
|
||||
}
|
||||
|
||||
|
||||
static void print_vmarea(void)
|
||||
{
|
||||
printk("---------------------\n");
|
||||
printk("TASK_SIZE = %p\n", TASK_SIZE);
|
||||
printk("---------------------\n");
|
||||
printk("STACK_TOP = %p\n", STACK_TOP);
|
||||
//printf("MMAP_BASE = %p\n", TASK_UNMAPPED_SIZE);
|
||||
#define HIGHMEM_END (unsigned long)(4 * 1024 * 1024 * 1024)
|
||||
#define HIGHMEM_START ((unsigned long)(-128UL << 20))
|
||||
#define high_memory HIGHMEM_START
|
||||
|
||||
printk("|---------------------| HIGHMEM_END = 0x1%0lx(4GB)\n", (unsigned long)4 << 30);
|
||||
printk("| | [%ldK]\n", (HIGHMEM_END - FIXADDR_TOP));
|
||||
printk("|---------------------| FIXADDR_TOP = 0x%0lx\n", FIXADDR_TOP);
|
||||
printk("| | Fix-mappinged Linear Address [%ldK]\n", (FIXADDR_TOP - FIXADDR_START) >> 10);
|
||||
printk("|---------------------| FIXADDR_START = 0x%0lx\n", FIXADDR_START);
|
||||
printk("| | Persistent Kernel Mapping [%ldM]\n", (FIXADDR_START - PKMAP_BASE) >> 20);
|
||||
printk("|---------------------| PKMAP_BASE = 0x%0lx\n", PKMAP_BASE);
|
||||
printk("| | [%ldK]\n", (PKMAP_BASE - VMALLOC_END) >> 10);
|
||||
printk("|---------------------| VMALLOC_END = 0x%0lx\n", VMALLOC_END);
|
||||
printk("| | Vmalloc Area [%ldM]\n", (VMALLOC_END - VMALLOC_START) >> 20);
|
||||
printk("|---------------------| VMALLOC_START = 0x%0lx\n", VMALLOC_START);
|
||||
printk("| | VMALLOC_OFFSET = [%luM/%luM]\n", (unsigned long)VMALLOC_OFFSET >> 20, (unsigned long)(VMALLOC_START - HIGHMEM_START) >> 20);
|
||||
printk("|---------------------| HIGHMEM_START = 0x%0lx\n", HIGHMEM_START);
|
||||
printk("| | Physical Memory Mapping[%ldM]\n", (HIGHMEM_START - PAGE_OFFSET) >> 20);
|
||||
printk("|---------------------| PAGE_OFFSET = 0x%0lx\n", PAGE_OFFSET);
|
||||
printk("TASK_SIZE = 0x%0lx(%ldG)\n", TASK_SIZE, TASK_SIZE >> 30);
|
||||
printk("---------------------\n");
|
||||
printk("STACK_TOP = 0x%0lx(%ldG)\n", STACK_TOP, STACK_TOP >> 30);
|
||||
|
||||
#undef high_memory
|
||||
|
||||
print_virtual_kernel_memoty_layout();
|
||||
test_virtual_kernel_memoty_layout();
|
||||
}
|
||||
|
||||
// http://lxr.free-electrons.com/source/arch/x86/include/asm/segment.h#L123
|
||||
#if CONFIG_X86
|
||||
static void print_segment(void)
|
||||
{
|
||||
long data = 0;
|
||||
long data = 0;
|
||||
|
||||
/*
|
||||
/*
|
||||
|
||||
---------------------------------------------------------------------------------------------
|
||||
| | INDEX | TI| RPL |
|
||||
---------------------------------------------------------------------------------------------
|
||||
__KERNEL_CS = 0x0010 = B 0 0 0 0 0 0 0 0 0 0 0 1 0 | 0 | 0 0 | index = 2, TI = 0, RPL = 0
|
||||
---------------------------------------------------------------------------------------------
|
||||
__KERNEL_DS = 0x0018 = B 0 0 0 0 0 0 0 0 0 0 0 1 1 | 0 | 0 0 | index = 3, TI = 0, RPL = 0
|
||||
---------------------------------------------------------------------------------------------
|
||||
__USER_DS = 0x0033 = B 0 0 0 0 0 0 0 0 0 0 1 1 0 | 0 | 1 1 | index = 6, TI = 0, RPL = 3
|
||||
---------------------------------------------------------------------------------------------
|
||||
__USER_DS = 0x002B = B 0 0 0 0 0 0 0 0 0 0 1 0 1 | 0 | 1 1 | index = 5, TI = 0, RPL = 3
|
||||
---------------------------------------------------------------------------------------------
|
||||
---------------------------------------------------------------------------------------------
|
||||
| | INDEX | TI| RPL |
|
||||
---------------------------------------------------------------------------------------------
|
||||
__KERNEL_CS = 0x0010 = B 0 0 0 0 0 0 0 0 0 0 0 1 0 | 0 | 0 0 | index = 2, TI = 0, RPL = 0
|
||||
---------------------------------------------------------------------------------------------
|
||||
__KERNEL_DS = 0x0018 = B 0 0 0 0 0 0 0 0 0 0 0 1 1 | 0 | 0 0 | index = 3, TI = 0, RPL = 0
|
||||
---------------------------------------------------------------------------------------------
|
||||
__USER_DS = 0x0033 = B 0 0 0 0 0 0 0 0 0 0 1 1 0 | 0 | 1 1 | index = 6, TI = 0, RPL = 3
|
||||
---------------------------------------------------------------------------------------------
|
||||
__USER_DS = 0x002B = B 0 0 0 0 0 0 0 0 0 0 1 0 1 | 0 | 1 1 | index = 5, TI = 0, RPL = 3
|
||||
---------------------------------------------------------------------------------------------
|
||||
|
||||
| LE little-endian | 低字节 -=> 高字节 |
|
||||
---------------------------------------------------------------------------------------------
|
||||
__KERNEL_CS = 0x0010 = | 00010000 00000000 |
|
||||
__KERNEL_DS = 0x0018 = | 00011000 00000000 |
|
||||
__USER_DS = 0x0033 = | 00110011 00000000 |
|
||||
__USER_DS = 0x002B = | 00101011 00000000 |
|
||||
*/
|
||||
data = __KERNEL_CS;
|
||||
printk("__KERNEL_CS = %0x\n", data);
|
||||
print_bit(&data, 2);
|
||||
| LE little-endian | 低字节 -=> 高字节 |
|
||||
---------------------------------------------------------------------------------------------
|
||||
__KERNEL_CS = 0x0010 = | 00010000 00000000 |
|
||||
__KERNEL_DS = 0x0018 = | 00011000 00000000 |
|
||||
__USER_DS = 0x0033 = | 00110011 00000000 |
|
||||
__USER_DS = 0x002B = | 00101011 00000000 |
|
||||
*/
|
||||
data = __KERNEL_CS;
|
||||
printk("__KERNEL_CS = 0x%0lx\n", data);
|
||||
//print_bit(&data, 2);
|
||||
|
||||
data = __KERNEL_DS;
|
||||
printk("__KERNEL_DS = %0x\n", data);
|
||||
print_bit(&data, 2);
|
||||
data = __KERNEL_DS;
|
||||
printk("__KERNEL_DS = 0x%0lx\n", data);
|
||||
//print_bit(&data, 2);
|
||||
|
||||
data = __USER_CS;
|
||||
printk("__USER_CS = %0x\n", data);
|
||||
print_bit(&data, 2);
|
||||
|
||||
data = __USER_DS;
|
||||
printk("__USER_DS = %0x\n", data);
|
||||
print_bit(&data, 2);
|
||||
//printk("__ESPFIX_SS = %0x\n", __ESPFIX_SS);
|
||||
data = __USER_CS;
|
||||
printk("__USER_CS = 0x%0lx\n", data);
|
||||
//print_bit(&data, 2);
|
||||
|
||||
data = __USER_DS;
|
||||
printk("__USER_DS = 0x%0lx\n", data);
|
||||
//print_bit(&data, 2);
|
||||
//printk("__ESPFIX_SS = %0x\n", __ESPFIX_SS);
|
||||
}
|
||||
|
||||
#else
|
||||
static inline void print_segment(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int hello_init(void)
|
||||
{
|
||||
print_module( );
|
||||
print_module( );
|
||||
|
||||
printk(KERN_ALERT "run in cpu %d\n", get_cpu());
|
||||
printk(KERN_ALERT "run in cpu %d\n", get_cpu());
|
||||
|
||||
//printk(KERN_ALERT "PAGE_OFFSET : 0x%lx, TASK_SIZE : 0x%lx", PAGE_OFFSET, TASK_SIZE);
|
||||
printk(KERN_ALERT "PAGE_OFFSET : 0x%lx\n", PAGE_OFFSET);
|
||||
//printk(KERN_ALERT "PAGE_OFFSET : 0x%lx, TASK_SIZE : 0x%lx", PAGE_OFFSET, TASK_SIZE);
|
||||
printk(KERN_ALERT "PAGE_OFFSET : 0x%lx\n", PAGE_OFFSET);
|
||||
|
||||
print_vmarea( );
|
||||
print_vmarea( );
|
||||
|
||||
print_segment( );
|
||||
print_segment( );
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,290 +1,25 @@
|
||||
进程虚拟地址空间
|
||||
=======
|
||||
http://blog.chinaunix.net/uid-14528823-id-4394419.html
|
||||
|
||||
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
|
||||
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
| 2016-06-14 | [Linux-4.7](http://lxr.free-electrons.com/source/?v=4.7) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux内存管理](http://blog.csdn.net/gatieme/article/category/6225543) |
|
||||
https://blog.csdn.net/hongzg1982/article/details/54880674
|
||||
|
||||
https://blog.csdn.net/u010278923/article/details/79893477
|
||||
|
||||
https://blog.csdn.net/moe26/article/details/10326017
|
||||
|
||||
#1 虚拟地址空间概述
|
||||
-------
|
||||
https://www.cnblogs.com/yfz0/p/5829443.html
|
||||
|
||||
用户层进程的虚拟地址空间是Linux的一个重要抽象 : 它向每个运行进程提供了同样的系统视图, 这使得多个进程可以同时运行, 而不会干扰到其他进程内存中的内容. 此外, 它容许使用各种高级的程序设计技术,如内存映射
|
||||
https://www.cnblogs.com/arnoldlu/p/8251333.html
|
||||
|
||||
从今天开始, 我将讨论内核是如何实现这些概念的. 这同样需要考察可用物理内存中的页帧与所有的进程虚拟地址空间中的页之间的关联 : **逆向映射(reverse
|
||||
mapping)* ***技术有助于从虚拟内存页跟踪到对应的物理内存页, 而**缺页处理(page fault handling)**则允许从块设备按需读取数据填充虚拟地址空间
|
||||
https://blog.csdn.net/qianlong4526888/article/details/8840128
|
||||
|
||||
https://www.cnblogs.com/wzw200/p/3741340.html
|
||||
|
||||
* 每个应用程序都有自身的地址空间,与所有其他应用程序分隔开
|
||||
|
||||
* 通常在巨大的线性地址空间中,只有很少的段可用于各个用户空间进程,这些段彼此有一定的距离。内核需要一些数据结构,来有效地管理这些(随机)分布的段。
|
||||
|
||||
* 地址空间只有极小的一部分与物理内存页直接关联。不经常使用的部分,则仅当必要时与页帧关联.
|
||||
|
||||
* 内核信任自身,但无法信任用户进程。因此,各个操作用户地址空间的操作都伴随有各种检查,以确保程序的权限不会超出应有的限制,进而危及系统的稳定性和安全性.
|
||||
|
||||
* fork-exec模型在UNIX操作系统下用于产生新进程. 如果实现得较为粗劣, 该模型的功能并不强大。因此内核必须借助于一些技巧,来尽可能高效地管理用户地址空间
|
||||
|
||||
#2 进程虚拟地址空间
|
||||
-------
|
||||
|
||||
##2.1 进程虚拟地址空间
|
||||
-------
|
||||
|
||||
各个进程的虚拟地址空间起始于地址0, 延伸到TASK_SIZE - 1, 其上是内核地址空间。 在IA-32系统上地址空间的范围可达$2^{32} = 4GB$, 总的地址空间通常按3:1比例划分,我们在下文中将关注该划分. 内核分配了1GB, 而各个用户空间进程可用的部分为3GB. 其他的划分比例也是可能的, 但正
|
||||
如前文的讨论, 只能在非常特定的配置和某些工作负荷下才有用.
|
||||
|
||||
与系统完整性相关的非常重要的一方面是, 用户程序只能访问整个地址空间的下半部分,不能访问内核部分. 如果没有预先达成"协议", 用户进程也不可能操作另一个进程的地址空间,因为后者的地址空间对前者不可见.
|
||||
|
||||
无论当前哪个用户进程处于活动状态, 虚拟地址空间内核部分的内容总是同样的. 取决于具体的硬件, 这可能是通过操作各用户进程的页表, 使得虚拟地址空间的上半部看上去总是相同的. 也可能是指示处理器为内核提供一个独立的地址空间, 映射在各个用户地址空间之上. 读者可以回想一下图1-3, 其中给出了相关的图示.
|
||||
|
||||
虚拟地址空间由许多不同长度的段组成, 用于不同的目的, 必须分别处理.
|
||||
|
||||
例如在大多数情况下, 不允许修改text段, 但必须可以执行其内容. 另一方面,必须可以修改映射到地址空间中的文本文件
|
||||
内容,而不能允许执行其内容. 因为这没有意义,文件的内容只是数据,并非机器代码.
|
||||
|
||||
##2.2 进程地址空间的布局
|
||||
-------
|
||||
|
||||
虚拟地址空间中包含了若干区域. 其分布方式是特定于体系结构的,但所有方法都有下列共同成分.
|
||||
|
||||
* 当前运行代码的二进制代码. 该代码通常称之为text,所处的虚拟内存区域称之为代码段(text section).
|
||||
|
||||
* 可执行文件的已初始化全局变量的内存映射, 称为数据段(data section).
|
||||
|
||||
* 包括未初始化全局变量(也就是bss段的零页)的内存映射, 页面中的信息全部为0值, 所以可用于映射bss段等目的.
|
||||
|
||||
* 用于保存局部变量和实现函数/过程调用栈(不要和进程内核栈混淆, 进程的内核栈独立存在并由内核维护)的零页内存映射
|
||||
|
||||
|
||||
* 程序使用的动态库的代码, 诸如C库或动态连接程序等共享库的代码段, 数据段和bss段.
|
||||
|
||||
* 存储动态产生的数据的堆
|
||||
|
||||
* 环境变量和命令行参数的段.
|
||||
|
||||
* 将文件内容映射到虚拟地址空间中的内存映射
|
||||
|
||||
|
||||
进程的虚拟地址空间中的任何有效地址都只能位于唯一的区域, 这些内存区域不能相互覆盖. 可以看到, 在执行的进程中, 每个不同的内存片段都对应一个独立的内存区域 : 栈, 对象代码, 全局变量, 被映射的文件等.
|
||||
|
||||
##2.3 内存描述符`mm_struct`
|
||||
-------
|
||||
|
||||
内核使用内存描述符结构体`struct mm_struct`表示进程的地址空间, 该结构体包含了和进程地址空间相关的全部信息
|
||||
|
||||
##2.3.1 内存描述符`mm_struct`
|
||||
-------
|
||||
|
||||
系统中的各个进程都具有一个`struct mm_struct`的实例,可以通过`task_struct`访问. 这个实例保存了进程的内存管理信息, 定义在[`include/linux/mm_types.h?v=4.7, line 395`](http://lxr.free-electrons.com/source/include/linux/mm_types.h?v=4.7#L395)
|
||||
|
||||
|
||||
|
||||
**可执行代码**占用的虚拟地址空间区域, 其开始和结束分别通过 `start_code`和`end_code`标记.
|
||||
|
||||
|
||||
类似地, `start_data`和`end_data`标记了包含**已初始化数据的区域**. 请注意, 在`ELF`二进制文件映射到地址空间中之后,这些区域的长度不再改变.
|
||||
|
||||
**堆**的起始地址保存在`start_brk`, `brk`表示堆区域当前的结束地址. 尽管堆的起始地址在进程生命周期中是不变的, 但堆的长度会发生变化,因而`brk`的值也会变.
|
||||
|
||||
**参数列表**和**环境变量**的位置分别由`arg_start`和 `arg_end`、`env_start`和`env_end`描述. 两个区域
|
||||
都位于栈中最高的区域.
|
||||
|
||||
[`mmap_base`](http://lxr.free-electrons.com/source/include/linux/mm_types.h?v=4.7#L404)表示虚拟地址空间中用于内存映射的起始地址, 可调用`get_unmapped_area`在`mmap`
|
||||
区域中为新映射找到适当的位置.
|
||||
|
||||
`task_size`, 顾名思义, 存储了对应进程的地址空间长度. 对本机应用程序来说, 该值通常是`TASK_SIZE`. 但64位体系结构与前辈处理器通常是二进制兼容的. 如果在64位计算机上执行32位二进制代码, 则`task_size`描述了该二进制代码实际可见的地址空间长度.
|
||||
|
||||
各个体系结构可以通过几个配置选项影响虚拟地址空间的布局。
|
||||
|
||||
* 如果体系结构想要在不同`mmap`区域布局之间作出选择, 则需要设置`HAVE_ARCH_PICK_MMAP_LAYOUT`, 并提供`arch_pick_mmap_layout`函数.
|
||||
|
||||
* 在创建新的内存映射时, 除非用户指定了具体的地址, 否则内核需要找到一个适当的位置. 如果体系结构自身想要选择合适的位置,则必须设置预处理器符号`HAVE_ARCH_UNMAPPED_AREA`, 并相应地定义`arch_get_unmapped_area`函数。
|
||||
|
||||
* 在寻找新的内存映射低端内存位置时, 通常从较低的内存位置开始, 逐渐向较高的内存地址搜索. 内核提供了默认的函数`arch_get_unmapped_area_topdown`用于搜索, 但如果某个体系结构想要提供专门的实现, 则需要设置预处理器符号`HAVE_ARCH_GET_UNMAPPED_AREA`.
|
||||
|
||||
* 通常, 栈自顶向下增长. 具有不同处理方式的体系结构需要设置配置选项`CONFIG_STACK_GROWSUP`
|
||||
|
||||
最后, 我们需要考虑进程标志`PF_RANDOMIZE`. 如果设置了该标志, 则内核不会为栈和内存映射的起点选择固定位置,而是在每次新进程启动时随机改变这些值的设置. 这引入了一些复杂性, 例如, 使得攻击因缓冲区溢出导致的安全漏洞更加困难. 如果攻击者无法依靠固定地址找到栈,那么想要构
|
||||
建恶意代码, 通过缓冲器溢出获得栈内存区域的访问权, 而后恶意操纵栈的内容,将会困难得多.
|
||||
|
||||
|
||||
下图说明了前述的各个部分在大多数体系结构的虚拟地址空间中的分布情况.
|
||||
|
||||
`text`段如何映射到虚拟地址空间中由ELF标准确定(有关该二进制格式的更多信息,请参见), 每个体系结构都指定了一个特定的起始地址 : IA-32系统起始于0x08048000, 在text段的起始地址与最低的可用地址之间有大约128 MiB的间距,用于捕获NULL指针. 其他体系结构也有类似的缺口 : `UltraSparc`计算机使用0x100000000作为text段的起始点, 而AMD64使用0x0000000000400000. 堆紧接着text段开始, 向上增长. 栈起始于STACK_TOP, 如果设置了 `PF_RANDOMIZE`, 则起始点会减少一个小的随机量. 每个体系结构都必须定义`STACK_TOP`, 大多数都设置为 `TASK_SIZE`, 即用户地址空间中最高的可用地址. 进程
|
||||
的参数列表和环境变量都是栈的初始数据.
|
||||
|
||||
用于内存映射的区域起始于`mm_struct->mmap_base`, 通常设置为`TASK_UNMAPPED_BASE`, 每个体系结构都需要定义. 几乎所有的情况下, 其值都是`TASK_SIZE/3`. 要注意,如果使用内核的默认配置, 则`mmap`区域的起始点不是随机的.
|
||||
|
||||
|
||||
![进程的线性地址空间的组成]()
|
||||
|
||||
如果计算机提供了巨大的虚拟地址空间, 那么使用上述的地址空间布局会工作得非常好. 但在32位计算机上可能会出现问题. 考虑IA-32的情况 : 虚拟地址空间从0到0xC0000000 , 每个用户进程有3GB可用. `TASK_UNMAPPED_BASE`起始于0x4000000, 即1GB处. 糟糕的是, 这意味着堆只有1GB
|
||||
空间可供使用, 继续增长则会进入到`mmap`区域, 这显然不是我们想要的.
|
||||
|
||||
问题在于, 内存映射区域位于虚拟地址空间的中间. 这也是在内核版本2.6.7开发期间为IA-32计算机引入一个新的虚拟地址空间布局的原因(经典布局仍然可以使用).
|
||||
|
||||
![mmap区域自顶向下扩展时,IA-32计算机上虚拟地址空间的布局]()
|
||||
|
||||
|
||||
其想法在于使用固定值限制栈的最大长度. 由于栈是有界的, 因此安置内存映射的区域可以在栈末端的下方立即开始. 与经典方法相反, 该区域现在是自顶向下扩展. 由于堆仍然位于虚拟地址空间中较低的区域并向上增长, 因此`mmap`区域和堆可以相对扩展, 直至耗尽虚拟地址空间中剩余的区域.
|
||||
|
||||
为确保栈与`mmap`区域不发生冲突,两者之间设置了一个安全隙.
|
||||
|
||||
|
||||
##2.3.2 分配内存描述符
|
||||
-------
|
||||
|
||||
|
||||
##2.3.3 撤销内存描述符
|
||||
-------
|
||||
|
||||
|
||||
##2.4 虚拟内存区域`vm_area_struct`
|
||||
-------
|
||||
|
||||
|
||||
|
||||
###2.4.1 虚拟内存区域`vm_area_struct`
|
||||
-------
|
||||
|
||||
|
||||
###2.4.2 VMA标志
|
||||
-------
|
||||
|
||||
|
||||
###2.4.3 VMA操作
|
||||
-------
|
||||
|
||||
|
||||
##2.3 建立布局
|
||||
-------
|
||||
|
||||
在使用`load_elf_binary`装载一个ELF二进制文件时,将创建进程的地址空间, 该函数定义在[fs/binfmt_elf.c?v=4.7, line 666](http://lxr.free-electrons.com/source/fs/binfmt_elf.c?v=4.7#L666)
|
||||
|
||||
|
||||
而`exec`系统调用刚好使用了该函数. 加载`ELF`文件涉及大量纷繁复杂的技术细节, 之前我们讲解进程调度的时候曾经专门讲解过这个函数, 因此我们现在只给出的代码流程图来主要关注建立虚拟内存区域所需的各个步骤.
|
||||
|
||||
|
||||
|
||||
![`load_elf_binary`的代码流程图]()
|
||||
|
||||
|
||||
如果全局变量`randomize_va_space`设置为1, 则启用地址空间随机化机制. 通常情况下都是启用的, 但在`Transmeta CPU`上会停用,因为该设置会降低此类计算机的速度. 此外,用户可以通过`/proc/sys/kernel/randomize_va_space`停用该特性
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
选择布局的工作由`arch_pick_mmap_layout`完成. 如果对应的体系结构没有提供一个具体的函数, 则使用内核的默认例程, 按如图4-1所示建立地址空间. 但我们更感兴趣的是, IA-32如何在经典布局和新的布局之间选择. 该函数定义在[`/arch/对应架构/mm/mmap.c`](http://lxr.free-electrons.com/ident?v=4.7;i=arch_pick_mmap_layout)
|
||||
|
||||
|
||||
| 设置进程的虚拟内存布局 | x86 | arm | arm64 |
|
||||
|:---------------------:|:---:|:---:|:-----:|
|
||||
| arch_pick_mmap_layout | [arch/x86/mm/mmap.c?v=4.7, line 100](http://lxr.free-electrons.com/source/arch/x86/mm/mmap.c?v=4.7#L100) | [arch/arm/mm/mmap.c?v=4.7, line 181](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L181) | [arch/arm64/mm/mmap.c?v=4.7, line 79](http://lxr.free-electrons.com/source/arch/arm64/mm/mmap.c?v=4.7#L79) |
|
||||
|
||||
|
||||
参见arm下arch_pick_mmap_layout函数的实现, 定义在[arch/arm/mm/mmap.c?v=4.7, line 181](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L181)
|
||||
|
||||
```cpp
|
||||
void arch_pick_mmap_layout(struct mm_struct *mm)
|
||||
{
|
||||
unsigned long random_factor = 0UL;
|
||||
|
||||
if (current->flags & PF_RANDOMIZE)
|
||||
random_factor = arch_mmap_rnd();
|
||||
|
||||
if (mmap_is_legacy()) {
|
||||
mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
|
||||
mm->get_unmapped_area = arch_get_unmapped_area;
|
||||
} else {
|
||||
mm->mmap_base = mmap_base(random_factor);
|
||||
mm->get_unmapped_area = arch_get_unmapped_area_topdown;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
如果用户通过`/proc/sys/vm/legacy_va_layout`给出明确的指示, 或者要执行为不同的UNIX变体编译、需要旧的布局的二进制文件, 或者栈可以无限增长(最重要的一点),则系统会选择旧的布局. 这使得很难确定栈的下界, 亦即`mmap`区域的上界.
|
||||
|
||||
在经典的配置下, `mmap`区域的起始点是`TASK_UNMAPPED_BASE`, 其值为0x4000000, 而标准函数`arch_get_unmapped_area`(其名称虽然带有`arch` , 但该函数不一定是特定于体系结构的, 内核也提供了一个标准实现)用于自下而上地创建新的映射.
|
||||
|
||||
在使用新布局时, 内存映射自顶向下增长.
|
||||
|
||||
标准函数`arch_get_unmapped_area_topdown`(我不会详细描述)负责该工作.
|
||||
|
||||
| 函数or变量 | x86 | arm | arm64 |
|
||||
|:---------------:|:---:|:---:|:-----:|
|
||||
| MIN_GAP/MAX_GAP | [arch/x86/mm/mmap.c?v=4.7, line 54](http://lxr.free-electrons.com/source/arch/x86/mm/mmap.c?v=4.7#L54) | [arch/arm/mm/mmap.c?v=4.7, line 19](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L19) | [arch/arm64/mm/mmap.c?v=4.7, line 36](http://lxr.free-electrons.com/source/arch/arm64/mm/mmap.c?v=4.7#L36) |
|
||||
| mmap_is_legacy | [arch/x86/mm/mmap.c?v=4.7, line 57](http://lxr.free-electrons.com/source/arch/x86/mm/mmap.c?v=4.7#L57) | [arch/arm/mm/mmap.c?v=4.7, line 22](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L22) | [arch/arm64/mm/mmap.c?v=4.7, line 39](http://lxr.free-electrons.com/source/arch/arm64/mm/mmap.c?v=4.7#L39) |
|
||||
| arch_mmap_rnd | [arch/x86/mm/mmap.c?v=4.7, line 68](http://lxr.free-electrons.com/source/arch/x86/mm/mmap.c?v=4.7#L68) | [arch/arm/mm/mmap.c?v=4.7, line 172](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L172) | [arch/arm64/mm/mmap.c?v=4.7, line 50](http://lxr.free-electrons.com/source/arch/arm64/mm/mmap.c?v=4.7#L50) |
|
||||
| mmap_base | [arch/x86/mm/mmap.c?v=4.7, line 84](http://lxr.free-electrons.com/source/arch/x86/mm/mmap.c?v=4.7#L84) | [arch/arm/mm/mmap.c?v=4.7, line 33](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L33) | [arch/arm64/mm/mmap.c?v=4.7, line 63](http://lxr.free-electrons.com/source/arch/arm64/mm/mmap.c?v=4.7#L63) |
|
||||
|
||||
|
||||
```cpp
|
||||
// http://lxr.free-electrons.com/source/arch/x86/mm/mmap.c?v=4.7#L54
|
||||
#define MIN_GAP (128*1024*1024)
|
||||
#define MAX_GAP (TASK_SIZE/6*5)
|
||||
|
||||
// http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L19
|
||||
/* gap between mmap and stack */
|
||||
#define MIN_GAP (128*1024*1024UL)
|
||||
#define MAX_GAP ((TASK_SIZE)/6*5)
|
||||
|
||||
// http://lxr.free-electrons.com/source/arch/arm64/mm/mmap.c?v=4.7#L36
|
||||
/*
|
||||
* Leave enough space between the mmap area and the stack to honour ulimit in
|
||||
* the face of randomisation.
|
||||
*/
|
||||
#define MIN_GAP (SZ_128M + ((STACK_RND_MASK << PAGE_SHIFT) + 1))
|
||||
#define MAX_GAP (STACK_TOP/6*5)
|
||||
```
|
||||
|
||||
|
||||
更有趣的问题是如何选择内存映射的基地址, 该工作由`mmap_base`来完成, arm架构下该函数定义在[`arch/arm/mm/mmap.c?v=4.7, line 19`](http://lxr.free-electrons.com/source/arch/arm/mm/mmap.c?v=4.7#L19)
|
||||
|
||||
```cpp
|
||||
static unsigned long mmap_base(unsigned long rnd)
|
||||
{
|
||||
unsigned long gap = rlimit(RLIMIT_STACK);
|
||||
|
||||
if (gap < MIN_GAP)
|
||||
gap = MIN_GAP;
|
||||
else if (gap > MAX_GAP)
|
||||
gap = MAX_GAP;
|
||||
|
||||
return PAGE_ALIGN(TASK_SIZE - gap - rnd);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
可以根据栈的最大长度, 来计算栈最低的可能位置, 用作`mmap`区域的起始点. 但内核会确保栈至少跨越128MB的空间. 另外, 如果指定的栈界限非常巨大, 那么内核会保证至少有一小部分地址空间不被栈占据.
|
||||
|
||||
如果要求使用地址空间随机化机制, 上述位置会减去一个随机的偏移量,最大为1MB.
|
||||
|
||||
另外, 内核会确保该区域对齐到页帧, 这是体系结构的要求.
|
||||
|
||||
初看起来, 读者可能认为64位体系结构的情况会好一点, 因为不需要在不同的地址空间布局中进行选择. 虚拟地址空间是如此巨大, 以至于堆和`mmap`区域的碰撞几乎不可能.
|
||||
|
||||
|
||||
|
||||
但从AMD64体系结构的`arch_pick_mmap_layout`定义来看,此中会出现另一个复杂情况:
|
||||
|
||||
```cpp
|
||||
arch_pick_mmap_layout
|
||||
```
|
||||
|
||||
|
||||
如果启用对32位应用程序的二进制仿真,任何以兼容模式运行的进程都应该看到与原始计算机上相同的地址空间。因此, `ia32_pick_mmap_layout`用于为32位应用程序布置地址空间。该函数实际上是IA-32系统上`arch_pick_mmap_layout`的一个相同副本,前文已经讨论过.
|
||||
|
||||
|
||||
AMD64系统上对虚拟地址空间总是使用经典布局,因此无需区分各种选项。如果设置了`PF_RANDOMIZE`标志,则进行地址空间随机化,变动原本固定的`mmap_base`.
|
||||
|
||||
我们回到`load_elf_binary`. 该函数最后需要在适当的位置创建栈:
|
||||
|
||||
```cpp
|
||||
load_elf_binary
|
||||
```
|
||||
|
||||
标准函数setup_arg_pages即用于该目的. 因为该函数只是技术性的,我不会详细讨论. 该函数需要栈顶的位置作为参数. 栈顶由特定于体系结构的常数STACK_TOP给出, 而后调用randomize_stack_top, 确保在启用地址空间随机化的情况下,对该地址进行随机偏移.
|
||||
一个节点一个bootmem_data结构,初始化的参数主要有:
|
||||
min_low_pfn 系统中可用的最小PFN
|
||||
max_low_pfn以低端内存域表示的最大PFN
|
||||
highstart_pfn 高端内存区域的起始PFN
|
||||
highend_pfn 高端内存区域的最后一个PFN
|
||||
max_pfn 表示系统中可用的最大PFN
|
||||
引导内存分配器在系统初始化start_kernel的时候被释放掉。接下来由伙伴分配器接管。
|
||||
PFN全称应该是Page Frame Number
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
|
||||
title: 使用 Hexo 搭建 GitHub Page 博客(一)
|
||||
date: 2018-09-02 18:40
|
||||
author: gatieme
|
||||
tags: hexo
|
||||
categories:
|
||||
- hexo
|
||||
thumbnail:
|
||||
blogexcerpt: 博文摘要
|
||||
|
||||
---
|
||||
|
||||
| CSDN | GitHub | Hexo |
|
||||
|:----:|:------:|:----:|
|
||||
| [Aderstep--紫夜阑珊-青伶巷草](http://blog.csdn.net/gatieme) | [`AderXCoding/system/tools`](https://github.com/gatieme/AderXCoding/tree/master/system/tools) | [gatieme.github.io](https://gatieme.github.io) |
|
||||
|
||||
<br>
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a>
|
||||
|
||||
本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可, 转载请注明出处, 谢谢合作
|
||||
|
||||
因本人技术水平和知识面有限, 内容如有纰漏或者需要修正的地方, 欢迎大家指正, 也欢迎大家提供一些其他好的调试工具以供收录, 鄙人在此谢谢啦
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
#1 疑问
|
||||
-------
|
||||
|
||||
|
||||
这个问题实际上是一个老生常谈的问题.
|
||||
|
||||
其中一个比较流行的答案就是 :
|
||||
|
||||
>中断没有进程上下文, 而所有的进程调度都是以进程为基础的. 如果睡眠之后, 进程调度器没法来唤醒它.
|
||||
>
|
||||
>仔细想想,这个答案其实不然.
|
||||
>
|
||||
>在没有实现或配置内核栈中断栈分离的时候, 中断发生时会借用当前被中断进程的 Kernel stack, 所以实际上中断是借宿在这个进程上, 这个时候中断睡眠是完全可以的或者说是可以实现的.(此处我们排除这个需求的不合理性), 中断上下文会保存在这个进程的 stack 上, 等到这个进程被唤醒时, 会从中断ISR中继续执行.
|
||||
>
|
||||
>如果内核栈和中断栈分离, 那么两者是无关的. 中断中没有进程上下文, 那么如果进行睡眠, 内核也并不是无法继续处理, 依旧可以选择一个合适的进程(比如被中断的进程)过来, 然后等中断睡眠醒来, 再继续处理中断. 理论上也可以实现.
|
||||
>
|
||||
>所以, 中断没有进程上下文 不是此问题的根本原因.
|
||||
|
||||
|
||||
|
||||
#2 分析
|
||||
-------
|
||||
|
||||
|
||||
##2.1 中断中睡眠有意义么(合理性)
|
||||
-------
|
||||
|
||||
首先中断是什么?
|
||||
|
||||
> 中断是指计算机运行过程中, 出现某些意外情况需主机干预时, 机器能自动停止正在运行的程序并转入处理新情况的程序, 处理完毕后又返回原被暂停的程序继续运行.
|
||||
>
|
||||
>指处理机处理程序运行中出现的紧急事件的整个过程. 程序运行过程中, 系统外部、系统内部或者现行程序本身若出现紧急事件, 处理机立即中止现行程序的运行. 自动转入相应的处理程序(中断服务程序), 待处理完后, 再返回原来的程序运行.
|
||||
|
||||
|
||||
这里传递了几个意思:
|
||||
|
||||
1. 中断的优先级很高, 必须要立即处理, 比任何进程的优先级都要高, 需要暂停程序的运行而去立马处理.
|
||||
|
||||
2. 中断不应该依赖于任何进程.
|
||||
|
||||
既然是这么重要的事情, 那么所以你为什么要问 "Linux 为什么中断不允许睡眠" 这样的问题.
|
||||
|
||||
很明显
|
||||
|
||||
从来没有人想过, 要在中断中支持睡眠这样的操作, 因为 "它" 完全不合理.
|
||||
|
||||
既然不合理, 那么 Linux 必然也是按照 "不允许睡眠" 这么实现的.
|
||||
|
||||
|
||||
##2.2 Linux 中断就是这么设计的(实现)
|
||||
-------
|
||||
|
||||
|
||||
先把中断处理流程给出来
|
||||
|
||||
|
||||
|
||||
|
||||
```cpp
|
||||
1. 进入中断处理程序
|
||||
2. 保存关键上下文
|
||||
3. 开中断(sti指令)
|
||||
4. 进入中断处理程序的 handler
|
||||
5. 关中断(cli指令)
|
||||
6. 写EOI寄存器(表示中断处理完成)
|
||||
7. 开中断
|
||||
```
|
||||
|
||||
硬中断, 对应于上图的1、2、3步骤, 在这几个步骤中, 所有中断是被屏蔽的, 如果在这个时候睡眠了, 操作系统不会收到任何中断(包括时钟中断), 系统就基本处于瘫痪状态(例如调度器依赖的时钟节拍没有等等…)
|
||||
|
||||
软中断, 对应上图的4(当然,准确的说应该是4步骤的后面一点,先把话说保险点,免得思一克又开始较真 )。这个时候不能睡眠的关键是因为上下文。
|
||||
大家知道操作系统以进程调度为单位,进程的运行在进程的上下文中,以进程描述符作为管理的数据结构。进程可以睡眠的原因是操作系统可以切换不同进程的上下文,进行调度操作,这些操作都以进程描述符为支持。
|
||||
中断运行在中断上下文,没有一个所谓的中断描述符来描述它,它不是操作系统调度的单位。一旦在中断上下文中睡眠,首先无法切换上下文(因为没有中断描述符,当前上下文的状态得不到保存),其次,没有人来唤醒它,因为它不是操作系统的调度单位.
|
||||
|
||||
此外,中断的发生是非常非常频繁的,在一个中断睡眠期间,其它中断发生并睡眠了,那很容易就造成中断栈溢出导致系统崩溃。
|
||||
如 果上述条件满足了(也就是有中断描述符,并成为调度器的调度单位,栈也不溢出了,理论上是可以做到中断睡眠的),中断是可以睡眠的,但会引起很多问题.例 如,你在时钟中断中睡眠了,那操作系统的时钟就乱了,调度器也了失去依据;例如,你在一个IPI(处理器间中断)中,其它CPU都在死循环等你答复,你确 睡眠了,那其它处理器也不工作了;例如,你在一个DMA中断中睡眠了,上面的进程还在同步的等待I/O的完成,性能就大大降低了……还可以举出很多例子。 所以,中断是一种紧急事务,需要操作系统立即处理,不是不能做到睡眠,是它没有理由睡眠。
|
||||
|
||||
======================================================
|
||||
|
||||
|
||||
1. 中断处理的时候,不应该发生进程切换,因为在中断 context 中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断,如果在 中断context中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没 有一个task_struct(这点对于softirq和tasklet一样),因此真的休眠了,比如调用了会导致block的例程,内核几乎肯定会死。
|
||||
|
||||
2. schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);
|
||||
|
||||
但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了.所以不可以在中断处理程序中调用schedule()。
|
||||
|
||||
3. 内核中schedule()函数本身在进来的时候判断是否处于中断上下文:
|
||||
|
||||
```cpp
|
||||
if(unlikely(in_interrupt()))
|
||||
|
||||
BUG();
|
||||
```
|
||||
|
||||
4. 中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌。
|
||||
|
||||
5. 处于中断context时候,内核是不可抢占的。因此,如果休眠,则内核一定挂起。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
另一篇:
|
||||
|
||||
|
||||
|
||||
http://blog.openrays.org/blog.php?do=showone&tid=455
|
||||
|
||||
|
||||
|
||||
其结论:
|
||||
|
||||
|
||||
|
||||
5. 中断处理时可否睡眠问题Linux 设计中,中断处理时不能睡眠,这个内核中有很多保护措施,一旦检测到内核会异常。当 一个进程A因为中断被打断时,中断处理程序会使用 A 的内核栈来保存上下文,因为是“抢”的 A 的CPU,而且用了 A 的内核栈,因此中断应该尽可能快的结束。如果 do_IRQ 时又被时钟中断打断,则继续在 A 的内核栈上保存中断上下文,如果发生调度,则 schedule 进 switch_to,又会在
|
||||
A 的 task_struct->thread_struct 里保存此时时种中断的上下文。假如其是在睡眠时被时钟中断打断,并 schedule 的话,假如选中了进程 A,并 switch_to 过去,时钟中断返回后则又是位于原中断睡眠时的状态,抛开其扰乱了与其无关的进程A的运行不说,这里的问题就是:该如何唤醒之呢??另外,和该中断共享中断号的中断也会受到影响。
|
||||
|
||||
|
||||
|
||||
======================================================
|
||||
|
||||
|
||||
|
||||
再一篇,也分析的很到位:
|
||||
|
||||
|
||||
|
||||
http://blog.csdn.net/maray/article/details/5770889
|
||||
|
||||
|
||||
|
||||
其结论:
|
||||
|
||||
Linux是以进程为调度单位的,调度器只看到进程内核栈,而看不到中断栈。在独立中断栈的模式下,如果linux内核在中断路径内发生了调度(从技术上讲,睡眠和调度是一个意思),那么linux将无法找到“回家的路”,未执行完的中断处理代码将再也无法获得执行机会。
|
||||
|
||||
————————————————————————————————————————————————————————————————
|
||||
为什么软中断中也不能睡眠
|
||||
|
||||
|
||||
这个问题实际上是一个老生常谈的问题,答案也很简单,Linux在软中断上下文中是不能睡眠的,原因在于Linux的软中断实现上下文有可能是中断上下文,如果在中断上下文中睡眠,那么会导致Linux无法调度,直接的反应是系统Kernel Panic,并且提示dequeue_task出错。所以,在软中断上下文中,我们不能使用信号量等可能导致睡眠的函数,这一点在编写IO回调函数时需要特别注意。在最近的一个项目中,我们在dm-io的callback函数中去持有semaphore访问竞争资源,导致了系统的kernel
|
||||
panic。其原因就在于dm-io的回调函数在scsi soft irq中执行,scsi soft irq是一个软中断,其会在硬中断发生之后被执行,执行上下文为中断上下文。
|
||||
|
||||
|
||||
|
||||
中断上下文中无法睡眠的原因大家一定很清楚,原因在于中断上下文不是一个进程上下文,其没有一个专门用来描述CPU寄存器等信息的数据结构,所以无法被调度器调度。如果将中断上下文也设计成进程上下文,那么调度器就可以对其进行调度,如果在开中断的情况下,其自然就可以睡眠了。但是,如果这样设计,那么中断处理的效率将会降低。中断(硬中断、软中断)处理都是些耗时不是很长,对实时性要求很高,执行频度较高的应用,所以,如果采用一个专门的后台daemon对其处理,显然并不合适。
|
||||
|
||||
|
||||
|
||||
Linux对中断进行了有效的管理,一个中断发生之后,都会通过相应的中断向量表获取该中断的处理函数。在Linux操作系统中都会调用do_IRQ这个函数,在这个函数中都会执行__do_IRQ(),__do_IRQ函数调用该中断的具体执行函数。在执行过程中,该函数通过中断号找到具体的中断描述结构irq_desc,该结构对某一具体硬件中断进行了描述。在irq_desc结构中存在一条链表irqaction,这条链表中的某一项成员都是一个中断处理方法。这条链表很有意思,其实现了中断共享,例如传统的PCI总线就是采用共享中断的方法,该链表中的一个节点就对应了一个PCI设备的中断处理方法。在PCI设备驱动加载时,都需要注册本设备的中断处理函数,通常会调用request_irq这个函数,通过这个函数会构造一个具体的irq
|
||||
action,然后挂接到某个具体irq_desc的action链表下,实现中断处理方法的注册。在__do_IRQ函数中会通过handle_IRQ_event()函数遍历所有的action节点,完成中断处理过程。到目前为止,中断处理函数do_IRQ完成的都是上半部的工作,也就是设备注册的中断服务程序。在中断上半部中,通常都是关中断的,基本都是完成很简单的操作,否则将会导致中断的丢失。耗时时间相对较长,对实时性要求不是最高的应用都会被延迟处理,都会在中断下半部中执行。所以,在中断上半部中都会触发软中断事件,然后执行完毕,退出服务。
|
||||
|
||||
|
||||
|
||||
__do_IRQ完成之后,返回到do_IRQ函数,在该函数中调用了一个非常重要的函数irq_exit(),在该函数中调用invoke_softirq(),invoke_softirq调用do_softirq()函数,执行软中断的操作。此时,程序的执行环境还是中断上下文,但是与中断上半部不同的是,软中断执行过程中是开中断的,能够被硬中断而中断。所以,如果用户的程序在软中断中睡眠,操作系统该如何调度呢?只有kernel panic了。另外,软中断除了上述执行点之外,还有其他的执行点,在内核中还有一个软中断的daemon处理软中断事务,驱动程序也可以自己触发一个软中断事件,并且在软中断的daemon上下文中执行。但是硬中断触发的事件都不会在这个daemon的上下文中执行,除非修改Linux中的do__IRQ代码。
|
||||
|
||||
|
||||
|
||||
上述对软中断的执行做了简要分析,我对Linux中的硬中断管理机制做了一些代码分析,这一块代码量不是很大,可移植性非常的好~~建议大家阅读,对我上述的分析和理解存在什么不同意见,欢迎大家讨论。
|
||||
|
||||
|
||||
|
||||
#参考资料
|
||||
-------
|
||||
|
||||
[中断--再问中断处理程序为什么不能睡眠 ?](http://bbs.chinaunix.net/thread-3778115-1-1.html)
|
||||
|
||||
[关于LINUX在中断(硬软)中不能睡眠的真正原因](http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22695386&id=196086)
|
||||
|
||||
[再思linux内核在中断路径内不能睡眠/调度的原因(2010)](https://blog.csdn.net/maray/article/details/5770889)
|
||||
|
||||
[]()
|
||||
|
||||
[]()
|
||||
|
||||
[]()
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
* 本作品/博文 ( [AderStep-紫夜阑珊-青伶巷草 Copyright ©2013-2017](http://blog.csdn.net/gatieme) ), 由 [成坚(gatieme)](http://blog.csdn.net/gatieme) 创作.
|
||||
|
||||
* 采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><a rel="license"href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可. 欢迎转载、使用、重新发布, 但务必保留文章署名[成坚gatieme](http://blog.csdn.net/gatieme) ( 包含链接: http://blog.csdn.net/gatieme ), 不得用于商业目的.
|
||||
|
||||
* 基于本文修改后的作品务必以相同的许可发布. 如有任何疑问,请与我联系.
|
||||
@@ -0,0 +1,35 @@
|
||||
前一段时间在研发、调试一个virtual scsi host时,遇到了一个锁的问题,这个问题也就是在spinlock的环境中调用了可能睡眠的函数,导致系统崩溃。在此,对这个问题进行一下总结。
|
||||
|
||||
|
||||
从理论上讲spinlock环境是不能睡眠的,这需要分两种情况进行讨论。
|
||||
|
||||
1. 在 `spinlock_irq`(关中断)的环境下, 如果上下文context)睡眠, 那么极有可能导致 `Linux` 系统无法正常调度, 并且 `Linux` 的心跳中断服务例程都无法调度, 所以, 很容易导致系统进入崩溃状态.
|
||||
|
||||
2. 在 `spinlock`(不关中断)的环境下, 如果上下文睡眠, 那么被调度的上下文极有可能再次访问被锁保护的临界资源, 从而导致系统死锁而崩溃.
|
||||
|
||||
`spin_lock` 本身在等待资源的时候, 就会让 CPU 自旋, 也就是死等那那里. 因此 `spin_lock` 中会调用 `"preempt_disable()"`, 即 `"关抢占"`.
|
||||
此时虽然禁止了抢占, 但是仍可在上下文中, 显式通过调用 scheduler/msleep 等函数让出 CPU. 试想一下, 如果在自旋锁保护的代码中间睡眠, 此时发生进程调度, 则可能另外一个进程会再次调用 `spinlock` 保护的这段代码.
|
||||
这时候由于之前的上下文已经持有了锁, 这个进程是不能拿到锁的, 只能在原地自旋, 不会
|
||||
再睡眠, 更不可能调度. 那么死锁自然就发生了.
|
||||
|
||||
从上面的分析来看, 在 `spinlock` 保护的范围内是不能睡眠的. 如果睡眠, 将会导致系统的崩溃.
|
||||
|
||||
导致死锁的过程:
|
||||
|
||||
```cpp
|
||||
[pid] Action ...... Comment
|
||||
|
||||
[1] 关抢占
|
||||
|
||||
[1] 获得锁
|
||||
|
||||
[1] 睡眠调度 ...... 尽管已经关闭了抢占,[1]依然可以通过主动调用
|
||||
schedule(), schedule_timeout()等主动让出CPU,调度其它进程。
|
||||
|
||||
[2] 关抢占 ...... [1]已经关闭抢占,所以这里相当于nop操作
|
||||
|
||||
[2] 获得锁失败 ...... [1]已经获得了锁,并且还没有释放
|
||||
|
||||
[2] 反复尝试获得锁 ...... 由于关闭了抢占,已经没人能够终止这个反复尝试的操作
|
||||
了,所以这里出现了死锁
|
||||
```
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
int kprobe__sys_clone(void *ctx) {
|
||||
bpf_trace_printk("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) PLUMgrid, Inc.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
|
||||
# run in project examples directory with:
|
||||
# sudo ./hello_world.py"
|
||||
# see trace_fields.py for a longer example
|
||||
|
||||
from bcc import BPF
|
||||
|
||||
with open('./hello_world.c', 'r') as fobj:
|
||||
BPF(text = fobj.readlines()).trace_print()
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
```cpp
|
||||
make -C ~/Work/Kernel/linux/build/x86_64 M=`pwd` BPF_SAMPLES_PATH=`pwd`
|
||||
```
|
||||
|
||||
静态编译
|
||||
|
||||
|
||||
```cpp
|
||||
KBUILD_HOSTLDLIBS += $(LIBBPF) -lelf -static -lz
|
||||
```
|
||||
|
||||
|
||||
#参考资料
|
||||
-------
|
||||
|
||||
[BPF Documentation and Resources](https://facebookmicrosites.github.io/bpf/docs/bpf-docs)
|
||||
|
||||
[](https://cilium.readthedocs.io/en/latest/bpf/)
|
||||
Reference in New Issue
Block a user