Linux内核原理-进程入门
来源:互联网 发布:外文电子图书数据库有 编辑:程序博客网 时间:2024/04/27 03:12
进程
进程是处于执行期的程序,进程不仅仅是一段可执行程序的代码,通常进程还包括其他资源,比如打开的文件,挂起的信号,内核内部的数据结构,处理器状态,内存地址空间,或多个执行线程,存放全局变量的数据段等。线程是进程中活动的对象,每个线程都拥有一个独立的程序计数器,进程炸,和一组进程寄存器。内核调度的对象是线程,而不是进程。传统的Unix系统中一个进程只包含一个线程。对于linux而言,线程只不过是一种特殊的进程罢了。在linux系统中通过fork()来复制现有进程的资源和创建一个新的进程。随后调用exec这组函数创建自己的地址空间最后载入要运行的程序,在linux中fork其实是clone系统调用来实现。程序执行完毕后使用exit退出执行,父进程调用wait或waitpid来等待子进程结束,并回收其资源。
进程描述符及任务结构
在linux内核中,使用了一个task_struct结构体来描述进程信息,并且通过双向循环链表将所有的进程连接起来。一个task_struct描述了进程打开文件信息,状态信息,地址空间信息,挂起的信号,进程的状态等等。
进程之间的关联如下图:
进程描述符的分配
在linux中进程描述符task_struct是通过slab分配器来进行分配的,这样可以达到对象复用和缓存着色的效果。task_struct这个结构体本身就很大,这个结构体在内核中会不断的进行分配和释放,这样很不高效,使用了salb分配器后只需要进行分配释放是没有开销的,slab释放了task_struct只是放在其对象池中,并没有真正释放。然后在2.6内核之前,task_struct的分配和释放不是通过slab分配器,而是直接放在内核栈的栈底,这样就可以通过栈顶指针很快的计算出task_struct的地址,因为在内核中task_struct是最经常要访问的数据结构,所以需要一个机制很快的获取当前进程的task_struct结构体的地址,在x86这种体系结构中,因为寄存器的缺乏没有独立的寄存器用来存放task_struct的地址,所以就通过这种方式来快速计算,在PowerPC系列的计算机中则是使用一个寄存器来存放task_struct的地址。在2.6内核后为了避免动态内存分配和释放所带来的资源消耗,采用了slab分配器来管理task_struct结构体,但是为了快速计算task_struct的地址,linux在栈低存放了一个thread_info结构 在这个结构中第一个元素就是当前的task_struct地址,这样就可以很快的计算出task_strcut的地址,并且task_struct交由slab分配器来管理,可以避免资源消耗。
获得thread_info地址的方法
struct thread_info { unsigned long flags; /* low level flags */ int preempt_count; /* 0 => preemptable, <0 => BUG */ struct task_struct *task; /* main task structure */ mm_segment_t addr_limit; /* thread address space */ struct exec_domain *exec_domain;/* execution domain */ __u32 cpu; /* current CPU */ unsigned long thr_ptr; /* TLS ptr */ struct restart_block restart_block;};static inline __attribute_const__ struct thread_info *current_thread_info(void){ register unsigned long sp asm("sp"); //获取sp栈顶指针的地址 return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); //计算thread_info的地址 // 注:THREAD_SIZE是内核堆栈的大小,不同体系结构这个值不同 // 假设这里是8K,那么只要将堆栈的栈顶地址后13位屏蔽掉就可以得到thread_info的地址 //得到thread_info就可以得到当前进程的task_struct结构体 //current_thread_info()->task 就是当前进程的task_struct到此为止,我相信大家对怎么快速获取task_struct的细节应该有所理解了。全局还有一个current宏,表示的就是当前进程的task_struct地址#define get_current() (current_thread_info()->task)#define current get_current()
下面是进程内核栈的图示:
进程的状态
在task_struct中的state字段表示的就是进程的状态
struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ void *stack; atomic_t usage; unsigned int flags; /* per process flags, defined below */ unsigned int ptrace; .......在这里使用了volatile来修饰state变量,主要是因为state需要实时从内存访问最新的值,而不是读取寄存器中的这个值默认gcc会对其进行优化,将state的值读入寄存器,每次访问寄存器中值,但是在多线程的环境中,state的值随时可能会改变,所以加入了volatile来修饰。从注释可以看出 state有三个值,-1 0 或者大于0,分别表示不可运行,就绪,停止,对于停止又细分出很多种,可以看下内核中对进程状态的定义:#define TASK_RUNNING 0 //正在运行或可以被执行#define TASK_INTERRUPTIBLE 1 //可中断的,进程正在睡眠(也就是被阻塞了)等待某些条件达成#define TASK_UNINTERRUPTIBLE 2 //不可中断,不被信号唤醒#define __TASK_STOPPED 4 //进程停止运行#define __TASK_TRACED 8//被其他进程追踪
进程状态转化如下图:
内核中一些操作进程状态的函数:
#define __set_current_state(state_value) \ do { current->state = (state_value); } while (0)#define set_current_state(state_value) \ set_mb(current->state, (state_value))#define barrier() __asm__ __volatile__("": : :"memory")#define mb() barrier()#define set_mb(var, value) do { var = value; mb(); } while (0)提供了两个宏用来设置当前进程的状态,_set_curent_state这个应该很好理解而set_current_state则是带有内存屏障的,常用于多处理和多线程的情况
注:对于内存屏障可以参考这篇博文,更多关于内存屏障的知识可以谷歌
进程的上下文
所谓的进程上下文其实就是进程的当前状态,一组状态数据而已,在进程切换的时候,保存当前进程的数据,载入要切换进程的数据,这样就可以实现进程的切换。状态数据通常包含CPU所有寄存器的值,进程的状态,堆栈中的内容等等。
需要传入运行队列,切换之前的进程task_struct和切换之后的进程task_structstatic inline void context_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next)在这个函数中做了一下几件事:1.开始进行两个进程的地址空间切换需要注意的是如果是当前是线程,那么active_mm指向其所属进程的地址空间mm为NULL,如果进程则active_mm是NULLmm指向其地址空间 mm = next->mm; oldmm = prev->active_mm; if (!mm) { //next是线程。则直接使用prev的地址空间 next->active_mm = oldmm; //那么地址空间指向prev的地址空间即可 atomic_inc(&oldmm->mm_count); //因为内核线程不访问用户态地址空间,所以这里惰性TLB模式 enter_lazy_tlb(oldmm, next); } else switch_mm(oldmm, mm, next); //next是进程,进行页表的切换 if (!prev->mm) { //如果prev是内核线程,那么切换后,设置运行队列的地址空间指向oldmm prev->active_mm = NULL; rq->prev_mm = oldmm; }2.寄存器和堆栈的切换switch_to(prev, next, prev);
相关知识点补充:
页表有两种,内核页表和进程页表,每个进程都有自己的页表,放在task_struct.pgd中,内核页表放在init_mm.pgd在保护模式下,从硬件角度看,其运行的基本对象为”进程”(或线程,而寻址则依赖于”进程页表”,在进程调度而进行上下文切换时,会进行页表的切换:即将新进程的pgd(页目录)加载到CR3寄存器中。内核页表中的内容为所有进程共享,每个进程都有自己的”进程页表”,”进程页表”中映射的线性地址包括两部分:用户态,内核态.其中内核态地址对应的相关页表项,对于所有进程来说都是相同的(因为内核空间对所有进程来说都是共享的),而这部分页表内容其实就来源于”内核页表”,即每个进程的”进程页表”中内核态地址相关的页表项都是“内核页表”的一个拷贝。
- Linux内核原理-进程入门
- Linux内核守护进程原理
- Linux内核 进程调度原理
- linux内核实时进程的调度原理
- linux内核普通进程CFS调度原理
- Linux内核switch_to宏实现进程切换的原理
- 《Linux内核分析》(二)——从一个简单Linux内核分析进程切换原理
- 《Linux内核分析》(二)——从一个简单Linux内核分析进程切换原理
- linux内核进程
- Linux内核进程切换
- Linux进程内核栈
- linux 内核进程 线程
- Linux内核进程调度
- Linux进程内核栈
- Linux内核-进程
- Linux内核-进程退出
- Linux内核-进程wait
- Linux内核-进程退出
- Java web 中实现简单的文件上传与下载——学习笔记
- 9.3 保护视图级别的元素
- C语言实现链表之单向链表(二)结点内存申请及数据初始化
- C++类内存分布
- VHDL学习记录
- Linux内核原理-进程入门
- 孩子身高居然长这么快?
- HDU 1176 免费馅饼(DP)
- JavaScript命名空间
- Mybatis-解决字段名与实体类属性名不相同的冲突
- android 在eclipse中把局部变量变成成员变量前自动加m
- [UVA 11517] Exact Change (背包DP)
- log4j.properties 配置详解
- Springmvc返回json