Linux进程管理
Linux进程管理(一)进程数据结构
Linux进程管理(二)进程调度
Linux进程管理(三)进程调度之主动调度
Linux进程管理(四)进程调度之抢占式调度
task_struct
结构来表示一个进程,这个结构体保存了进程的所有信息,所以它非常庞大,在讲解Linux内核的进程管理,我们有必要先分析这个
task_struct
中的各项成员
struct list_head tasks;
Linux将所有的 task_struct 串联成一个双向循环链表
pid_t pid;
pid_t tgid;
struct task_struct *group_leader;
pid
,它在内核中是唯一的,在Linux中,我们可以使用 ps -ef
查看所有的进程,其中 PID 就是进程号。pid可以给用户查看指定进程的信息,可以通过pid给指定的进程发送信号通过 getpid 返回的是 tgid,也就是说同一个线程组共享一个 pid
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
在 task_struct 中,定义了这样几个变量,与进程的状态有关
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
int exit_state;
unsigned int flags;
state 和 exit_state 的定义如下
/* Used in tsk->state: */
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* Used in tsk->exit_state: */
#define EXIT_DEAD 16
#define EXIT_ZOMBIE 32
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
state相关
exit_state相关
进程的状态转换如下
flags的某些定义如下
#define PF_EXITING 0x00000004
#define PF_VCPU 0x00000010
#define PF_FORKNOEXEC 0x00000040
进程调度相关的变量如下
//是否在运行队列上
int on_rq;
//优先级
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
//调度器类
const struct sched_class *sched_class;
//调度实体
struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;
//调度策略
unsigned int policy;
//可以使用哪些CPU
int nr_cpus_allowed;
cpumask_t cpus_allowed;
struct sched_info sched_info;
u64 utime;//用户态消耗的CPU时间
u64 stime;//内核态消耗的CPU时间
unsigned long nvcsw;//自愿(voluntary)上下文切换计数
unsigned long nivcsw;//非自愿(involuntary)上下文切换计数
u64 start_time;//进程启动时间,不包含睡眠时间
u64 real_start_time;//进程启动时间,包含睡眠时间
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
struct list_head children; /* list of my children */
struct list_head sibling; /* linkage in my parent's children list */
进程之间有亲缘关系,所以所有的进程实际上是一棵树,上面说过,进程组成一个双向循环链表,这并不冲突,因为既是双向循环链表,又是一棵树
所有进程组成的关系如下
real_parent 和 parent 在大多数情况下是一样的,只有在某些特殊情况下才会不一样
struct mm_struct *mm;
struct mm_struct *active_mm;
每个进程都有自己独立的地址空间,内核使用了 mm_struct 结构体来管理进程的地址空间
/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;
每个进程都有一个文件系统的数据结构,就是 fs_struct
每个进程还要维护它所打开的文件,这些信息在 files_struct 中
上面讲解了 task_struct 的大部分成员,下面来讲解进程的内核栈
程序的运行需要使用到栈,所以不管进程是在内核态运行还是在用户态运行都需要用到栈
Linux将进程地址空间分为内核空间和用户空间,它们之间是不能直接访问的,而一个进程某些时候可能在用户态运行,某些时候可能在内核态运行(发生系统调用时),所以一个进程既需要用户栈又需要内核栈
下面就来讲解内核给进程分配的栈结构
在 task_struct 中,有一个变量指向该进程的内核栈,如下
struct task_struct {
...
void *stack;
...
};
内核栈的大小在内核中的定义如下
#define THREAD_SIZE_ORDER 1
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
一个 PAGE_SIZE 是4K,左移一位就是乘以2,所以 THREAD_SIZE 就是8K,所以大体j结构如下
接下来我们看这8K的空间的结构分布
在这段空间的最底部,存放着一个 struct thread_info 结构体,何以证明呢?
在Linux中有一个 union thread_union 共用体,其定义如下
union thread_union {
#ifndef CONFIG_THREAD_INFO_IN_TASK
struct thread_info thread_info;
#endif
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
其中的 stack 表示栈空间,大小为 THREAD_SIZE 个字节
union 表示是一个共用体,可以看出,thread_info 在位于这个栈的最底部,如下图所示
Linux中发生系统调用时,会从用户态变成内核态,然后执行内核代码,当内核代码执行完之后,又会回到用户态执行用户代码
在进程从用户态变成内核态的时候,内核需要将用户态运行时寄存器的值保存下来,然后修改寄存器,当内核代码执行完之后,又将寄存器的值恢复,这些寄存器的值保存在哪里呢?
在内核栈的最高端,存放着一个 pt_regs 结构,这个结构包含了相关寄存器的定义,用户态寄存器的值就保存在这里,对于X86 32 位其定义如下
struct pt_regs {
unsigned long bx;
unsigned long cx;
unsigned long dx;
unsigned long si;
unsigned long di;
unsigned long bp;
unsigned long ax;
unsigned long ds;
unsigned long es;
unsigned long fs;
unsigned long gs;
unsigned long orig_ax;
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
};
此外剩余的空间才是用作函数栈,栈是向下生长的,所以进程的内核栈就变成下面这个样子
接下来看 thread_info 的定义,如下
struct thread_info {
struct task_struct *task; /* main task structure */
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
mm_segment_t addr_limit;
unsigned int sig_on_uaccess_error:1;
unsigned int uaccess_err:1; /* uaccess failed */
};
thread_info 中有一个变量 task_struct,指向拥有这个内核栈的进程,如下所示
Linux内核中可以通过 current 宏来获取当前正在运行的进程,它的实现十分巧妙,下面我们一起来看一看
#define get_current() (current_thread_info()->task)
#define current get_current()
current 通过 get_current(),进而调用 current_thread_info()->task
我们看一看 current_thread_info 的定义
static inline struct thread_info *current_thread_info(void)
{
return (struct thread_info *)
(current_stack_pointer & ~(THREAD_SIZE - 1));
}
current_stack_pointer 表示当前栈顶寄存器的值,对于 X86 就是 esp,在内核态的时候,current_stack_pointer 表示内核栈中的某一个位置
THREAD_SIZE 我们上面说过是8K,THREAD_SIZE - 1 就是8K剩下的所有位,如下
那么这个操作是什么意思呢?
(current_stack_pointer & ~(THREAD_SIZE - 1)
意思就是将 current_stack_pointer 的低12位清空
我们从这个 current_thread_info 函数可以看出,通过这个操作就可以获得 thread_info 对象,这是为什么呢?
这是因为,内核栈在申请的时候,总是 8K 对齐的,也就是说地址的低12位肯定为0
当进程在内核态运行的时候,栈顶指针总是指向这块申请的内核栈中的某一个区域,内核栈的大小最大也就8K,所以将当前栈顶指针的低12位置零就可以得到内核栈的基址
而 thread_info 存在于内核栈的栈底处,所以也就获取到了该进程对应的 thread_info 结构
thread_info 结构中有一个 task_struct* 成员,指向该进程的 task_struct,所以也就可以获得该进程的 task_struct 结构
不禁感叹,Linux内核的实现真是巧妙啊
好了,关于Linux进程的数据结构就介绍到这里了,后面的文章将讲解Linux的进程调度