在 linux 里面,无论是进程、线程,到了内核我们统一称之为任务( Task ),由一个叫做task_struct的结构统一管理,这个结构体包含了一个进程所需要的所有信息。接下来我们基于 kernel 5.2来分析这个结构。
首先 task_struct 结构体位于 /linux5.2/include/linux/sched.h 文件中。
每一个任务都需要有一个状态,涉及到任务状态的是下面的几个变量:
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile
long
state;
/*
Per
task
flags
(PF_*),
defined
further
below
: */
unsigned
int flags;
int exit_state;
state状态的值可以取以下:
/*
Used
in
tsk
->state: */
#
define
TASK_RUNNING 0x0000
#
define
TASK_INTERRUPTIBLE 0x0001
#
define
TASK_UNINTERRUPTIBLE 0x0002
#
define
__TASK_STOPPED 0x0004
#
define
__TASK_TRACED 0x0008
/*
Used
in
tsk
->exit_state: */
#
define
EXIT_DEAD 0x0010
#
define
EXIT_ZOMBIE 0x0020
#
define
EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/*
Used
in
tsk
->state
again
: */
#
define
TASK_PARKED 0x0040
#
define
TASK_DEAD 0x0080
#
define
TASK_WAKEKILL 0x0100
#
define
TASK_WAKING 0x0200
#
define
TASK_NOLOAD 0x0400
#
define
TASK_NEW 0x0800
#
define
TASK_STATE_MAX 0x1000
/*
Convenience
macros
for the sake of
set_current_state: */
#
define
TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#
define
TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)
#
define
TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)
#
define
TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
/*
Convenience
macros
for the sake of
wake_up(): */
#
define
TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
/* get_task_state(): */
#
define
TASK_REPORT (TASK_RUNNING | TASK_INTERRUPTIBLE | \
TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
__TASK_TRACED | EXIT_DEAD | EXIT_ZOMBIE | \
TASK_PARKED)
每个进程都必然处于以上所有状态中的一种
每个任务都需要有一个标识符,这个标识可以用来做下发指令和任务显示。
pid_t pid;
pid_t tgid;
struct
task_struct *group_leader;
如果一个进程只有主线程,那么pid和tgid都是自己,group_leader指向的也是自己。
如果进程创建了其他线程,线程有自己的pid,tgid就是主线程的pid,group_leader指向的是进程的主线程。
那么一个进程中 pid 的取值范围是多少呢
查看 /include/linux/threads.h 文件会有这样一个宏
#
define
PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
在CONFIG_BASE_SMALL为0的情况下,pid的取值范围是0到32767。
struct
list_head tasks;
我们发现有一个 list_head的结构体,这是一个链表。没错,内核就是通过链表将任务串联起来的。
那么为什么使用链表 而不是数组。数组是连续的内存空间更适合存储啊。
这个是因为要维护众多的 task 关系,一个task 节点的parent 指针指向其父进程的 task,children指针指向子进程所有task的头部,然后又靠sibling指针来维护兄弟 task。所以很明显链表结构更合适。
在 linux 中,任何一个进程都拥有一个父进程,所有进程之间都有着直接或者间接的联系,拥有同一父进程的所有进程称之为兄弟进程,其实整个进程就是一颗进程树。
struct
task_struct __rcu *real_parent;
struct
task_struct __rcu *parent;
struct
list_head children;
struct
list_head sibling;
struct
task_struct *group_leader;
一个任务能访问哪些文件,能访问哪些其他的任务,以及可以被哪些任务所访问,这些问题都是由权限系统来控制。
那么 linux 进程中的权限到底是怎么样的存在?
/*
Process
credentials
: */
/*
Tracer
's
credentials
at
attach
: */
const
struct
cred __rcu *ptracer_cred;
/*
Objective
and
real
subjective
task
credentials
(
COW
): */
const
struct
cred __rcu *real_cred;
/*
Effective
(
overridable
)
subjective
task
credentials
(
COW
): */
const
struct
cred __rcu *cred;
根据注释就能知道 real_cred是被操作的权限,**cred **是这个进程操作其他的权限。
__rcu( Read - Copy update ) 共享数据的同步机制
接下来我们看一个 cred 这个结构体,位于 cred .h 中
kuid_t uid; /*
real
UID
of
the
task
*/
kgid_t gid; /*
real
GID
of
the
task
*/
kuid_t suid; /*
saved
UID
of
the
task
*/
kgid_t sgid; /*
saved
GID
of
the
task
*/
kuid_t euid; /*
effective
UID
of
the
task
*/
kgid_t egid; /*
effective
GID
of
the
task
*/
kuid_t fsuid; /*
UID
for
VFS
ops
*/
kgid_t fsgid; /*
GID
for
VFS
ops
*/
unsigned
securebits; /*
SUID
-
less
security
management
*/
kernel_cap_t cap_inheritable; /*
caps
our
children
can
inherit
*/
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /*
caps
we
can
actually
use
*/
kernel_cap_t cap_bset; /*
capability
bounding
set
*/
kernel_cap_t cap_ambient; /*
Ambient
capability
set
*/
一般来说,fsuid、euid和uid 是一样的, fsuid、egid和gid也是一样的。
linux 通过SUID( Set User ID on execution ) 来赋予权限的话,会带来安全隐患,那么为了解决这个问题,linux 引用了capabilities 能力机制。
capabilities 机制将 root 用户的权限细分,可以分别的启动和禁用,在实际的操作当中,如果euid 不是root,便会检查该特权具有哪些capabilities。
在linux 操作系统中 可以查看/proc/{PID}/status查看进程所属属性
接来下 查看一下 capabilities定义了哪些常见的权限
#
define
CAP_CHOWN 0
#
define
CAP_KILL 5
#
define
CAP_SETUID 7
#
define
CAP_NET_BIND_SERVICE 10
#
define
CAP_NET_RAW 13
#
define
CAP_SYS_MODULE 16
#
define
CAP_SYS_RAWIO 17
#
define
CAP_SYS_PTRACE 19
#
define
CAP_SYS_BOOT 22
#
define
CAP_SYS_TIME 25
#
define
CAP_AUDIT_READ 37
#
define
CAP_LAST_CAP CAP_AUDIT_READ
在 task_struct 结构中,内核栈是由 stack 变量管理
void
*stack;
内核栈位于pt_regs和thread_info之间:pt_regs 、内核栈、thread_info
内核栈是从该内存内存空间的自顶向下(从高位到地位),而thread_info 是从底向上的(从地位到高位)。内核栈的栈顶地址存储在esp中。所以当用户从用户态进入内核态后 esp会指向内核栈的底部。
通过 thread_info 获取task_struct:
在include/linux/thread_info.h文件中 current_thread_info() 方法
#
define
current_thread_info() ((struct thread_info *)current)
在arch/x86/include/asm/current.h
static
__always_inline
struct
task_struct *get_current(
void
)
{
return
this_cpu_read_stable(current_task);
}
内核栈的大小为:
#
define
THREAD_SIZE_ORDER 1
#
define
THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
#
ifdef
CONFIG_KASAN
#
define
KASAN_STACK_ORDER 1
#
else
#
define
KASAN_STACK_ORDER 0
#
endif
#
define
THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
#
define
THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
内存栈的最高地址端,存放的是一个 pt_regs 结构 /arch/x86/include/uapi/asm/ptrace.h
struct
pt_regs {
long
ebx;
long
ecx;
long
edx;
long
esi;
long
edi;
long
ebp;
long
eax;
int xds;
int xes;
int xfs;
int xgs;
long
orig_eax;
long
eip;
int xcs;
long
eflags;
long
esp;
int xss;
};
struct
pt_regs {
unsigned
long
r15;
unsigned
long
r14;
unsigned
long
r13;
unsigned
long
r12;
unsigned
long
rbp;
unsigned
long
rbx;
unsigned
long
r11;
unsigned
long
r10;
unsigned
long
r9;
unsigned
long
r8;
unsigned
long
rax;
unsigned
long
rcx;
unsigned
long
rdx;
unsigned
long
rsi;
unsigned
long
rdi;
unsigned
long
orig_rax;
unsigned
long
rip;
unsigned
long
cs;
unsigned
long
eflags;
unsigned
long
rsp;
unsigned
long
ss;
};