深入理解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内核设计与实现>中第三章讲解了进程管理,在关于进程和线程的概 ...
随机推荐
- SpringBoot项目中,Redis的初次使用
1.引入Redis依赖包,在application.yml中配置redis <dependency> <groupId>org.springframework.boot< ...
- 使用webuploader实现大文件传输
这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得 ...
- NetworkX系列教程(6)-对graph进行操作
小书匠Graph图论 graph生成后,除了有查看操作,还有移除等操作,还有其他更多操作,具体可以看这里.下面将比较graph操作前后的不同. 目录: 7.对图进行操作 7.1移除某些节点和边 7.2 ...
- Gluon学习02-使用GPU
小书匠kindle 目录,方便快速定位: 1.安装cuda与cudnn 2.安装mxnet-gpu 本机环境介绍: 系统:Linuxmint Python版本:Python3 1.安装cuda与cud ...
- 关于解决ruby源码安装 gem install报错问题
因做redis集群需要安装ruby,源码安装过后gem install redis安装redis接口报错 解决方案: 确保主机安装zlib,没有安装执行 yum -y install zlib zli ...
- java 封装返回结果实体类 返回结果以及错误信息
public class ResponseMessage { private final static String STATUS_OK = "0"; private final ...
- Apache Flink - 内存管理
JVM: JAVA本身提供了垃圾回收机制来实现内存管理 现今的GC(如Java和.NET)使用分代收集(generation collection),依照对象存活时间的长短使用不同的垃圾收集算法,以达 ...
- deepin 删除文件后目录不刷新解决方案
调整最大文件监控数量 sudo vim /etc/sysctl.conf 添加参数 fs.inotify.max_user_watches = 运行使配置生效 sudo /sbin/sysctl -p ...
- java定时案例
好久没写笔记了,变懒了! java定时运行的三个案例: 一, 通过sleep方法来达到定时任务的效果 public class testTime { public static void main(S ...
- python人生如初见之初见yield
今天学习爬虫Scrapy框架搭建的时候,了解了yield的用法.了解一个东西,无外乎 WHAT? HOW? WHY? WHAT yield英文意思是屈服,退位,放弃.额...其实它是Python中的一 ...