深入理解linux内核-进程和程序
进程描述符task_struct
task_struct
{
//进程基本信息
pid 进程id号
tgid 线程组id号,与线程组领头线程pid号相同 getpid()返回该值
tasks init_struct链接所有task_struct结构
run_list; //当前进程所处的运行链表
array 指向与进程相关的prio_array_t结构
real_parent当前进程的父进程,没有的话将会变成进程1(init)的描述符
parent 被执行跟踪时的跟踪父进程(ptrace)
children 链接所有子进程
sibling 兄弟进程链表
group_loader 进程组领头进程的描述符指针
signal->pgrp 进程组领头进程的PID
signal->session 登录会话领头进程的pid
ptrace_children 所有被本进程跟踪的子进程链表
ptrace_list 指向所跟踪进程其实际父进程的链表的前一个和下一个元素????
pid pids[4]; //四个结构用于查找指定 进程id、进程组id、线程组id、会话id。每个结构用于分别保存相同散列值的相同或不同pid列表
//资源限制
signal->rlim[rLIMIT_CPU...].rlim_cur; //当前资源限制
signal->rlim[rLIMIT_CPU...].rlim_max; //普通用户最大权限
//进程切换
thread_struct thread;//保存进程切换时内核硬件上下文
进程优先级
进程运行状态(可运行、可中断等待、不可中断等待、暂停、跟踪、僵死、僵死撤销)
//进程地址空间 mm_struct
//当前目录 fs_struct
//进程访问文件 files_struct
//所接收的信号 signal_struct
//相关的tty tty_struct
}
不可中断状态:驱动在进行一些不能被中断的操作,因此进程处于该状态。
暂停状态:进程收到SIGSTOP SIGTSTP SIGTTIN SIGTTOU进入暂停状态
跟踪状态:进程执行处于被debugger程序暂停的状态
僵死状态:进程执行终止,等待返回进程信息时。
僵死撤销状态:为防止多线程同时等待进程终止时的信息,信息被获取后的状态
线程描述符thread_info
thread_info与内核态堆栈放在一起占用一个(4K)或两个页(8k)(用户态堆栈不在这里)
内核态时可以通过堆栈寄存器获取当前thread_info结构的地址。
thread_info
{
cpu//当前CPU
}
prio_array_t
{
int nr_active; 链表中进程描述符的数量
unsigned long[5] bitmap; 当某个优先权链表不为空时对应位为1
struct list_head[140] queue; 140个优先权队列
}
将不同优先权的进程排入不同链表
进程组和线程组的概念
进程组:表示一个作业(job),例如 ls|sort|more三个进程处于一个进程组
进程组:进程描述符中signal->pgrp相同的所有进程处于一个进程组
线程组:进程描述符中tgid相同的所有进程处于一个线程组。
getpid() kill() _exit()对线程组整体起作用。
线程组所有成员死亡后才会产生一个信号通知线程组的领头进程的父进程
通过pid快速查找进程
为了能快速找到对应进程描述符,内核引入四个散列表(保存在pid_hash数组)
进程pid散列表
线程组tgid散列表
进程组pgrp散列表
会话session散列表
pid
{
int nr;//对应类型的pid数值
struct hlist_node pid_chain;//相同散列值但pid不同的链表 链接pid结构
struct list_head pid_list;//相同散列值相同pid的进程双向链表 链接pid结构
}
对于每一个散列表,进程描述符有一个pid数据结构对应
进程的组织
运行状态有对应链表
停止、僵死、僵死撤销 状态没有对应的链表
可中断等待和不可中断等待有多种独立的等待队列
等待队列
struct __wait_queue_head
{
spinlock_t lock; //自旋锁
struct list_head task_list;//非互斥进程从第一个位置放,互斥进程放在最后一个
}
wait_queue_t
{
unsigned int flags;//1表示等待队列是互斥资源的访问,0等待队列是非互斥资源
struct task_struct *task; //对应的进程描述符
wait_queue_func_t func;//表示等待队列的唤醒函数
struct list_head list;//所有排入等待队列的wait_queue_t
}
sleep_on类函数在一些条件不能使用:必须测试条件并且当条件还没得到验证时又紧接着让进程去睡眠????
sleep_on(wait_queue_head_t) (非互斥:一旦条件满足则所有非互斥等待都会唤醒!!!)
interruptible_sleep_on() (非互斥)
sleep_on_timeout() (非互斥)
interruptible_sleep_on_timeout() (非互斥)
prepare_to_wait() //(非互斥)需要自己调用schedule()或者schedule_timeout()
prepare_to_wait_exclusive() //(互斥)需要自己调用schedule()或者schedule_timeout()
finish_wait()
唤醒函数
wake_up 不带nr和all则只唤醒一个互斥进程
wake_up_nr nr代表唤醒互斥进程的数量
wake_up_all all代表唤醒所有互斥进程
wake_up_interruptible interruptible代表只唤醒可中断睡眠,不带该后缀唤醒两种睡眠
wake_up_interruptible_nr
wake_up_interruptible_all
wake_up_interruptible_sync sync代表如果唤醒 进程优先级更高**不会**立即执行该高优先级进程
wake_up_locked 当等待队列中的自旋锁已经被持有时使用
进程资源限制
地址空间最大数 RLIMIT_AS
内存信息转储空间大小 RLIMIT_CORE
进程使用CPU的最长时间 RLIMIT_CPU
堆大小的最大值 RLIMIT_DATA
文件大小最大值 RLIMIT_FSIZE
文件锁数量最大值 RLIMIT_LOCKS
非交换内存的最大值 RLIMIT_MEMLOCK
消息队列中的最大字节数 RLIMIT_MSGQUEUE
打开文件描述符的最大数 RLIMIT_NOFILE
用户拥有进程最大数 RLIMIT_NPROC
进程拥有页框最大数 RLIMIT_RSS
进程挂起信号的最大数 RLIMIT_SIGPENDING
栈大小的最大数 RLIMIT_STACK
进程切换
thread_struct
{
eip//进程恢复执行后需要执行的首地址(保存+加载)
esp//进程切换时内核态指针(保存+加载)
esp0//内核态初始指针(仅加载)
tls_array[3];//线程局部存储段(仅加载)
fs gs;段寄存器(保存+加载)
debugreg;调试寄存器dr0-dr3 dr6-dr7(仅加载)
io_bitmap_ptr//表示IO权限位图是否有数据
}
硬件上下文:进程恢复执行前必须装入寄存器的一组数据
硬件上下文的一部分放在TSS段中,剩余部分在内核态堆栈中
可执行上下文:进程执行时需要的所有信息
可执行上下文包含硬件上下文
进程切换只发生在内核态,在切换之前,用户态进程使用的所有寄存器内容已保存在内核态堆栈上(包括用户态堆栈信息)
任务状态段(TSS):保存内核态堆栈地址,检查in out指令执行时是否有IO许可权,linux中每个CPU只有一个TSS段
thread_struct:在任务描述符中在进程切换时保存内核硬件上下文(包含大部分CPU寄存器,不包括eax、ebx这些通用寄存器(这些在内核堆栈中))
switch_to函数执行步骤(主要堆栈切换、执行指针切换)
1.将prev和next分别存入eax和edx防止堆栈切换导致指针变化
2.保存需要保存的寄存器信息eflag和ebp(pushfl ;pushl ebp)
3.将原堆栈指针esp保存在prev的结构中
4.从next中将新堆栈指针写到esp中
5.将原进程恢复后需要执行的地址存入prev中
6.将新进程next的eip压入到新进程的栈中(栈已经切换完成)
7.调用__switch_to,该函数引用eax和edx获得两个进程的其他硬件上下文并进行切换(FPU、MMX、XMM寄存器)
8.恢复堆栈中的寄存器eflag和ebp
9.从eax寄存器拷贝到last变量(本次进程切换被切出的进程描述符地址)
__switch_to函数执行步骤(引用eax和edx获得本次切出和切入进程的进程描述符,栈顶保存了需要恢复执行的地址)
1.__unlazy_fpu()保存切出进程的FPU、MMX和XMM寄存器
2.获得cpu下标(内核栈切换完毕,通过栈指针找到thread_info,内部的cpu字段)
3.将thread.esp0装入对应本地CPU的TSS的esp0字段(用户态切内核态后内核态的堆栈指针)
4.装入线程局部存储段(TLS)thread.tls_array[0-2]
5.保存原线程的fs、gs
6.加载新线程的fs、gs(实际可能会产生无效的段寄存器值异常,并触发修正!!!!)
7.加载6个调试寄存器dr0-dr3 dr6-dr7
8.根据io_bitmap_ptr,懒惰模式设置iobitmap,需要时会产生异常,然后更新。
9.返回值为eax被切出的进程描述符,返回的执行地址为栈顶的标号1地址
考虑进程A的切出和切入
prev、next、last为局部变量(保存在堆栈中)
last用于返回被切出的进程描述符
切出A 切入A
进程A切换为进程B 进程C切换成进程A
A B C A
prev=A => prev=B prev=C => prev=A
next=B => next=other next=A => next=B
eax=prev => last=eax=A eac=prev=C last=eax=C
第一列和第四列能看出,切出是的堆栈状态和切入时相同
创建进程
轻量级进程:共享页表、打开文件表、信号处理。
vfork:使用clone实现,指定SIG_CHLD信号 flag为CLONE_VM CLONE_VFORK,堆栈为当前堆栈
//创建的子进程和父进程共享内存地址空间,父进程在子进程退出或运行一个新的程序前阻塞。clone
fork:使用clone实现,子进程结束给父进程发送SIG_CHLD信号,clone标志为0,堆栈为当前堆栈(依赖写时复制机制可以同时运行)
clone:(需要传递进程函数、参数、新的堆栈、线程局部存储段(TLS)、ptid、ctid)
CLONE_VM 共享页表
CLONE_FS 共享根目录和当前工作目录umask、 不能和CLONE_NEWNS同时设置!!!
CLONE_FILES 共享打开文件
CLONE_SIGHAND 共享信号处理 必须共享内存描述符CLONE_VM!!!
CLONE_PTRACE 共享被调试状态、
CLONE_VFORK VFORK?????
CLONE_PARENT 共享父进程、
CLONE_THREAD 共享线程组 必须共享信号CLONE_SIGHAND!!!
CLONE_NEWNS 新建命名空间 不能和CLONE_FS同时设置!!!
CLONE_SYSVSEM 共享IPC取消信号量操作、
CLONE_SETTLS 新建TLS(局部存储段)
CLONE_PARENT_SETTID 将子进程PID返回给父进程ptid、?????????
CLONE_CHILD_CLEARTID 子进程退出或执行新程序时清除指定变量ctid、?????
CLONE_UNTRACED 禁止内核线程跟踪进程、
CLONE_CHILD_SETTID 将字进程PID返回给子进程ptid、??????????
CLONE_STOPPED 子进程默认停止状态
内核线程
使用dofork实现,CLONE_VM、CLONE_UNTRACED
进程0
从无到有创建的内核线程,初始化内核需要的所有数据结构,每个CPU都会启动一个进程用于空闲时运行,启动进程1
进程1
完成内核初始化,调用execve系统调用装入可执行程序init,变为普通进程,拥有自己的每进程内核数据结构???。
进程克隆过程????暂时不总结
3.5进程终止、进程删除?????暂时不总结
几个进程能并发地执行同一个程序,而同一个进程能顺序的执行几个程序??????
深入理解linux内核-进程和程序的更多相关文章
- 读书笔记之Linux系统编程与深入理解Linux内核
前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...
- 《深入理解Linux内核》 读书笔记
深入理解Linux内核 读书笔记 一.概论 操作系统基本概念 多用户系统 允许多个用户登录系统,不同用户之间的有私有的空间 用户和组 每个用于属于一个组,组的权限和其他人的权限,和拥有者的权限不一样. ...
- 【读书笔记::深入理解linux内核】内存寻址【转】
转自:http://www.cnblogs.com/likeyiyy/p/3837272.html 我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0 ...
- 【读书笔记::深入理解linux内核】内存寻址
我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0xC0000000:这是内核地址空间的地址转换关系. 这句话瞬间让我惊呆了,根据我的CPU的知识,开 ...
- 35、在编译Linux内核中增加程序需要完成以下3项工作
在编译Linux内核中增加程序需要完成以下3项工作: 将编写的源代码拷入Linux内核源代码的相应目录. 在目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项 在目录的Makefile文 ...
- 深入理解Linux内核-进程
1.进程的静态特性 进程:程序执行时的一个实例 进程描述符(task_struct): 进程的基本信息(thread_info).指向内存区描述符的指针(mm_struct).进程相关的tty(tty ...
- 尝试理解Linux容器进程与宿主机共享内核到底是什么意思?
背景 近期接触容器技术时,经常看到各类比较容器与虚拟机区别的文章中会提到:容器是共享宿主机的内核,而虚拟机则是拥有自己独立的内核,所以不可能在Linux上用容器运行windows,但是用虚拟机则可以. ...
- 《深入理解linux内核》第三章 进程
进程的七种状态 在内核源码的 include/linux/sched.h文件中: task_struct的status可表示 #define TASK_RUNNING 0 #define TASK_I ...
- linux内核--进程与线程
http://blog.csdn.net/yusiguyuan/article/details/12154823 在<linux内核设计与实现>中第三章讲解了进程管理,在关于进程和线程的概 ...
随机推荐
- learning scala Function Recursive Tail Call
可以使用scala库,可以从字面上看出是在调用 递归函数: code import scala.util.control.TailCalls._ val arrayDonuts: Array[Stri ...
- Python学习之--基础语法
一.定义 Python 是一种解释型.面向对象.动态数据类型的高级程序设计语言. 二.Python变量的命名规则 1. 变量名只能包含字母.数字和下划线: 2. 变量名不能包含空格: 3. 不要将Py ...
- codevs 4244 平衡树练习
二次联通门 : codevs 4244 平衡树练习 Splay实测指针占用空间大约是数组的3倍, 且时间上也慢了差不多1s 数组版评测记录如下 指针版评测记录如下 以上数据仅限这一个题, 对于 ...
- DEA使用git提交代码时,点了commit之后卡死在performing code analysis部分,或者performing code analysis结束后没有进入下一步操作。
把"Perform code analysis" 和 "Check TODO" 复选框前面的勾去掉就好了. 这个可能是因为所分析的目标文件太大了,造成一直分析不 ...
- thinkphp5/php cors跨域处理
现在做项目,很多都是前后端分离.也就是前段,后端都有自己的域名. 那么前段请求后端接口的时候,就会出现跨域问题.出现跨域的问题,主要 是浏览器的安全策略-同源策略.那么怎么解决跨域问题呢,抛出主角 C ...
- ssh免密码登录与常见问题
免密码登录 cd ~/.ssh ssh-keygen -t rsa #然后敲回车 mv id_rsa.pub master_rsa.pub cat master_rsa.pub >> au ...
- 正则re.complie作用
封装一个原本重复使用的正则表达式 prog = re.compile(pattern) result = prog.match(string)
- JavaScript--自定义事件Event
在开发过程中,js原生事件不足以满意开发需求,需要开发者自定义事件. 一.Event Event()构造函数创建一个新的Event. event = new Event(typeArg,eventIn ...
- Yarn状态机
1 概述 为了增大并发性,Yarn采用事件驱动的并发模型,将各种处理逻辑抽象成事件和调度器,将事件的处理过程用状态机表示.什么是状态机? 如果一个对象,其构成为若干个状态,以及触发这些状态发生相互转移 ...
- linux red hat下安装tomcat的过程
linux下安装tomcat <-----------1.0----------------->:首先需要先去tomcat官网下载linux版本的tomcat的安装包; 我在官网上下载的 ...