您的位置:首页 > 财经 > 金融 > 操作系统概述(三、虚拟化)

操作系统概述(三、虚拟化)

2024/11/17 21:25:23 来源:https://blog.csdn.net/surfaceyan/article/details/130937673  浏览:    关键词:操作系统概述(三、虚拟化)

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 十一、操作系统上的进程
    • 1. 从系统启动到第一个进程
      • 系统调用:fork(), 创建进程
      • execv()
      • PATH环境变量
      • 销毁进程
  • 十二、进程的地址空间
    • **查看进程的地址空间**
    • 进程地址空间管理
    • 进程地址空间隔离
  • 十三、系统调用和 shell
  • 十四、C标准库的实现
    • fd, execve等封装
    • 内存管理封装
  • 十五、fork
    • 概念
    • fork的实现
    • other
    • 应用
    • 弊端
  • 十六、可执行文件
  • 可执行文件:状态机的描述
    • 解析可执行文件
    • 逆向工程(Reverse Engineering)
    • from C code to binary
  • 十七、可执行文件的加载
    • ELF loader on OS
    • 动态链接
  • 十八、XV6
      • 调度
  • 二十、处理器调度
      • Linux Namespace Control Groups(cgroups)
  • man proc
    • NAME
    • 描述:
    • 推荐阅读


前言

十一、操作系统上的进程

操纵系统内核的启动:CPU Reset->Firmware->Boot loader->Kernel_start()->…
init进程, init进程通过系统调用创建Linux中的所有…

  • 操作系统启动后做了什么?
  • 操作系统如何管理程序(进程)

1. 从系统启动到第一个进程

eg:

int main()
{cte_init(on_interrupt); // 注册中断for (int i = 0; i < LENGTH(tasks); ++i){// 启动线程,某些mcu甚至没有内存虚拟化,只能启动简单的线程}mpe_init(); // 进入系统内核
}

操作系统启动后会加载“第一个程序”,之后Linux kernel进入后台成为“中断/异常处理程序”。

程序:状态机

  • c代码视角:语句
  • 汇编/机器代码视角:指令
  • 与操作系统交互的方式:syscall

syscall: 进程管理,内存管理,文件管理…

系统调用:fork(), 创建进程

调用系统API, fork将当前状态复制

/* Clone the calling process, creating an exact copy.Return -1 for errors, 0 to the new process,and the process ID of the new process to the old process.  */
extern __pid_t fork (void) __THROWNL;

操作系统:状态机的管理者
虚拟化:操作系统可以同时管理多个状态机,每一步选择一个状态执行

#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid1 = fork();pid_t pid2 = fork();pid_t pid3 = fork();printf("Hello World from (%d, %d, %d)\n", pid1, pid2, pid3); 
}
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>int main(int argc, const *argv[])
{for (int i = 0; i < 2; ++i) {fork();printf("Hello\n");}for (int i = 0; i < 2; ++i) {wait(nullptr);}
}

./a.out 打印6个Hello,但是
./a.out | cat 以及 ./a.out | cat 显示8个Hello
需要在开头加入 setbuf(stdout, nullptr); 不缓冲标准输出

解释:

printf("Hello");  // fflush(stdout);
int *ptr = nullptr;
*ptr = 1;

若无 // 程序报错时不会输出hello,若在输出前清空缓冲区则在报错前会输出hello

printf(); 会缓冲标准输出,fork时缓冲区的内容也会同步复制,因此./a.out | wc -l 为8

execv()

/* Replace the current process, executing PATH with arguments ARGV andenvironment ENVP.  ARGV and ENVP are terminated by NULL pointers.  */
extern int execve (const char *__path, char *const __argv[],char *const __envp[]) __THROW __nonnull ((1, 2));

重置状态机,

man execvebash -c env  # 显示当前环境变量
#include <unistd.h>
#include <stdio.h>// int main(int argc, char* argv[], char* envp[]);
int main()
{// nullptr 为手册中规定的必填项char*  argv[] = {"/bin/bash", "-c", "env", nullptr};char*  envp[] = {"HELLO=WORLD", nullptr};execve(argv[0], argv, envp);  // 重置状态机printf("Hello, World!\n");
}

常用环境变量:

export HELLO=WORLD
so:
env | grep HELLO
get: HELLO=WORLD

PATH环境变量

PATH=x:y:z gcc test.c  # 改变gcc环境变量从而到会不能正常运行

销毁进程

销毁状态机
return;
void exit(int status); // 进程normal terminate,将 status 返回给父进程
_exit();
syscall(SYS_exit, 0);

#include <stdlib.h>
#include <stdio.h>
void func()
{printf("Goodbye, OS\n");
}
int main(int argc, char* argv[])
{atexit(func);// main函数返回,退出进程if (argc < 2) return EXIT_FAILURE;// 退出进程if (strcmp(argv[1], "exit") == 0) exit(0);// 退出进程,不调用funcif (strcmp(argv[1], "_exit") == 0) _exit(0);// 强行退出当前线程,不安全,不会析构对象if (strcmp(argv[1], "__exit") == 0) syscall(SYS_exit, 0);
}

详情:

man exit
man _exit
man syscall

十二、进程的地址空间

c代码:堆、栈、内核区

汇编代码: 地址空间+寄存器

char *p可以和intptr_t互相转换

  • 可以指向“任何地方”
  • 合法的地址(可读或可写)
    • 代码(main, %rip会从此处取出待执行的指令)只读
    • 数据(static int x), 读写
    • 堆栈(int y), 读写
    • 运行时分配的内存,读写
    • 动态链接库
  • 非法的地址
    • NULL, 导致segmentation fault

查看进程的地址空间

32085:   ./a.out
0000561407b26000      4K r---- a.out
0000561407b27000      4K r-x-- a.out
0000561407b28000      4K r---- a.out
0000561407b29000      4K r---- a.out
0000561407b2a000      4K rw--- a.out
00005614097ac000    132K rw---   [ anon ]
00007f9754000000    132K rw---   [ anon ]
00007f9754021000  65404K -----   [ anon ]
00007f975bbfe000      4K -----   [ anon ]
00007f975bbff000   8192K rw---   [ anon ]
00007f975c3ff000      4K -----   [ anon ]
00007f975c400000   8192K rw---   [ anon ]
00007f975cc00000    160K r---- libc.so.6
00007f975cc28000   1620K r-x-- libc.so.6
00007f975cdbd000    352K r---- libc.so.6
00007f975ce15000     16K r---- libc.so.6
00007f975ce19000      8K rw--- libc.so.6
00007f975ce1b000     52K rw---   [ anon ]
00007f975d000000    616K r---- libstdc++.so.6.0.30
00007f975d09a000   1088K r-x-- libstdc++.so.6.0.30
00007f975d1aa000    444K r---- libstdc++.so.6.0.30
00007f975d219000     44K r---- libstdc++.so.6.0.30
00007f975d224000     12K rw--- libstdc++.so.6.0.30
00007f975d227000     12K rw---   [ anon ]
00007f975d2fc000     16K rw---   [ anon ]
00007f975d300000     56K r---- libm.so.6
00007f975d30e000    496K r-x-- libm.so.6
00007f975d38a000    364K r---- libm.so.6
00007f975d3e5000      4K r---- libm.so.6
00007f975d3e6000      4K rw--- libm.so.6
00007f975d3e7000     12K r---- libgcc_s.so.1
00007f975d3ea000     92K r-x-- libgcc_s.so.1
00007f975d401000     16K r---- libgcc_s.so.1
00007f975d405000      4K r---- libgcc_s.so.1
00007f975d406000      4K rw--- libgcc_s.so.1
00007f975d419000      8K rw---   [ anon ]
00007f975d41b000      8K r---- ld-linux-x86-64.so.2
00007f975d41d000    168K r-x-- ld-linux-x86-64.so.2
00007f975d447000     44K r---- ld-linux-x86-64.so.2
00007f975d453000      8K r---- ld-linux-x86-64.so.2
00007f975d455000      8K rw--- ld-linux-x86-64.so.2
00007fffca336000    132K rw---   [ stack ]
00007fffca3a6000     16K r----   [ anon ]
00007fffca3aa000      8K r-x--   [ anon ]
ffffffffff600000      4K --x--   [ anon ]

pmap(1) - report memory of a process

  • Claim: pmap是通过访问procfs(/proc/)实现的
    查看进程的地址空间
  • 进程的地址空间:若干连续的“段”
  • “段”的内存可以访问
  • 不在段内/违反权限的内存访问触发SIGSEGV
    • gdb可以“越权访问”,但不能访问“不存在”的地址

在不进入系统内核的情况下完成系统调用
例子:

  • time,时间:内核维护秒级的时间(所有进程映射同一个页面)
  • 例子:gettimeofday
    • RTFSC
  • RTFM

进程地址空间管理

#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

将文件映射到进程地址空间

进程地址空间隔离

进程内的地址和数据只能访问本进程的地址空间

通过open("/proc/pid/mem, xxx);可访问其他进程的内存空间

十三、系统调用和 shell

shell提供用户接口

  • “与人类直接交互的第一个程序”
  • 帮助人类创建/管理进程(应用程序)、数据文件…

如:

  • “Command-line interface”
  • shell 是一门“把用户指令翻译成系统调用”的编程语言
  • man sh(推荐阅读!), bash …
  • Graphical Shell(GUI)如Windows,Symbian,Android…

shell 常用命令:

  • 重定向 ls > a.txt
  • 管道 ls >| wc -l
  • 后台 ls &
  • 命令组合 (echo a; echo b) | wc -l

A Zero-dependency UNIX Shell

  • 零库函数依赖(-ffreestanding编译、ld链接)
  • 可以作为最小Linux的init程序
  • 用到 文件描述符:一个打开文件的“指针”

如何阅读A Zero-dependency UNIX Shell的代码?

  • strace + gdb

管道的运行原理? easy and clear

十四、C标准库的实现

libc:
如何实现 printf(const char* fmt, …); 可变参数

fd, execve等封装

stdio.h
FILE* 背后其实是是个文件描述符

  • 可以用gdb查看具体的FILE* (如stdout)
  • 可以看到 glibc 的一些内部实现
  • 可以加载glibc的 debugs symbols
  • 封装了文件描述符上的系统调用(fseek, fgetpos, ftell, feof)
// 更易用的封装接口
execlp("echo", "hello", "world", nullptr);

环境变量:env, bash -c env

实现:打印环境变量的小程序:

#include <stdio.h>
int main()
{// Q? environ 是如何被赋值的?extern char** environ;for (char** env = environ; *env; env++) {printf("%s\n", *env);}
}

编译:
gcc env.c -g -static 静态链接
gcc env.c -g 动态链接

gdb a.out
p (char**)environstartiwa (char**)environc

内存管理封装

malloc 和 free

  • 在大区间[L, R) 中维护不相交的区间集合
    M = {[l0, r0), [l1, r1), …}
  • malloc(s) - 返回一段大小未 s 的区间
    • 必要时可以向操作系统申请额外的[L, R) (观察strace)
    • 允许在内存不足时“拒绝”请求
  • free(l) - 给定l 删除 {l, r) ∈ M

Premature optimization is the root of all evil. – D.E.Knuth

workload? 合理假设

  • 越小的对象创建/分配越频繁
  • 较为频繁地分配中等大小的对象
  • 低频率的大对象分配
  • 满足并行要求,即每个线程都会“同时”分配内存
    设置两套系统:
  • fast path
    • 性能极好、并行度极高、覆盖大部分情况
    • 但有小概率会失败
  • slow path
    • 不在乎那么快
    • 但把困难的事情做好
      • 计算机系统里有很多这样的例子(比如cache)

参考 STL 的二级空间分配器
先用锁分配大块内存,然后再把大块内存切成同等小块的分配

十五、fork

概念

系统调用 -> libc -> shell -> 应用软件栈

fd本质是int型,它像一个指针,指向了操作系统的对象。

在调用execve时,fd不会重置!

int open(const char* pathname, int flags);

  • RTFM:O_CLOEXEC, O_APPEND
    文件描述符:一个指向操作系统内对象的“指针”
  • 对象只能通过操作系统允许的方式访问
  • 从0开始编号(0,1,2…stdin,stdout,stderr)
  • 可以通过open取的;close释放;dup“复制”
  • 对于数据文件,文件描述符会“记住”上次访问文件的位置
    • write(3, “a”, 1); write(3, “b”, 1);

fork下的文件描述符

fd = open("a.txt", O_WRONLY | O_CREAT); assert(fd > 0);
int pid = fork(); assert(pid > 0);
if (pid == 0) {write(fd, "Hello", 5);
} else {write(fd, "World", 5);
}

在dup时,不同的fd号指向同一个对象,同时也共享offset

fork的实现

mmu分页
进程所有的页面属于操作系统,进程只拥有映射表

page fault 缺页错误

copy on write

  • "Copy-on-write"只有被写入的页面才会复制一份
    • 被复制后,整个地址空间都被标记为“只读”
    • 操作系统捕获Page Fault后酌情复制页面
    • fork-execve效率得到提升
  • 操作系统会维护每个页面的引用计数
申请内存,malloc(128M);
for(int i=0; i<1000; ++i) {if (pid == 0) break;
}
使用内存
  • 所以,真个操作系统里libc代码和只读数据只有一个副本!
  • 推论:统计进程占用的内存是个伪命题

所以一个进程占用了多少内存如何计算?
用虚拟内存查看内存泄露

other

利用fork实现回溯
dfs_fork

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>#define DEST  '+'
#define EMPTY '.'struct move {int x, y, ch;
} moves[] = {{ 0, 1, '>' },{ 1, 0, 'v' },{ 0, -1, '<' },{ -1, 0, '^' },
};char map[][512] = {"#######","#.#.#+#","#.....#","#.....#","#...#.#","#######","",
};void display();void dfs(int x, int y) {if (map[x][y] == DEST) {display();} else {int nfork = 0;for (struct move *m = moves; m < moves + 4; m++) {int x1 = x + m->x, y1 = y + m->y;if (map[x1][y1] == DEST || map[x1][y1] == EMPTY) {int pid = fork(); assert(pid >= 0);if (pid == 0) { // map[][] copiedmap[x][y] = m->ch;dfs(x1, y1);exit(0); // clobbered map[][] discarded} else {nfork++;waitpid(pid, NULL, 0); // wait here to serialize the search}}}while (nfork--) wait(NULL);}
}int main() {dfs(1, 1);
}void display() {for (int i = 0; ; i++) {for (const char *s = map[i]; *s; s++) {switch (*s) {case EMPTY: printf("   "); break;case DEST : printf(" ○ "); break;case '>'  : printf(" → "); break;case '<'  : printf(" ← "); break;case '^'  : printf(" ↑ "); break;case 'v'  : printf(" ↓ "); break;default   : printf("▇▇▇"); break;}}printf("\n");if (strlen(map[i]) == 0) break;}fflush(stdout);sleep(1); // to see the effect of parallel search
}

应用

跳过初始化

  • Zygote Process (Android)
    • Java Virtual Machine 初始化涉及大量的类加载
    • 一次加载,全员使用
      • App 使用的系统资源
      • 基础类库
      • libc
  • Chrome site isolation (Chrome)
  • Fork server (AFL)

利用fork保存当前程序快照,从而记录程序运行中的某种状态

弊端

  • 如果只有内存和文件描述符,没问题
  • 信号
  • 线程
  • 进程间通讯对象
  • ptrace(追踪/调试)

so:

int posix_spawn(pid_t *pid, char*path, posix_spawn_file_actions_t *file_actions, posix_spawnattr_t *attrp, char* argv[], char* envp[]);

十六、可执行文件

  • 可执行文件
  • 解析可执行文件
  • 链接和加载
    • 假设只有静态链接

手册:
System V ABI: System V Application Binary Interface

file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=b6e0470ba2180936a61ec7ab71131a73121f88ee, for GNU/Linux 3.2.0, not stripped

可执行文件:状态机的描述

  • 可执行文件是最重要的操作系统对象
  • 描述了状态机的初始状态+迁移的数据结构
    • 寄存器+内存
strace a.out
---
execve("./a.out", ["./a.out"], 0x7ffe0d273c20 /* 63 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc6f6cbdc0) = -1 EINVAL (Invalid argument)
brk(NULL)                               = 0xd62000
brk(0xd62dc0)                           = 0xd62dc0
arch_prctl(ARCH_SET_FS, 0xd623c0)       = 0
set_tid_address(0xd62690)               = 7873
set_robust_list(0xd626a0, 24)           = 0
rseq(0xd62d60, 0x20, 0, 0x53053053)     = 0
uname({sysname="Linux", nodename="MyComputer", ...}) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
readlink("/proc/self/exe", "/home/sdf/a.out", 4096) = 15
getrandom("\x81\x04\x29\xc6\xd5\x76\x65\x0f", 8, GRND_NONBLOCK) = 8
brk(0xd83dc0)                           = 0xd83dc0
brk(0xd84000)                           = 0xd84000
mprotect(0x4c1000, 16384, PROT_READ)    = 0
exit_group(0)                           = ?
+++ exited with 0 +++strace ./a.c
---
execve("./a.c", ["./a.c"], 0x7ffe85525b00 /* 63 vars */) = -1 ENOEXEC (Exec format error)
strace: exec: Exec format error
+++ exited with 1 +++
  • Linux
    • a.out(deprecated)
    • ELF(Executable Linkable Format)
    • Shell-bang
      • Shell-bang 其实是一个“偷换参数”的execve
      • 当加载器读取到#!后会将其后的参数传入execve
      #! magic number
      

解析可执行文件

https://www.gnu.org/software/binutils/
GNU binutils

  • 生成可执行文件
    • ld(linker), as(assembler)
    • ar, ranlib
  • 分析可执行文件
    • objcopy/objdump/readelf
    • addr2line, size, nm

gdb core
bt/backtrace
如何追踪到程序挂掉时的调用栈?
函数调用call时会在栈上留下endbr,然后push %rbp,然后mov %rsp,%rbp

逆向工程(Reverse Engineering)

from C code to binary

int main()
{hello();
}
void hello()
{char* p = (char*)main + 0xa + 1;int32_t offset = *(int32_t*)p;assert( (char*)main + 0xf + offset == (char*)hello );// (char*)main + 0xf // call hello的next PC 
}

重新理解编译、链接
连接器(ld)将所有的符号链接

十七、可执行文件的加载

  • 若干真正的静态ELF加载器
  • 动态链接和加载

ELF loader on OS

可执行文件

  • 一个描述了状态机的初始状态(迁移)的数据结构
    • 不同于内存里的数据结构,“指针”都被“偏移量”代替
    • 数据结构各个部分定义:/usr/include/elf.h

加载器(loader)

  • 解析数据结构+复制到内存+跳转
  • 创建进程运行时初始状态(argv, evnp, …)
    • loader-static.c
      • 可以加载任何静态链接的代码 minimal.S, dfs-fork.c
      • 并且能正确处理参数/环境变量 env.c
    • RTFM:

loader-static.c

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <elf.h>
#include <fcntl.h>
#include <sys/mman.h>#define STK_SZ           (1 << 20)
#define ROUND(x, align)  (void *)(((uintptr_t)x) & ~(align - 1))
#define MOD(x, align)    (((uintptr_t)x) & (align - 1))
#define push(sp, T, ...) ({ *((T*)sp) = (T)__VA_ARGS__; sp = (void *)((uintptr_t)(sp) + sizeof(T)); })void execve_(const char *file, char *argv[], char *envp[]) {// WARNING: This execve_ does not free process resources.int fd = open(file, O_RDONLY);assert(fd > 0);Elf64_Ehdr *h = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0);assert(h != (void *)-1);assert(h->e_type == ET_EXEC && h->e_machine == EM_X86_64);Elf64_Phdr *pht = (Elf64_Phdr *)((char *)h + h->e_phoff);for (int i = 0; i < h->e_phnum; i++) {Elf64_Phdr *p = &pht[i];if (p->p_type == PT_LOAD) {int prot = 0;if (p->p_flags & PF_R) prot |= PROT_READ;if (p->p_flags & PF_W) prot |= PROT_WRITE;if (p->p_flags & PF_X) prot |= PROT_EXEC;void *ret = mmap(ROUND(p->p_vaddr, p->p_align),              // addr, rounded to ALIGNp->p_memsz + MOD(p->p_vaddr, p->p_align),   // lengthprot,                                       // protectionMAP_PRIVATE | MAP_FIXED,                    // flags, private & strictfd,                                         // file descriptor(uintptr_t)ROUND(p->p_offset, p->p_align)); // offsetassert(ret != (void *)-1);memset((void *)(p->p_vaddr + p->p_filesz), 0, p->p_memsz - p->p_filesz);}}close(fd);static char stack[STK_SZ], rnd[16];void *sp = ROUND(stack + sizeof(stack) - 4096, 16);void *sp_exec = sp;int argc = 0;// argcwhile (argv[argc]) argc++;push(sp, intptr_t, argc);// argv[], NULL-terminatefor (int i = 0; i <= argc; i++)push(sp, intptr_t, argv[i]);// envp[], NULL-terminatefor (; *envp; envp++) {if (!strchr(*envp, '_')) // remove some verbose onespush(sp, intptr_t, *envp);}// auxv[], AT_NULL-terminatepush(sp, intptr_t, 0);push(sp, Elf64_auxv_t, { .a_type = AT_RANDOM, .a_un.a_val = (uintptr_t)rnd } );push(sp, Elf64_auxv_t, { .a_type = AT_NULL } );asm volatile("mov $0, %%rdx;" // required by ABI"mov %0, %%rsp;""jmp *%1" : : "a"(sp_exec), "b"(h->e_entry));
}int main(int argc, char *argv[], char *envp[]) {if (argc < 2) {fprintf(stderr, "Usage: %s file [args...]\n", argv[0]);exit(1);}execve_(argv[1], argv + 1, envp);
}

boot main

#include <stdint.h>
#include <elf.h>
#include <x86/x86.h>#define SECTSIZE 512
#define ARGSIZE  1024static inline void wait_disk(void) {while ((inb(0x1f7) & 0xc0) != 0x40);
}static inline void read_disk(void *buf, int sect) {wait_disk();outb(0x1f2, 1);outb(0x1f3, sect);outb(0x1f4, sect >> 8);outb(0x1f5, sect >> 16);outb(0x1f6, (sect >> 24) | 0xE0);outb(0x1f7, 0x20);wait_disk();for (int i = 0; i < SECTSIZE / 4; i ++) {((uint32_t *)buf)[i] = inl(0x1f0);}
}static inline void copy_from_disk(void *buf, int nbytes, int disk_offset) {uint32_t cur  = (uint32_t)buf & ~(SECTSIZE - 1);uint32_t ed   = (uint32_t)buf + nbytes;uint32_t sect = (disk_offset / SECTSIZE) + (ARGSIZE / SECTSIZE) + 1;for(; cur < ed; cur += SECTSIZE, sect ++)read_disk((void *)cur, sect);
}static void load_program(uint32_t filesz, uint32_t memsz, uint32_t paddr, uint32_t offset) {copy_from_disk((void *)paddr, filesz, offset);char *bss = (void *)(paddr + filesz);for (uint32_t i = filesz; i != memsz; i++) {*bss++ = 0;}
}static void load_elf64(Elf64_Ehdr *elf) {Elf64_Phdr *ph = (Elf64_Phdr *)((char *)elf + elf->e_phoff);for (int i = 0; i < elf->e_phnum; i++, ph++) {load_program((uint32_t)ph->p_filesz,(uint32_t)ph->p_memsz,(uint32_t)ph->p_paddr,(uint32_t)ph->p_offset);}
}static void load_elf32(Elf32_Ehdr *elf) {Elf32_Phdr *ph = (Elf32_Phdr *)((char *)elf + elf->e_phoff);for (int i = 0; i < elf->e_phnum; i++, ph++) {load_program((uint32_t)ph->p_filesz,(uint32_t)ph->p_memsz,(uint32_t)ph->p_paddr,(uint32_t)ph->p_offset);}
}void load_kernel(void) {Elf32_Ehdr *elf32 = (void *)0x8000;Elf64_Ehdr *elf64 = (void *)0x8000;int is_ap = boot_record()->is_ap;if (!is_ap) {// load argument (string) to memorycopy_from_disk((void *)MAINARG_ADDR, 1024, -1024);// load elf header to memorycopy_from_disk(elf32, 4096, 0);if (elf32->e_machine == EM_X86_64) {load_elf64(elf64);} else {load_elf32(elf32);}} else {// everything should be loaded}if (elf32->e_machine == EM_X86_64) {((void(*)())(uint32_t)elf64->e_entry)();} else {((void(*)())(uint32_t)elf32->e_entry)();}
}

linux 内核源码

  • 解压
  • make menuconfig (生成.config文件)
  • make bzImage -j8 (生成镜像???)

编译结果

  • vmlinux(ELF格式的内核二进制代码)
  • vmlinuz ( 压缩的镜像,可以直接被QEMU加载 )
  • readelf入口地址0x1000000(物理内存16M位置)

动态链接

存储保护和加载位置

  • 允许将.dl中的一部分以某个指定的权限映射到内存的某个位置(program header table)
  • 允许自由指定加载器
  • 加入INTERP

空间浪费

  • 字符串存储在常量池,统一通过“指针”访问

  • “符号表”就是Global Offset Table(GOT)
  • 增加一层indirection: Procedure Linkage Table(PLT)
  • 所有未解析的符号都统一翻译成call

十八、XV6

source code:
xv6-riscv

xv6: UNIX v6的现代“克隆”
接近完整的UNIX Shell体验

  • 基本工具集(wc, echo, cat, …)
  • 命令执行、管道、重定向
    • 支持多处理器
    • Now in RISC-V
  • 通过这些系统调用足够支撑以下应用
    • cc, as, ld, vi, sed, awk, troff, lp, …

当拿到makefile时:

  1. make -nB qemu
  2. 想要知道所有命令调用序列
    make -nB qemu | vim -
    :set nowrap
    格式化显示文件的编译
    :%s/ /\r /g
  3. bear make qemu 在xv6内生成 compile_commands.json

调度

在这里插入图片描述

二十、处理器调度

  • 最简单最直接的调度方法:轮询调度法或称时间片轮转法。

考虑到上述调度算法的缺陷,引入优先级策略
nice : [-20,19] 数字越大优先级越低。nice好人卡,nice=19老好人了,谁要CPU 就给谁,一点不抢;nice=-20,这个人坏极了,一直霸占cpu。

  • 基于优先级的调度

    • RTOS: 优先级高的抢占优先级低的,优先级高的总是先执行,直到高优先级程序放弃cpu执行权
    • Linux:nice相差10,CPU资源获取率相差10倍
    • nice/renice:
      将某个进程绑定到某个cpu上
      taskset -c 0 nice -n 19 yes > /dev/null &
      taskset -c 0 nice -n 9 yes > /dev/null &
  • 动态优先级(MLFQ)

  • Complete Fair Scheduling (CFS)

    const int sched_prio_to_weight[40] =
    {
    /*-20*/ 88761,71755,56483,46273,36291, 
    /*-15*/ 29154,23254,18705,14949,11916, 
    /*-10*/ 9548,7620,6100,4904,3906, 
    /*-5*/  3121,2501,1991,1586,1277, 
    /*0*/   1024,820,655,526,423, 
    /*5*/   335,272,215,172,137, 
    /*10*/  110, 87, 70, 56,45, 
    /*15*/  36, 29, 23, 18, 15, 
    }
    

    哪个程序占用的 虚拟cpu时间 短就给谁 CPU

没有完美的调度算法,不存在一种包揽所有场景的调度算法。 算法设计需要和业务相配合

Linux Namespace Control Groups(cgroups)

namespace轻量级虚拟化
cgroups 允许以进程组为单位管理资源


man proc

NAME

proc - 进程信息,伪文件系统

描述:

proc伪文件系统

推荐阅读

The GNU C Library
newlibc

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com