系统的进程管理:
-
系统的进程的运转方式
系统时间:(jiffies 系统滴答)
CPU内部有一个RTC(定时器),会在上电的时候调用mktime函数算出从1970年的1月1日0时开始到当前开机点所过的秒数 给mktime传递的参数是从rtc芯片中读出,转化为时间存入全局变量中,并且会为jiffies所用
1个jiffies 系统滴答是10ms,每隔10ms会引发一个定时器中断,这个中断中进行以下操作:
- 执行timer_interrupt进行了jiffies的自加
- call do_timer
调用do_timer函数:
if(cpl) //CPL变量是内核中用来指示被中断程序的特权 0表示内核进程 1表示被中断用户进程 current->utime++; //utime用户程序的运行时间 else current->stime++; //stime内核程序的运行时间 next_timer :是嫁接于jiffies变量的所有定时器的事件链表,这个结构体中的jiffies是指当前时间距离要执行时间的差值,小于等于0时执行这个结构体中的函数指针fn task_struct 中的current 是指当前进程 task_struct[] task:进程向量表 current->counter:进程的时间片 counter在哪里用? 进程的调度就是task_struct[]进程链表的检索,找时间片最大的那个进程对象,然后进行调用,直到时间片为0,退出 之后再进行新一轮的调用 counter在哪里被设置? 当全部的task_struct[] task[] 所有的进程的counter都为0,就进行新一轮的时间片分配 (*p)->counter = ((*p)->counter >>1 ) + (*p)->priority; 优先级时间片轮转调度算法
-
task_struct结构体介绍:内核进程就是一个结构体的链表task_struct task[]
-
每个进程中有
-
LDT 局部描述符(描述符都是指针) 包含:数据段 代码段
-
TSS 进程的状态描述符 :在进程运行的过程中,CPU需要知道的进程的状态标识,以及
-
堆栈
每个进程内存分布如下
高地址
栈
堆
TSS
数据段
代码段
低地址
-
- 系统级别 GDT **全局描述符表** (每个CPU都有一个GDT)
-
如何进行创建一个新的进程
Linux在初始化过程中会进行0号进程的创建,用fork方法
之前的这些初始化操作都是在内核态完成
sched_init() 做了什么事情?
内核态 :不可以抢占
用户态 :可以抢占
move_to_user_mode()之后会执行fork() 创建0号用户空间进程,0号进程是所有进程的父进程,然后执行用户态的init()函数,对用户空间进行初始化
0号进程:
打开标准输入 输出 错误的控制台句柄
创建1号进程,如果创建成功(fork返回为0),在1号进程中打开"/etc/rc"配置文件,并执行shell程序"/bin/sh"
在调用pid = fork()时
子程序的pid返回为零
而父程序返回的pid值为生成子程序在系统中的真实pid值而非父程序本身在系统中的真实pid值
0号进程退出init()函数,不会结束,他会在没有其他进程调用的时候调用,只会执行for(;;)pause()暂停
进程创建:
在task链表中找一个进程空位存放当前的进程
创建一个task_struct
设置task_struct
进程的创建就是对0号进程或者当前进程的复制,task_struct task[0]结构体的复制,并进行栈堆的复制
进程的创建就是一个系统调用 sys_fork
给当前要创建的进程分配一个进程号,find_empty_process
创建一个子进程的task_struct结构体
将当前的子进程放到整体进程链表中
设置创建好的task_struct结构体
- 如果当前进程使用了协处理器,那就设置当前创建进程的协处理
- copy_mem 进行老进程向新进程代码段 数据段(LDT)的拷贝
- 如果老进程打开了某个文件,那么子进程也同样打开了这个文件,所以将文件的计数++
- 设置进程的两个段,并结合刚才拷贝过来的数据 组装成一个进程
- 给进程的状态标志设置成一个可运行的状态
- 返回进程的pid
进程调度
void schedule(void) 进程调度函数
switch_to(next) 进程切换函数
进程的状态:
- 运行状态 可以被运行 就绪状态 进程切换只能在运行状态
- 可中断睡眠状态 可以被信号中断(kill -8 -6 SIGCHLD 等 ) 使其变成RUNING 对应poll机制 wait/waitpid
- 不可中断睡眠状态 只能被wakeup所唤醒 使其编程RUNING 当多个进程等待资源的时候会进入sleep,对应这个状态
- 暂停状态 收到SIGSTOP SIGTSTP SIGTTIN进入暂停状态
- **僵尸状态 ** 进程停止运行了,但是父进程还没有将其清空 waitpid
进程切换
- 将需要切换的进程赋值给当前进程指针 current
- 进行进程的上下文切换
- 上下文:程序运行时 CPU的特殊寄存器 通用寄存器 (TSS)等信息 + 当前堆栈中的信息
sleep_on() :可以多个进程组成一个睡眠链表 ,例如某个资源是互斥的,当资源被某一个进程占用时,其他进程便无法访问此资源。那么若某进程无法得到资源,其使用sleep_on(&res_wait) 在资源的wait队列上睡眠,注意到这里的wait只是指针变量
《Linux内核设计的艺术》》
- 进程的退出
进程销毁函数 exit() --> 一个系统调用 do_exit()
首先该函数会释放进程的代码段和数据段占用的内存,清空
关闭进程打开的所有文件,对当前的目录和i节点进行同步(文件操作)
如果当前要销毁的进程有子进程,那么就让1号进程作为新的父进程(init进程)
如果当前进程是一个会话头进程,则会终止会话中的所有进程
改变当前进程的运行状态,变成TASK_ZOMBIE僵死状态,并且向其父进程发送SIGCHLD信号
release(struct task_struct * p )
完成清空任务描述表中的对应进程表项,释放对应的内存页(代码段 数据段 堆栈)
static inline int send_sig(long sig,struct task_struct *p,int priv)
给指定的进程p发送对应的sig,task结构体中一个信号列表(*p)->signal
static void kill_session(void)
终止会话,终止当前进程的会话,给其发送SIGHUP信号
int sys_kill(int pid,int sig)
kill 不是杀死的意思,向对应的进程号或者进程组号发送任何信号
pid >0 给对应的pid发送sig
pid =0 给当前进程的进程组发送sig
pid =-1 给任何进程发送
pid <-1 给进程组号为-pid的进程组发送信号
static void tell_father(int pid)
向父进程发送SIGCHLD信号
int do_exit(long code)
ldt[1] ldt段 ldt[2] tss段
- 父进程在运行子进程的时候 一般都会运行wait waitpid这两个函数(父进程等待某个子进程终止的),当父进程收到SIGCHLD信号时,父进程会终止僵死状态的子进程
- 首先父进程会把子进程的运行时间累加到自己的进程变量中
- 把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽
syscall_xxx 或者do_xxx 一般都是一个中断调用的函数