MIPS Linux 存储管理分析札记

来自Jack's Lab
(版本间的差异)
跳转到: 导航, 搜索
(内核态线程、内核线程以及进程的内核栈问题)
(内核态线程、内核线程以及进程的内核栈问题)
 
(未显示1个用户的1个中间版本)
第56行: 第56行:
 
因此linux 下,每个线程都会有一个内核栈,这个当线程数目很大时是个问题,因此会对线程数目进行限制:
 
因此linux 下,每个线程都会有一个内核栈,这个当线程数目很大时是个问题,因此会对线程数目进行限制:
 
<source lang=bash>
 
<source lang=bash>
# 查看系统最大线程数
+
# 查看 F1C 系统最大线程数
 
$ cat /proc/sys/kernel/threads-max
 
$ cat /proc/sys/kernel/threads-max
 
862
 
862
  
# 查看每个用户的最大进程数(包含线程)
+
# 查看 F1C 每个用户的最大进程数(包含线程)
 
$ cat /proc/sys/kernel/pid_max
 
$ cat /proc/sys/kernel/pid_max
 
32768
 
32768
第70行: 第70行:
 
$ ulimit -u  # 最大用户进程数
 
$ ulimit -u  # 最大用户进程数
 
431
 
431
$ ulimit -n  # 最大文件描述符数
 
1024
 
 
$ ulimit -s  # 栈大小 (KB)
 
$ ulimit -s  # 栈大小 (KB)
 
8192
 
8192

2025年12月10日 (三) 12:40的最后版本

目录

[编辑] 1 内核在从汇编语言跳到C语言中时,其栈的栈顶是在哪里设置的?

内核在进入 start_kernel 前,将 esp 指向了 init_thread_union + THREAD_SIZE, init_thread_union 位于 .data.init_task 节,是个 union 结构,其大小为 THREAD_SIZE(页大小为 4KB 时,THREAD_SIZE 为两个页,即 8KB),定义的比较巧妙:

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};


THREAD_SIZE 定义在 arch/mips/include/asm/thread_info.h:

/* thread information allocation */
#if defined(CONFIG_PAGE_SIZE_4KB) && defined(CONFIG_32BIT)
#define THREAD_SIZE_ORDER (1)
#endif
#if defined(CONFIG_PAGE_SIZE_4KB) && defined(CONFIG_64BIT)
#define THREAD_SIZE_ORDER (2)
#endif
#ifdef CONFIG_PAGE_SIZE_8KB
#define THREAD_SIZE_ORDER (1)
#endif
#ifdef CONFIG_PAGE_SIZE_16KB
#define THREAD_SIZE_ORDER (0)
#endif
#ifdef CONFIG_PAGE_SIZE_32KB
#define THREAD_SIZE_ORDER (0)
#endif
#ifdef CONFIG_PAGE_SIZE_64KB
#define THREAD_SIZE_ORDER (0)
#endif

#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)


[编辑] 2 内核态线程、内核线程以及进程的内核栈问题

linux 下线程库的实现,以常用的 Linuxthread 或者 NPTL 为例,这些库创建线程是使用这些标志 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND 调用 clone() 来实现的,就是一个轻量级进程。


在使用 fork, vfork, clone 这些函数创建进程或者轻量级进程时,内核都会调用 alloc_task_struct() 为其分配一个 task_struct; 调用 alloc_thread_info() ,表面上看好像是分配 thread_info 结构,实际上:

#define alloc_thread_info(tsk) kmalloc(THREAD_SIZE, GFP_KERNEL)


分 配了 THREAD_SIZE 的空间,底端为 thread_info ,指向它的指针保存于 task_struct->thread_info 中。可以看到,task_struct->thread_info + THREAD_SIZE 即为进程或者轻量级进程的内核栈栈底。

无论是使用 clone 生成轻量级进程,还是使用 kernel_thread 创建内核线程,他们都会进入 do_fork,进而调用 alloc_thread_info(),因此他们都有内核栈,大小为 THREAD_SIZE,底端为 thread_info 。

因此linux 下,每个线程都会有一个内核栈,这个当线程数目很大时是个问题,因此会对线程数目进行限制:

# 查看 F1C 系统最大线程数
$ cat /proc/sys/kernel/threads-max
862

# 查看 F1C 每个用户的最大进程数(包含线程)
$ cat /proc/sys/kernel/pid_max
32768

# 查看所有限制
$ ulimit -a

# 查看特定限制
$ ulimit -u  # 最大用户进程数
431
$ ulimit -s  # 栈大小 (KB)
8192

threads-max 主要由 kernel/fork.c 中的 sysctl_max_threads() 处理,引用局部变量 max_threads

pid_max 定于于内核源码 include/linux/threads.h 中:

#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
                      (sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))


[编辑] 3 MIPS 与 X86 的 TLB 差别

其在于对 TLB 不命中时的处理上:

  • MIPS 会触发TLB Refill 异常,内核的 tlb_refill_handler 会以 pgd_current 为当前进程的 PGD 基址,索引获得转换失败的虚址对应的 PTE,并将其填入 TLB,完了CPU 把刚刚转换失败的虚地址再走一下 TLB 就OK了。
  • X86 在 TLB 不命中时,是由硬件 MMU 以 CR3 为当前进程的 PGD 基址,索引获得 PFN 后,直接输出 PA。同时 MMU 会填充 TLB 以加快下次转换的速度。


另外转换失败的虚址,MIPS 使用 BadVAddr 寄存器存放,X86 使用 CR2 存放



[编辑] 4 关于 MIPS32 页表的大小问题

系统每 fork 一个进程或者 clone 一个进程没有CLONE_VM标志,内核都会调用 __get_free_pages 为新进程分配一个页给页目录表,前512项初始化都指向invalid_pte_table(空页表,共有 PAGE_SIZE/sizeof(pte_t) 项),后面的都从 init 进程继承。而后内核会根据新进程所需的存储空间大小,为其分配页表(大小为一个页),分配内存页,设置相应的页表项,设置对应的页目录表项。


可以看到,只要进程数目固定页目录表所需的空间是固定的,每个进程页目录表为一个页大小;而进程总页表的大小要依赖于进程代码、数据的大小。


一个典型的 MIPS32 系统,页大小 (PAGE_SIZE) 为4KB,单用户模式,仅有bash进程,其页表大小为 48KB 左右(cat /proc/meminfo|grep PageTables);页目录的大小可由下式计算:

PGD_SIZE = NR_PROCESS(exclude kernel thread) * PAGE_SIZE

页大小 (PAGE_SIZE) 定义在 arch/mips/include/asm/page.h:

/*
 * PAGE_SHIFT determines the page size
 */
#ifdef CONFIG_PAGE_SIZE_4KB
#define PAGE_SHIFT  12
#endif
#ifdef CONFIG_PAGE_SIZE_8KB
#define PAGE_SHIFT  13
#endif
#ifdef CONFIG_PAGE_SIZE_16KB
#define PAGE_SHIFT  14
#endif
#ifdef CONFIG_PAGE_SIZE_32KB
#define PAGE_SHIFT  15
#endif
#ifdef CONFIG_PAGE_SIZE_64KB
#define PAGE_SHIFT  16
#endif

#define PAGE_SIZE   (_AC(1,UL) << PAGE_SHIFT)



[编辑] 5 MIPS TLB

MIPS TLB 的每项的主要数据有:

 | G | ASID | VPN | PFN | 


其中 VPN 为 virtual page number, PFN 为 physical frame number。 项与项之间没有次序,CPU 转换时是直接匹配的。不能称为 Page Table 。

而 Page Table 当年设计成 数组结构,以数组的下标作为虚页号是经过考虑的,即使今天看来,我们依然能够体会它的巧妙。



[编辑] 6 Reference



个人工具
名字空间

变换
操作
导航
工具箱