Linux内核——进程管理01(进程创建)
进程管理01(进程创建)
该部分主要内容包括:
- task_struct结构体的基本概念
- 0号进程的创建
- 1号进程的创建
- 后续进程的创建
进程的状态——task_struct结构体
task_struct结构体是PCB(Process Control Block,进程控制块)在Linux中的具体实现,它是 Linux 内核中用于表示进程的核心数据结构,包含了进程的所有信息。

代码以Linux0.1.1版本为例:
struct task_struct {
long state; /* 进程状态:-1 不可运行,0 可运行,>0 已停止 */
long counter; /* 进程剩余的时间片 */
long priority; /* 进程优先级 */
long signal; /* 待处理信号的位图 */
struct sigaction sigaction[32]; /* 信号处理函数数组 */
long blocked; /* 被阻塞(屏蔽)信号的位图 */
int exit_code; /* 进程退出代码 */
unsigned long start_code, end_code, end_data, brk, start_stack; /* 内存管理字段:代码段、数据段、堆、栈的起始和结束地址 */
long pid, father, pgrp, session, leader; /* 进程ID、父进程ID、进程组ID、会话ID、会话领导者标志 */
unsigned short uid, euid, suid; /* 用户ID、有效用户ID、保存的用户ID */
unsigned short gid, egid, sgid; /* 组ID、有效组ID、保存的组ID */
long alarm; /* 闹钟剩余时间 */
long utime, stime, cutime, cstime, start_time; /* 用户态CPU时间、内核态CPU时间、子进程用户态时间、子进程内核态时间、进程启动时间 */
unsigned short used_math; /* 是否使用了协处理器(FPU) */
/* 文件系统相关信息 */
int tty; /* 关联的终端设备号,-1 表示无终端 */
unsigned short umask; /* 文件创建掩码(权限) */
struct m_inode * pwd; /* 当前工作目录的 inode 指针 */
struct m_inode * root; /* 根目录的 inode 指针 */
struct m_inode * executable; /* 可执行文件的 inode 指针 */
unsigned long close_on_exec; /* exec 时关闭的文件描述符位图 */
struct file * filp[NR_OPEN]; /* 打开文件的文件指针数组 */
/* 进程的局部描述符表(LDT) */
struct desc_struct ldt[3]; /* LDT 条目:0-空,1-代码段,2-数据段和堆栈段 */
/* 进程的任务状态段(TSS) */
struct tss_struct tss; /* 用于硬件上下文切换的任务状态段 */
};

task_struct结构体中包含了进程运行中的相关信息,例如:
counter:剩余时间片
priority:进程优先级(Linux使用时间片优先级调度算法)
signal
…
进程调度过程中保存的任务状态,比如寄存器等,均保存在TSS任务状态段中(Linux0.1.1版本,后续版本中TSS段被优化掉了)
该结构体保存了硬件的上下文信息,用于保存恢复现场

进程的创建
0号进程的创建(进程初始化)
在内核初始化的过程中,会创建0号进程
代码路径:linux-0.11\init\main.c
0号进程和进程调度的初始化的创建为:sched_init();

在sched_init()中完成0号进程创建以及调度器的初始化:
调度器初始化在进行一系列相关初始化后,重点是设置了一个系统调用set_system_gate(0x80,&system_call)
进程的创建和调度都是由这个系统调用完成的

0号进程创建后,创建1号进程进入init()函数
代码如下:
void main(void)
{
/*
* 此时中断仍然被禁用。先进行必要的设置,然后启用中断
*/
ROOT_DEV = ORIG_ROOT_DEV; /* 设置根设备为原始根设备 */
drive_info = DRIVE_INFO; /* 获取驱动器信息 */
memory_end = (1<<20) + (EXT_MEM_K<<10); /* 计算内存结束地址:1MB + 扩展内存大小 */
memory_end &= 0xfffff000; /* 对齐到 4KB 边界 */
if (memory_end > 16*1024*1024) /* 如果内存超过 16MB */
memory_end = 16*1024*1024; /* 限制内存结束地址为 16MB */
if (memory_end > 12*1024*1024) /* 如果内存超过 12MB */
buffer_memory_end = 4*1024*1024; /* 设置缓冲区结束地址为 4MB */
else if (memory_end > 6*1024*1024) /* 如果内存超过 6MB */
buffer_memory_end = 2*1024*1024; /* 设置缓冲区结束地址为 2MB */
else
buffer_memory_end = 1*1024*1024; /* 否则设置为 1MB */
main_memory_start = buffer_memory_end; /* 主内存起始地址为缓冲区结束地址 */
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024); /* 初始化 RAMDISK */
#endif
mem_init(main_memory_start, memory_end); /* 初始化内存管理 */
trap_init(); /* 初始化陷阱(异常处理) */
blk_dev_init(); /* 初始化块设备 */
chr_dev_init(); /* 初始化字符设备 */
tty_init(); /* 初始化终端 */
time_init(); /* 初始化时间 */
sched_init(); /* 初始化调度器 */
buffer_init(buffer_memory_end); /* 初始化缓冲区 */
hd_init(); /* 初始化硬盘 */
floppy_init(); /* 初始化软盘 */
sti(); /* 启用中断 */
move_to_user_mode(); /* 切换到用户模式 */
if (!fork()) { /* 我们假设 fork 成功 */
init(); /* 在子进程中执行 init 函数 */
}
for(;;) pause();
}
1号进程的创建(init进程)
使用fork()完成1号进程的创建
- 切换到用户模式:通过move_to_user_mode()函数将内核从特权模式切换到用户模式
- 执行init()函数:1号进程执行init()函数,负责初始化用户环境并启动系统服务
在1号进程中:
1. 打开标准输入、输出、错误控制台句柄
2. 创建1号进程,在1号进程中打开"/etc/rc",并执行shell程序/bin/sh

后续进程的创建
代码路径:linux-0.11\kernel\fork.c
进程创建过程:
1. 在task链表中找一个进程空位存放当前的进程
2. 创建一个task_struct
3. 配置这个task_struct
在0号进程创建过程中提到进程的创建是一个系统调用的过程,因此在system_call.s中可以找到该系统调用过程:
.align 2
_sys_fork:
call _find_empty_process // 找空位、分配进程号
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process
addl $20,%esp
1: ret
从这个系统调用过程可以看出:进程的创建就是一个进程的复制过程,或者说是对原进程的task_struct复制的过程
首先是找空位、分配一个新的进程号(调用find_empty_process函数);
创建一个子进程主体(调用copy_process函数)
struct task_struct *p;
p = (struct task_struct *) malloc(sizeof(task_struct ));

设置该子进程(copy_process函数内部)
设置内容包括:pid、父进程pid、时间片优先级、tss、寄存器、分配代码段、数据段内存(调用copy_mem函数)、继承父进程打开的文件、pwd、root目录等;

最终返回新进程pid
完整代码如下:
int copy_process(int nr, long ebp, long edi, long esi, long gs, long none,
long ebx, long ecx, long edx,
long fs, long es, long ds,
long eip, long cs, long eflags, long esp, long ss)
{
struct task_struct *p;
int i;
struct file *f;
p = (struct task_struct *) get_free_page(); /* 获取一页空闲内存用于新任务 */
if (!p)
return -EAGAIN; /* 内存不足时返回错误 */
task[nr] = p; /* 将新任务指针存入任务数组 */
*p = *current; /* 复制当前进程的任务结构体(不包括内核栈) */
p->state = TASK_UNINTERRUPTIBLE; /* 设置新进程状态为不可中断 */
p->pid = last_pid; /* 设置新进程的 PID */
p->father = current->pid; /* 设置父进程 PID */
p->counter = p->priority; /* 初始化时间片计数器 */
p->signal = 0; /* 初始化信号 */
p->alarm = 0; /* 初始化闹钟 */
p->leader = 0; /* 新进程不是会话领导者 */
p->utime = p->stime = 0; /* 初始化用户态和内核态 CPU 时间 */
p->cutime = p->cstime = 0; /* 初始化子进程的 CPU 时间 */
p->start_time = jiffies; /* 设置进程启动时间 */
p->tss.back_link = 0; /* 初始化 TSS 的后向链接 */
p->tss.esp0 = PAGE_SIZE + (long) p; /* 设置内核栈指针 */
p->tss.ss0 = 0x10; /* 设置内核栈段选择符 */
p->tss.eip = eip; /* 设置指令指针 */
p->tss.eflags = eflags; /* 设置标志寄存器 */
p->tss.eax = 0; /* 初始化寄存器 */
p->tss.ecx = ecx;
p->tss.edx = edx;
p->tss.ebx = ebx;
p->tss.esp = esp;
p->tss.ebp = ebp;
p->tss.esi = esi;
p->tss.edi = edi;
p->tss.es = es & 0xffff; /* 设置段寄存器 */
p->tss.cs = cs & 0xffff;
p->tss.ss = ss & 0xffff;
p->tss.ds = ds & 0xffff;
p->tss.fs = fs & 0xffff;
p->tss.gs = gs & 0xffff;
p->tss.ldt = _LDT(nr); /* 设置局部描述符表 */
p->tss.trace_bitmap = 0x80000000; /* 设置调试位图 */
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387)); /* 保存浮点状态 */
if (copy_mem(nr, p)) { /* 复制内存 */
task[nr] = NULL; /* 失败时清除任务指针 */
free_page((long) p); /* 释放内存 */
return -EAGAIN; /* 返回错误 */
}
for (i = 0; i < NR_OPEN; i++) /* 复制打开的文件 */
if (f = p->filp[i])
f->f_count++;
if (current->pwd)
current->pwd->i_count++; /* 增加当前工作目录的引用计数 */
if (current->root)
current->root->i_count++; /* 增加根目录的引用计数 */
if (current->executable)
current->executable->i_count++; /* 增加可执行文件的引用计数 */
set_tss_desc(gdt + (nr << 1) + FIRST_TSS_ENTRY, &(p->tss)); /* 设置 TSS 描述符 */
set_ldt_desc(gdt + (nr << 1) + FIRST_LDT_ENTRY, &(p->ldt)); /* 设置 LDT 描述符 */
p->state = TASK_RUNNING; /* 设置进程状态为运行中 */
return last_pid; /* 返回新进程的 PID */
}
int copy_mem(int nr, struct task_struct * p)
{
unsigned long old_data_base, new_data_base, data_limit;
unsigned long old_code_base, new_code_base, code_limit;
code_limit = get_limit(0x0f); /* 获取代码段限制 */
data_limit = get_limit(0x17); /* 获取数据段限制 */
old_code_base = get_base(current->ldt[1]); /* 获取当前进程的代码段基址 */
old_data_base = get_base(current->ldt[2]); /* 获取当前进程的数据段基址 */
if (old_data_base != old_code_base)
panic("We don't support separate I&D"); /* 不支持代码段和数据段分离 */
if (data_limit < code_limit)
panic("Bad data_limit"); /* 数据段限制不能小于代码段限制 */
new_data_base = new_code_base = nr * 0x4000000; /* 为新进程分配新的基址 */
p->start_code = new_code_base; /* 设置新进程的代码段起始地址 */
set_base(p->ldt[1], new_code_base); /* 设置新进程的代码段基址 */
set_base(p->ldt[2], new_data_base); /* 设置新进程的数据段基址 */
if (copy_page_tables(old_data_base, new_data_base, data_limit)) {
free_page_tables(new_data_base, data_limit); /* 复制页表失败时释放内存 */
return -ENOMEM; /* 返回内存不足错误 */
}
return 0; /* 成功返回 */
}
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid) < 0) last_pid = 1; /* 递增 PID,如果溢出则重置为 1 */
for (i = 0; i < NR_TASKS; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat; /* 检查 PID 是否已存在 */
for (i = 1; i < NR_TASKS; i++)
if (!task[i])
return i; /* 返回空闲的任务槽 */
return -EAGAIN; /* 没有空闲任务槽时返回错误 */
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)