程序锅

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于

  • 搜索
基础知识 Etcd LeetCode 计算机体系结构 Kubernetes Containerd Docker 容器 云原生 Serverless 项目开发维护 ELF 深入理解程序 Tmux Vim Linux Kernel Linux numpy matplotlib 机器学习 MQTT 网络基础 Thrift RPC OS 操作系统 Clang 研途 数据结构和算法 Java 编程语言 Golang Python 个人网站搭建 Nginx 计算机通用技术 Git

Linux Kernel | Linux task_struct 结构体概述

发表于 2021-09-14 | 分类于 Linux Kernel | 0 | 阅读次数 2946

1. task_struct 概述

在 Linux 内核中,无论是进程还是线程,到了内核里面,都叫做任务(Task),由统一的数据结构 task_struct 进行管理。task_struct 是 Linux 中的进程描述符,是感知进程存在的唯一实体。Linux 内核中通过一个双向循环链表将所有的 task_struct 串了起来。

不同的操作系统中,PCB 所包含的内容也会不同。

1.1. 任务 ID

// include\linux\sched.h
pid_t pid;
pid_t tgid;
struct task_struct *group_leader;

pid(process ID)是任务的唯一标识符,每一个任务的 pid 都是不一样的。也就是说,如果一个进程有多个线程,那么这些多个线程所使用的 pid 也都是不一样的。

tgid(thread group ID),是线程组的 ID,一个进程中的所有线程的 tgid 都是一样的。如果一个进程,只有主线程,那么 pid 是自己,tgid 也是自己。如果一个进程,创建了其他线程,那么其他线程都有自己的 pid,但是其他线程的 tgid 是进程主线程的 pid。

group_leader 则指向进程主线程的 task_struct。同上,如果一个进程,只有主线程,那么 pid 是自己,group_leader 指向的也是自己。如果一个进程,创建了其他线程,那么这些线程的 group_leader 指向的都是进程主线程的 task_struct。

有了 tgid 之后,我们就知道 task_struct 代表的是一个进程还是一个线程了。

1.2. 进程亲缘关系

Linux kernel 中主要用以下这些成员变量来表示进程的亲缘关系:

// include\linux\sched.h

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 */

「详细可以看 Linux 进程-进程数量、状态、关系」

1.3. 任务状态

// include\linux\sched.h

volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
int exit_state;
unsigned int flags;

「详细可以看 Linux 进程-进程数量、状态、关系」

1.4. 进程权限

进程权限控制是指进程能否有权限访问某个文件、能否访问其他进程、能否进行某些操作,以及进程能否被其他项目组访问。task_struct 中关于进程权限的成员变量有如下这些,其中 cred 表示我这个进程可以操作谁,实质上就是我操作别人时具有的权限是什么;real_cred 表示谁能操作我这个进程。

操作其实就是一个对象对另一个对象进行某些动作。当动作要实施的时候,需要审核权限,当两边的权限匹配上了,那么就可以实施操作。

// include\linux\sched.h

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu         *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu         *cred;

注释中的 Objective 是指我这个进程当前是被操作的对象,而那个想操作我的进程就是 Subjective。我能操作谁,那么这个时候我就是那个 subjective,而被我操作的那个就是 Objective。

「详细可以看 Linux 进程-权限」

1.5. 运行统计信息

task_struct 中有关于进行进程运行统计信息的字段,如下所示。主要记录了进程在用户态或者内核态上消耗的时间、上下文切换的次数。

// include\linux\sched.h

u64        utime;			// 用户态消耗的CPU时间
u64        stime;			// 内核态消耗的CPU时间

unsigned long      nvcsw;	 // 自愿(voluntary)上下文切换计数
unsigned long      nivcsw;	 // 非自愿(involuntary)上下文切换计数

u64        start_time;		 // 进程启动时间,不包含睡眠时间
u64        real_start_time;	 // 进程启动时间,包含睡眠时间

「详细可以看 Linux 进程-运行统计信息」

1.6. 进程调度

// include\linux\sched.h

//是否在运行队列上
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;

1.7. 信号处理

// include\linux\sched.h

/* 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;

blocked 代表被阻塞暂不处理的信号。

pending 代表尚等待处理的信号。

sighand 代表正在通过信号处理函数进行处理的信号,处理的结果是可以是忽略,也可以是结束进程。

信号处理函数默认使用的是用户态的函数栈,当然也可以开辟新的栈专门用于信号处理,这就是 sas_ss_xxx 这三个变量的作用。

「详细可以看 Linux IPC-信号」

1.8. 内存管理

进程的虚拟地址空间分为用户虚拟地址空间和内核虚拟地址空间,所以进程共享内核虚拟地址空间,但每个进程有独立的用户虚拟地址空间。这块的内容可具体查看 Linux 内存管理。

内核线程没有用户地址空间,那么 mm 将为空,active_mm 则指向此时用户态的地址空间。对于用户进程来说,mm 和 active_mm 是一样的。

// include\linux\sched.h

struct mm_struct                *mm;
struct mm_struct                *active_mm;

「详细可以看 Linux 内存管理」

1.9. 文件与文件系统

每个进程都有一个文件系统的数据结构,还有一个打开文件的数据结构。这块的内容可具体查看 Linux 文件系统。

// include\linux\sched.h

/* Filesystem information: */
struct fs_struct                *fs;
/* Open file information: */
struct files_struct             *files;

「详细可以看 Linux 文件系统」

1.10. 内核态栈

在程序的执行过程中,一旦调用了系统调用,那么就需要进入内核继续执行。跟在用户态下函数执行的过程类似,进程陷入到内核态执行时也有一个栈,我们称其为内核栈。

Linux 给每个 task 都分配了内核栈。在 x86 32 系统上,内核栈的大小是 8K。在 x86 64 系统上,内核栈的大小一般是 16K,并且要求起始地址必须是 8192 的整数倍。

// include\linux\sched.h

struct thread_info    thread_info;
void  *stack;

// arch\x86\include\asm\page_32_types.h
#define THREAD_SIZE_ORDER	1
#define THREAD_SIZE		(PAGE_SIZE << THREAD_SIZE_ORDER)

// arch\x86\include\asm\page_64_types.h
#ifdef CONFIG_KASAN
#ifdef CONFIG_KASAN_EXTRA
#define KASAN_STACK_ORDER 2
#else
#define KASAN_STACK_ORDER 1
#endif
#else
#define KASAN_STACK_ORDER 0
#endif

#define THREAD_SIZE_ORDER	(2 + KASAN_STACK_ORDER)
#define THREAD_SIZE  (PAGE_SIZE << THREAD_SIZE_ORDER)

内核栈是一个非常特殊的数据结构,它还包含了 thread_info 和 pt_regs 等数据结构,也就是说 THREAD_SIZE 的长度是指包含了 thread_info 和 pt_regs 长度之后的,如下图所示。

这段空间的最低位置,是一个 thread_info 数据结构,这个数据结构是对 task_struct 的补充。需要这个数据结构主要是因为 task_struct 通用,而 linux 需要考虑到不同体系结构,而不同体系结构会有一套自己需要保存的东西。所以往往与体系结构相关的内容都会被保存到 thread_info,也就说 thread_info 这个数据结构由不同的体系结构自己定义,可以查看 arch 目录下各体系结构对 thread_info 这个结构体的定义。

在内核代码中有一个 union,就是将 task_struct、thread_info 以及 stack 放到一起的。当然这个具体放不放在一起,得看宏定义的情况。

// include\linux\sched.h

union thread_union {
#ifndef CONFIG_ARCH_TASK_STRUCT_ON_STACK
	struct task_struct task;
#endif
#ifndef CONFIG_THREAD_INFO_IN_TASK
	struct thread_info thread_info;
#endif
	unsigned long stack[THREAD_SIZE/sizeof(long)];
};

在内核栈的最高地址端,存放的是结构体 pt_regs。这个结构体也是跟体系结构相关的,x86 32 位和 64 位的结构体的定义就是不一样的,如下所示。

在系统调用的时候,从用户态切换到内核态的时候,首先要做的第一件事情就是将用户态运行过程中的 CPU 上下文保存起来,其实主要就是保存在这个结构的寄存器变量里。这样当从内核系统调用返回的时候,就可以从进程在刚才的地方继续运行下去。而在系统调用的过程中,压栈的顺序和 struct pt_regs 中寄存器定义的顺序是一样的。


#ifdef __i386__
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;
};
#else 
struct pt_regs {
  unsigned long r15;
  unsigned long r14;
  unsigned long r13;
  unsigned long r12;
  unsigned long bp;
  unsigned long bx;
  unsigned long r11;
  unsigned long r10;
  unsigned long r9;
  unsigned long r8;
  unsigned long ax;
  unsigned long cx;
  unsigned long dx;
  unsigned long si;
  unsigned long di;
  unsigned long orig_ax;
  unsigned long ip;
  unsigned long cs;
  unsigned long flags;
  unsigned long sp;
  unsigned long ss;
/* top of stack page */
};
#endif 

「关于用户态函数栈的或者内核态函数更多的内容,可以看 Linux 进程-函数栈」

1.11. PCB 总结

1.12. 巨人的肩膀

  1. 极客时间专栏《趣谈Linux操作系统》
卷死我
dawnguo 微信支付

微信支付

dawnguo 支付宝

支付宝

  • 本文作者: dawnguo
  • 本文链接: /archives/117
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# Linux # Linux Kernel
数据结构和算法 | 无锁链表/队列的实现【纯纯的代码】
Linux Kernel | Linux 函数(内核态和用户态)
  • 文章目录
  • 站点概览
dawnguo

dawnguo

215 日志
24 分类
37 标签
RSS
Creative Commons
© 2018 — 2025 程序锅
0%