1进程运转方式

总结:大概就是每10ms进定时器中断,在中断函数do_timer 中,进行进程的调度等操作。

详细:

系统时间(jiffies 系统滴答)

CPU内部RTC定时器,会在上电的时候调用mktime函数,算出距离1970年的秒数。在mktime.c中实现。long kernel_mktime(struct tm * tm),tm是由硬件RTC(CMOS)传入的,计算完了会保存在全局变量startup_time(sched.c)中,被系统滴答所用jiffies,

jiffies 系统滴答

10ms 10ms进定时器中断,中断函数timer_interrupt中jiffies自加,system_call.s中有代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_timer_interrupt:
push %ds # save ds,es and put kernel data space
push %es # into them. %fs is used by _system_call
push %fs
pushl %edx # we save %eax,%ecx,%edx as gcc doesn't
pushl %ecx # save those across function calls. %ebx
pushl %ebx # is saved as we use that in ret_sys_call
pushl %eax
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
incl _jiffies
movb $0x20,%al # EOI to interrupt controller #1
outb %al,$0x20
movl CS(%esp),%eax
andl $3,%eax # %eax is CPL (0 or 3, 0=supervisor)
pushl %eax
call _do_timer # 'do_timer(long CPL)' does everything from
addl $4,%esp # task switching to accounting ...
jmp ret_from_sys_call

然后call _do_timer ,在sched.c中定义:
10:13如果是用户进程,就utime加一,内核进程stime加一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void do_timer(long cpl)
{
extern int beepcount;
extern void sysbeepstop(void);

if (beepcount)
if (!--beepcount)
sysbeepstop();

if (cpl) # 表示被中断的进程是内核进程还是用户进程 1 用户进程 0 内核进程
current->utime++; # current 就是当前进程task_struct结构,utime,用户程序的运行时间
else
current->stime++;

if (next_timer) { #时间链表的指针 如果有这个定时器,就去执行中断服务函数
next_timer->jiffies--;
while (next_timer && next_timer->jiffies <= 0) {
void (*fn)(void);

fn = next_timer->fn;
next_timer->fn = NULL;
next_timer = next_timer->next;
(fn)();
}
}
if (current_DOR & 0xf0)
do_floppy_timer();#软盘的
if ((--current->counter)>0) return;#当前进程的counter
current->counter=0;
if (!cpl) return;#内核 return
schedule();
}

next_timer是嫁接于jiffies变量的所有定时器的链表、各种时间都会用到jiffies变量,这个变量就像是一个时间轴
current->counter进程的时间片:标志这个进程还能运行多长时间

task_struct 进程

task_struct[]进程链表 ,

counter—-在哪里用,在进程调度的时候,检索task_struct[]链表,找时间片最大的进程,进行调用,直到时间片为0,退出。之后再进行新一轮的调度

counter—–在哪里被设置:当全部的task_struct[](task[])里的进程counter为0,进行新一轮的时间片分配,优先级分配 ,sche.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
 *  'schedule()' is the scheduler function. This is GOOD CODE! There
 * probably won't be any reason to change this, as it should work well
 * in all circumstances (ie gives IO-bound processes good response etc).
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */
void schedule(void)
{
int i,next,c;
struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}

/* this is the scheduler proper: */

while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;#优先级分配时间片,加到了上面
}
switch_to(next);
}

优先级时间片轮转调度算法
时间片的计算,为上一次时间的一半加上优先级:counter = counter/2 + priority

2进程创建

图片

每一个进程的组合图,描述符就是指针,通过描述符找到代码段,数据段。

TSS段

TSS结构在Sched.h中,主要是一些x86的一些通用寄存器,最后的i387是协处理器的结构体,进程运行时候的一些CPU相关的结果保存在tss结构中,进程的一些状态标志,段属性,页属性等等,通用寄存器值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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; 在cpu中只有一个这个寄存器
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;
};

比如进程1在运行,他的数据在cpu的相关寄存器中,这时候进程2插进来了,内核会把相关的cpu寄存器值等参数保存到进程1的TSS段中,把进程2的TSS相关数据恢复到cpu中
图片
描述符只是地址不放数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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;
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
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();#调度初始化 清空task链表 ,设置寄存器, 中断
buffer_init(buffer_memory_end);#缓冲区初始化
hd_init();#硬盘初始化,硬盘驱动
floppy_init();#软盘初始化,软盘驱动
sti();
move_to_user_mode(); 之前都是在内核态
if (!fork()) { /* we count on this going ok */
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();
}

在main中首先是把内核代码拷贝到内存中,然后初始化,最后,Fork函数创建0号进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void init(void)
{
int pid,i;

setup((void *) &drive_info);
(void) open("/dev/tty0",O_RDWR,0);打开标准输入控制台 0是句柄
(void) dup(0);打开标准输出控制台
(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号进程创建了 , fork返回0创建成
close(0);
if (open("/etc/rc",O_RDONLY,0))
_exit(1);
execve("/bin/sh",argv_rc,envp_rc);
_exit(2);
}
if (pid>0) 返回了父进程
while (pid != wait(&i)) 等待父进程退出 把参数放到i的内存中去
/* nothing */;
while (1) {
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
if (!pid) { 创建成功了
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));
}
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() */
}

在0号进程中, 打开标准输入输出,错误的句柄。创建1号进程,如果创建成功,在1号进程中打开/etc/rc,执行shell程序/bin/sh
0号进程,不可能结束,他会在没有其他进程调用的时候调用,只会执行for(;;) pause();也就是说,在空闲的时候回执行0号进程暂停

进程创建:

oop思想

fork函数

1 在task链表中找一个空位存在

2 创建一个task_struct

3 设置task _struct

进程的创建是系统调用,在system_call.s中

1
2
3
4
5
6
7
8
9
10
11
12
13
.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

首先,给要创建的进程分配一个进程号,find_empty_process.然后,通用寄存器入栈。复制当前进程或者0号进程。
0号进程复制,就是task_struct的复制,栈堆的复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
 *  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)
{
struct task_struct *p;
int i;
struct file *f;

p = (struct task_struct *) get_free_page(); 分配内存给新的进程malloc
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)) {代码段和数据段的拷贝和设置
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));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING; /* do this last, just in case */
return last_pid;
}

copy_process主要工作如下
1 分配新进程的结构体的内存

struct task_struct *p;

p = (struct task_struct * ) get_free_page();

  //

p = (struct task_struct * ) malloc(sizeof(task_struct));

2 放入进程链表中

3 设置task_struct

4 协处理器

5 代码段和数据段的拷贝和设置

6 子进程继承文件打开属性

7 子进程继承其他属性

8 设置tss和ldt段,结合拷贝过来的堆栈(空),组装成一个进程

1
2
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));

9 设置进程为可运行状态
10 返回进程号

3调度

分时调度

由系统调用完成

调度指的是从一些进程中选择出一个将要执行的进程

内核态

不可抢占,不能做进程的切换

用户态

可抢占

进程的状态

调度的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
 *  'schedule()' is the scheduler function. This is GOOD CODE! There
 * probably won't be any reason to change this, as it should work well
 * in all circumstances (ie gives IO-bound processes good response etc).
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */
void schedule(void)
{
int i,next,c;
struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
(*p)->signal |= (1<<(SIGALRM-1));设置警告信号
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}

/* this is the scheduler proper: */

while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
switch_to(next);
}

主要通过schedule函数来实现调度,具体工作内容如下:
总结:选出一个调度的进程

具体:

/* check alarm, wake up any interruptible tasks that have got a signal */

遍历所有的进程,找出满足条件的:(设置了alarm,并且到点了),给这个进程设置警告信号,清空他的alarm;找出满足条件的进程:(有信号,并且是非阻塞信号,并且进程状态是TASK_INTERRUPTIBLE),给这些进程的状态设置为TASK_RUNNING;十三点sdsdssdf

/* this is the scheduler proper: */

找出counter最大的进程,如果进程的时间片都用完了,重新分配时间片counter=counter/2+

priority

进程的切换由switch_to函数完成,具体工作内容如下:

总结:切换进程

详细:1将需要求换的进程赋值给当前进程指针

加东西

sleep_on函数,当某个进程想要访问cpu资源的时候,cpu资源又被占用了,就会调用sleepon

函数,把进程休眠、形成了一个等待队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;

if (!p)
return;
if (current == &(init_task.task)) 当前是0号进程
panic("task[0] trying to sleep");
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp)
tmp->state=0;
}

应用:省电

辅助函数:

show_task打印

math_state_restore协处理器

进程状态 :

4退出

5通信