1. main函数之前的事情
在终端执行C/C++程序,终端将调用execve函数执行程序。
- filename —— 表示可执行文件
- argv —— 表示参数
- envp —— 表示环境变量
构建一个进程,然后执行。
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
1)首先是构造linux_binprm对象
- mm是mm_struct对象
- pgd是新进程的页表,mm_alloc_pgd申请储存页表的空间,然后拷贝内核页表内容
-
vma是栈空间,最大8MB,初始时4KB
-
p指向栈顶,初始时指向vm_end - sizeof(void*)位置处
将filename、env和argv写入到栈中。

2)然后调用load_elf_binary()函数加载ELF文件,初始化进程虚拟内存空间
- begin_new_exec:设置新的mm_struct数据结构,刷新页表
- setup_new_exec:设置mmap_base位置
- setup_arg_pages:随机化stack_top位置
3)最后调用start_thread()函数,修改ip和sp寄存器的值,开始执行_start()函数,开始main()函数执行
#define START_THREAD(elf_ex, regs, elf_entry, start_stack) \
start_thread(regs, elf_entry, start_stack)
void
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
{
start_thread_common(regs, new_ip, new_sp,
__USER_CS, __USER_DS, 0);
}

2. 进程 vs 线程
Linux并没有区分进程和线程,两者没有差别,统一是task,使用task_struct描述。
- thread_info和thread记录和架构相关的task信息
- stack内核栈,默认大小16KB,stack_vm_area表示使用vmalloc分配的内核栈内存区域
- mm记录虚拟内存
- fs和files分别表示文件系统和打开的文件
- signal表示信号

Linux也没有提供创建进程和线程的系统调用,fork/vfork/clone统一调用kernel_clone()函数创建一个task。

clone_flags控制了创建task时,父子task可以共享的资源。
- CLONE_VM:虚拟地址空间
- CLONE_FS:文件系统
- CLONE_FILES:打开的文件
- CLONE_SIGHAND:信号以及处理handler
当调用pthread_create()函数创建一个线程时,使用的clone_flags如下:
const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
| CLONE_SIGHAND | CLONE_THREAD
| CLONE_SETTLS | CLONE_PARENT_SETTID
| CLONE_CHILD_CLEARTID
| 0);
当使用CLONE_VM时,copy_mm()函数直接使用父进程的mm;否则调用dup_mm复制父进程的虚拟内存空间:遍历每个vma,复制页表:
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
// ...
if (clone_flags & CLONE_VM) {
mmget(oldmm);
mm = oldmm;
} else {
mm = dup_mm(tsk, current->mm);
if (!mm)
return -ENOMEM;
}
tsk->mm = mm;
tsk->active_mm = mm;
sched_mm_cid_fork(tsk);
return 0;
}
如果支持COW,父进程和子进程的页表被设置位写保护wrprotect。
对对应的page执行写操作会触发缺页中断,do_cow_fault()函数会处理这类缺页中断。

另外,pthread_create传入了newsp表示线程用户空间的栈空间(pthread使用mmap分配的内存)。
在运行子进程前,内核栈空间构造了fork_frame对象。
struct fork_frame {
struct inactive_task_frame frame;
struct pt_regs regs;
};
pt_regs::bp指向线程栈空间,子进程运行ret_from_fork_asm()返回用户空间,使用newsp线程栈空间。

更多内容请看下回。
1535

被折叠的 条评论
为什么被折叠?



