1:相关信息
代码:linux 0.11
2: 进程结构体
每个进程在内核中都对应着一个结构体struct task_struct,用来表示进程的状态和相关信息
struct task_struct {
/* these are hardcoded - don't touch */
long state; /*进程的状态 -1 unrunnable(为运行), 0 runnable(运行), >0 stopped(停止) */
long counter; //进程的时间片计数,当为0时就要进行调度切换 counter = counter/2 + priority
long priority;//进行的优先级,越大越高,优先被执行
long signal;//信号
struct sigaction sigaction[32];//信号位图
long blocked; /* bitmap of masked signals 阻塞和非阻塞*/
/* various fields */
int exit_code;//退出码
unsigned long start_code,end_code,end_data,brk,start_stack;//开始码 结束码...
long pid,father,pgrp,session,leader;//进程ID
unsigned short uid,euid,suid;//用户ID
unsigned short gid,egid,sgid;//组ID
long alarm; //警告
long utime,stime,cutime,cstime,start_time; //用户运行时间,内核运行时间,子进程用户运行时间内, 子进程内核运行时间,开始时间
unsigned short used_math;//是否使用协处理器
/* file system info */
int tty; /* -1 if no tty, so it must be signed 是否开发控制台 */
unsigned short umask;
struct m_inode * pwd;//路径
struct m_inode * root;//根
struct m_inode * executable;
unsigned long close_on_exec;
struct file * filp[NR_OPEN];//记录当前进程打开的文件
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];//存放代码和数据的地址
/* tss for this task */
struct tss_struct tss; //存放记录当前CPU寄存器的值
};
struct tss_struct {
long back_link; /* 16 high bits zero */
long esp0;
long ss0; /* 16 high bits zero */
long esp1;
long ss1; /* 16 high bits zero */
long esp2;
long ss2; /* 16 high bits zero */
long cr3;
long eip;
long eflags;
long eax,ecx,edx,ebx;
long esp;
long ebp;
long esi;
long edi;
long es; /* 16 high bits zero */
long cs; /* 16 high bits zero */
long ss; /* 16 high bits zero */
long ds; /* 16 high bits zero */
long fs; /* 16 high bits zero */
long gs; /* 16 high bits zero */
long ldt; /* 16 high bits zero */
long trace_bitmap; /* bits: trace 0, bitmap 16-31 */
struct i387_struct i387;
};
其中进程在内核中的结构表示为:
3:进行创建的过程
1:进程调度初始化 sched_init();
2:切换到用户态 move_to_user_mode();
3:进程初始化
- 手动创建0号进程,作为所有进程的父进程
fork() init()
- 在0号进程中,打开标准输入,输出,错误控制台。
- 并创建1号进程,1号进程中打开/etc/rc(配置文件,开机运行的配置,例如logo) 并执行/bin/sh
- 0号进程不会结束,在没有其他进程调用的时候调用。调用时执行for(; ; ) pause();
4:进程创建fork()
函数,创建其他进程
- 在task链表找一个进行空位存放当前进程
- 创建一个task_struct
- 设置task_struct
进程的创建就是系统调用,所以起始位置在system_call.s中, 本质是对当前进程进行复制(结构体的赋值,把当前结构体的置赋值给新的结构体)
其次,对栈和堆进行复制
3.1:linux 的main函数对应的过程
void main(void) /* This really IS void, no error here. */
{ /* The startup routine assumes (well, ...) this */
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
//内存拷贝
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;//4K对齐
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;//最大为16M
if (memory_end > 12*1024*1024) //缓存区根据内存的大小确定大小
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#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()) { /* we count on this going ok 创建0号进程,如何创建成功就返回一个0*/
init();
}
/*
* NOTE!! For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
for(;;) pause();
}
3.2:进程调度初始化 sched_init()
创建0号进程
void sched_init(void)
{
int i;
struct desc_struct * p;
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));//把init_task(第一个进程)的tss段的地址,放到dg中tss0的位置 4号位置
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));//把init_task的ldt段的地址,放到dg中ldt0的位置 5号位置
p = gdt+2+FIRST_TSS_ENTRY; //p指向下一个tss的地址,6号位置,那么p中的a,b就表示tss1
for(i=1;i<NR_TASKS;i++) { //遍历63个进程(第一个除外),进行清空
task[i] = NULL; //进程链表清空
p->a=p->b=0; //对应GDT中的tss 设置为0
p++;//指向GDT中对应该进程的ldt
p->a=p->b=0;对应GDT中的ldt设置为0
p++;//指向下一个GDT中的ldt和tss 每轮清除2个,清空了63*2个desc_sturct 再加上前面的6个总共 132个。gdt总共有256个
}
/* Clear NT, so that we won't have troubles with that later on */
//设置一些寄存器
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
ltr(0);
lldt(0);
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
set_intr_gate(0x20,&timer_interrupt);//设置系统门
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call);//设置系统中断->系统调用,(进程的相关动作都是系统调用)
}
全局描述符表(Global Descriptor Table, 简称GDT)
//head.h
typedef struct desc_struct {
unsigned long a,b;
} desc_table[256];
extern desc_table idt,gdt; //含有256个desc_struct
在sched_init代码中设置tss和ldt中的两个宏:表示的是首个TSS和首个LDT的位置,下面注释中表示的是gdt各个描述符的
/*
* Entry into gdt where to find first TSS. 0-nul, 1-cs, 2-ds, 3-syscall
* 4-TSS0, 5-LDT0, 6-TSS1 etc ...
*/
#define FIRST_TSS_ENTRY 4
#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
gdt:
//null
.word 0,0,0,0 ! dummy
//cs
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) //大小8MB
.word 0x0000 ! base address=0 //代码段起始地址0x0
.word 0x9A00 ! code read/exec // 权限 读/执行
.word 0x00C0 ! granularity=4096, 386
//ds
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) //大小8MB
.word 0x0000 ! base address=0 //代码段起始地址0x0
.word 0x9200 ! data read/write // 权限 读/写
.word 0x00C0 ! granularity=4096, 386
其中GDT和单个进程的关系如下图
首先set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
会被展开为_set_tssldt_desc(((char *) (gdt+FIRST_TSS_ENTRY)),((int)(&(init_task.task.tss))),"0x89"),
也就是addr=&(init_task.task.tss),
而n = gdt+FIRST_TSS_ENTRY。
同时将addr转换为int,而将n转换为char*
#define _set_tssldt_desc(n,addr,type) \
__asm__ ("movw $104,%1\n\t" \
"movw %%ax,%2\n\t" \
"rorl $16,%%eax\n\t" \
"movb %%al,%3\n\t" \
"movb $" type ",%4\n\t" \
"movb $0x00,%5\n\t" \
"movb %%ah,%6\n\t" \
"rorl $16,%%eax" \
::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \
"m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \
)
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89")
#define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")
1:内联汇编的输入部分:
- 第0个参数:
"a" (addr)
,将addr也就是&(init_task.task.tss)给eax. - 第1个参数:
"m"(*(n))
,一个内存数,将n的值也就是gdt+4这个数,作为一个内存地址,这个其实就是gdt中进程0的TSS描述符的首字节。第0-1个字节共16位应该放着这个段的限长。 - 第2个参数:
"m" (*(n+2))
,一个内存数,gdt中进程0的TSS描述符的第2个字节。第2-3字节放着这个基地址的低16位。 - 第3个参数:
"m" (*(n+4))
,进程0的tss段描述的第4个字节。这里面放的基地址的中8位。 - 第4个参数:
"m"(*(n+5))
,进程0的tss段描述符的第5个字节,里面放的类型 - 第5个参数:
"m"(*(n+6))
,进程0的tss段描述符的第6个字节,里面放的段限长,粒度啥的 - 第6个参数:
"m"(*(n+7))
,进程0的tss段描述符的第7个字节,里面放的基地址的高8位。
所以这些参数其实就是构成了这个段描述符的各个部分。
2:汇编指令的含义
-
"movw $104,%1\n\t"
设置段限长为104个单位。具体多少得看G位。也就是倒数第二个字节的最高位。 -
"movw %%ax,%2\n\t" \
将输入的&(init_task.task.tss)地址给基地址的低16位。 -
"rorl $16,%%eax\n\t" \,
将eax右循环移动16位 -
"movb %%al,%3\n\t" \,
al其中就是地址的中8位,第3个参数。 -
"movb $" type ",%4\n\t" \,
编译后就是"movb $0x89,%4\n\t" ,而0x89=1000 1001。底4位1001是类型域,第5位S为0,说明这是一个系统描述符,而类型又是1001,说明这是一个32位的TSS系统描述符。第6-7位说明这个段的DPL为0,最高位存在位置1 。 -
"movb $0x00,%5\n\t" \,
给第5个参数,这个参数是控制粒度啥的,通过这个最高位为0,可以看出这个段的粒度为1B。所以这个段的限长为104个字节。其实TSS结构的长度就是104字节。 -
"movb %%ah,%6\n\t" \
将基地址的最后的最高8位写到第6个参数。 -
"rorl $16,%%eax" \,eax
再右循环移位就是让eax设置为0了。
经过上面的过程,将gdt里面的第一个TSS描述符就做好了,这个描述符的基地址指向init_task.tss的地址。
同理LDT0也是这样来够着的,只不过LDT0的类型和基地址不同而已了。
3.3 init()函数
当linux初始化文成后,会调用init创建1号进程
void init(void)
{
int pid,i;
setup((void *) &drive_info);//设置驱动信息
(void) open("/dev/tty0",O_RDWR,0); //打开标准输入控制台,句柄为0
(void) dup(0);//打开标准输出控制台 其中dup(int fildes)的作用复制文件描述符,重定向输入输出
(void) dup(0);//打开标准错误控制台
printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
NR_BUFFERS*BLOCK_SIZE);
printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
if (!(pid=fork())) {//创建1号进程,成功返回0,并在新进程中打开rc并运行sh
close(0);
if (open("/etc/rc",O_RDONLY,0))//配置信息
_exit(1);
execve("/bin/sh",argv_rc,envp_rc);//执行shell命令
_exit(2);
}
if (pid>0)//在父进程中
while (pid != wait(&i))//等待父进程退出
/* nothing */;
while (1) { //当上面创建进程失败的情况下
if ((pid=fork())<0) { //又创建进程,失败的情况下
printf("Fork failed in init\r\n");
continue;
}
if (!pid) {//pid = 0 成功
close(0);close(1);close(2); //关闭输入,输出,错误句柄
setsid();
(void) open("/dev/tty0",O_RDWR,0);//打开输入,输出,错误句柄
(void) dup(0);
(void) dup(0);
_exit(execve("/bin/sh",argv,envp));//再次打开sh,参数argv和上面不同
}
while (1)
if (pid == wait(&i))
break;
printf("\n\rchild %d died with code %04x\n\r",pid,i);
sync();
}
_exit(0); /* NOTE! _exit, not exit() */
}
3.4 fork函数
3.4.1: 系统fork _sys_fork:
进程的创建属于系统调用,所以起始位置在system_call.s中
//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
3.4.2: find_empty_process
在进程链表中找到一个空闲的位置, 并返回位置信息i,给当前要创建的进行分配一个进程号
int find_empty_process(void)
{
int i;
repeat:
if ((++last_pid)<0) last_pid=1;
for(i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->pid == last_pid) goto repeat;
for(i=1 ; i<NR_TASKS ; i++)
if (!task[i])
return i;
return -EAGAIN;
}
3.4.3: copy_process:
创建一个进行结构体,放到链表中去
/*
* Ok, this is the main fork-routine. It copies the system process
* information (task[nr]) and sets up the necessary registers. It
* also copies the data segment in it's entirety.
*/
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)//nr:是进程号,其他的通用寄存器的值
{
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; /* NOTE! this doesn't copy the supervisor stack */
p->state = TASK_UNINTERRUPTIBLE;//设置状态,不可中断的状态,防止立刻执行
p->pid = last_pid;
p->father = current->pid;
p->counter = p->priority;
p->signal = 0;
p->alarm = 0;
p->leader = 0; /* process leadership doesn't inherit */
p->utime = p->stime = 0;
p->cutime = p->cstime = 0;
p->start_time = jiffies;
p->tss.back_link = 0;
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)) { //进行代码段和数据段的复制,成功返回0
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
for (i=0; i<NR_OPEN;i++)//父进程有打开的文件,那没子进程也会打开文件
if (f=p->filp[i])
f->f_count++;//文件打开的计数加1
if (current->pwd) //父进程有当前路径
current->pwd->i_count++;//也加1
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
/*通过nr找到gdt中当前进程描述符应该存放的位置,存放tss和ldt的地址*/
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING; /* do this last, just in case 状态设置为可运行,准备运行*/
return last_pid; //返回进程的ID号
}
3.4.4: copy_mem()
进行代码段和数据段的复制
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;//获取新代码段和数据段的地址 nr*64M, 每个进程拥有64M的大小
p->start_code = new_code_base;
set_base(p->ldt[1],new_code_base);//设置新代码段地址到ldt[1]
set_base(p->ldt[2],new_data_base);//设置新数据段地址到ldt[2]
if (copy_page_tables(old_data_base,new_data_base,data_limit)) { //进行老数据段到新数据段copy
free_page_tables(new_data_base,data_limit); //失败就释放
return -ENOMEM;
}
return 0;
}