虚拟地址空间
- 内核在进程和物理内存之间提供了一层抽象层,这一层是
虚拟地址空间
,进程的内存视图是通过虚拟地址空间
构建的。虚拟地址空间为进程提供了假象(虚拟内存
),每个进程以为自己在执行过程中独占了整个内存,虚拟内存
是由内核的内存管理和CPU的MMU
协调实现的。每个进程都会有32位或者64位的地址空间,这个地址空间范围是由系统结构所限定的。每个进程通过MMU
加载到虚拟地址空间中,任何进程尝试方位其边界之外的地址空间都会触发硬件故障,从而使内存管理器能够检测和终止违反的进程(Segment Fault
错误)。
用户空间和内核空间
- Linux操作系统将整个内存分割为两个逻辑分区:用户空间和内核空间。这种分开设计确保所有分配有地址空间的进程都映射到内存的用户空间,而内核数据和服务都在内核空间中运行。内核通过与硬件协调实现这种保护。当应用程序发起
syscall(系统调用)
,CPU切换为特权模式,从用户空间切换到内核空间,所请求的服务完成后,内核使用了另外一组CPU指令将进程从内核空间切换到用户空间。
上下文切换
- 当一个进程通过系统调用请求内核服务时,内核将代表调用进程来执行,此时内核被认为是进程上下文中执行,
上下文切换
是指进程存储一个进程或者线程的状态,然后完成某种服务后恢复和唤醒进程的执行点,这个上下文切换
是由时间开销的,保存旧进程的状态(拷贝当前进程涉及到的寄存器的状态到PCB
),加载已经保存进程状态的新进程(从进程的PCB
中拷贝到寄存器中),上下文切换
时间时依赖于硬件。
进程描述符
- 进程从创建到退出过程都是有内核的进程管理子系统进行管理。一个进程在内存中还被分配一个称为描述符的数据结构,内核用进程描述符来识别、管理和调度进程。内核定义了
struct task_struct
类型描述一个进程的实例,包括进程所有的属性。标识的详细信息、资源分配的情况。
- 一个进程从产生到退出一直出于不同的状态中,这个也叫做进程状态,它们定义了进程当前不同的状态。
TASK_RUNNING
是进程正在执行或在调度器运行队列中的抢占CPU;TASK_INTERRUPTIBLE
是进程出于可中断的等待状态,它依旧出于等待状态知道所有等待条件为真,例如互斥锁调用、IO准备等。TASK_KILLABLE
这个于TASK_INTERRUPTIBLE
类似,不同的TASK_KILLABLE
只会发生在致命信号上。TASK_UNINTERRUPTIBLE
是进程处于不可中断的等待状态,进程处于这种状态不会被产生的信号唤醒,只有它正在等待的事件发生时候进程才转换为TASK_RUNNING
状态。TASK_STOPPED
是进程已经收到终止的信号,进程终止;TASK_TRACED
是一个进程可能被一个调试器进行检查,这时候可认为进程出于这个状态;TASK_ZOMBIE
是进程碑额终止,但是进程的资源尚未回收;EXIT_DEAD
是父进程使用wait方法收集子进程的退出状态后,子进程终止,并且释放它的所有资源.
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
// 保存处理器特定的状态信息,并且它是任务结构体的关键信息
struct thread_info thread_info;
#endif
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
/*
* This begins the randomizable portion of task_struct. Only
* scheduling-critical items should be added above here.
*/
randomized_struct_fields_start
void *stack;
atomic_t usage;
// 进程相应的各种属性,字段中每一位对应于一个进程生命周期中的各个字段。
unsigned int flags;
unsigned int ptrace;
#ifdef CONFIG_SMP
struct llist_node wake_entry;
int on_cpu;
#ifdef CONFIG_THREAD_INFO_IN_TASK
/* Current CPU: */
unsigned int cpu;
#endif
// prio和static_prio,prio帮助确定调度进程的优先级,如果进程被分配了实时调度策略,则噶字段保存静态优先级,范围是1~99.对于正常进程,这个字段保存了由nice的值得来的动态优先级
int prio;
int static_prio;
int normal_prio;
// 指定实时调度策略的进程优先级
unsigned int rt_priority;
const struct sched_class *sched_class;
// se/rt/dk 是每个任务属于的调度实体,因为调度是在每个实体级别的完成的。se用于所有正常进程;rt是用于实时进程;dl用于截止期进程。
struct sched_entity se;
struct sched_rt_entity rt;
#ifdef CONFIG_CGROUP_SCHED
struct task_group *sched_task_group;
#endif
struct sched_dl_entity dl;
#endif
// 该字段保存和进程调度策略相关信息
unsigned int policy;
int nr_cpus_allowed;
// 该字段制定了进程的CPU掩码,在多处理器系统中,进程允许在哪个CPU上进行调度
cpumask_t cpus_allowed;
// pid是进程的唯一标识符,它是pid_t类型,可以通过/proc/sys/kernel/pid_max接口制定默认的最大值,默认最大值是2的32次方大约400万
pid_t pid;
// 保存线程组id,假设创建一个新进程,它的pid和tgid是相同的。当进程产生一个新线程时候,新的子进程将获得唯一的pid
pid_t tgid;
// 对于正常进程,real_parent和parent两个指针指向同一个task_struct,它们区别仅在于使用posix线程实现的多线程进程,对于这种情况,real_parent指向父进程的结构,parent指向收到SIGCHLD信号的进程任务的结构体
struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
// 指向子任务结构体链表的指针
struct list_head children;
// 指向兄弟任务结构体链表的指针
struct list_head sibling;
// 指向进程组组长的结构体
struct task_struct *group_leader;
// 存储了文件系统信息
struct fs_struct *fs;
// 文件描述符保存了一些指针,这些指针指向进程为了执行各个操作而打开的所有文件
struct files_struct *files;
/* Namespaces: */
struct nsproxy *nsproxy;
/* CPU-specific state of this task: */
struct thread_struct thread;
};