Linux进程数据结构分析

Linux进程结构分析

在 linux 里面,无论是进程、线程,到了内核我们统一称之为任务( Task ),由一个叫做task_struct的结构统一管理,这个结构体包含了一个进程所需要的所有信息。接下来我们基于 kernel 5.2来分析这个结构。

0x00 task_struct 分析

首先 task_struct 结构体位于 /linux5.2/include/linux/sched.h 文件中。

0x01 任务状态

每一个任务都需要有一个状态,涉及到任务状态的是下面的几个变量:

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

每个进程都必然处于以上所有状态中的一种

  1. TASK_RUNNING 表示进程时刻准备着运行的状态。当处于这个状态的进程获得时间片的时候,就是运行中;如果没有获得时间片,那么说明被其他进程强占了,再等待再次分配时间片。
  2. TASK_INTERRUPTIBLE,可中断的睡眠状态。当前进程阻塞状态,虽然此时在睡眠状态,但可以被信号唤醒,唤醒后进行信号的处理,此时处理的函数可以由程序员来决定。
  3. TASK_UNINTERRUPTIBLE,不可中断的睡眠状态。此时在睡眠状态时,不可被信号唤醒,只能等待I/O操作完成。如果在I/O过程中发生意外无法完成,那么就会形成一个无法唤醒的进程。包括 kill 命令也无法唤醒,因为 kill 本身就是一种信号。
  4. TASK_KILLABLE,可终止的睡眠状态。从上面的注释可以发现为了唤醒而使用的宏,它设置的值是TASK_UNINTERRUPTIBLE 和 TASK_WAKEKILL ,即在 TASK_UNINTERRUPTIBLE 的基础上 可以响应致命唤醒信号。
  5. __TASK_STOPPED ,被停止状态。 在进程接收到 SIGTTIN、SIGSTOP、SIGTSTP、STGTTOU信号后会进入此状态。
  6. __TASK_TRACED 进程被 debugger 等进程监视。
  7. EXIT_ZOMBIE 进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息。
  8. EXIT_DEAD 进程的最终状态。
0x02 任务ID

每个任务都需要有一个标识符,这个标识可以用来做下发指令和任务显示。

	pid_t				pid;
	pid_t				tgid;
	
   
    struct
    task_struct		*group_leader;
  • pid 是 process id
  • tgid是 thread group id
  • 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。

0x03 内核是如何把任务串联起来的
	
   
    struct
    list_head		tasks;

我们发现有一个 list_head的结构体,这是一个链表。没错,内核就是通过链表将任务串联起来的。

那么为什么使用链表 而不是数组。数组是连续的内存空间更适合存储啊。

这个是因为要维护众多的 task 关系,一个task 节点的parent 指针指向其父进程的 task,children指针指向子进程所有task的头部,然后又靠sibling指针来维护兄弟 task。所以很明显链表结构更合适。

0x04 进程亲属关系

在 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;
  • real_parent 指向其父进程,如果创建它的父进程不存在,则指向PID为1的init进程。
  • parent 指向其父进程, 当它终止时必须向它的父进程发信号。值通常与 real_parent 相同。
  • children 表示链表的头部,链表中的所有元素都是它的子进程。
  • sibling 用于把当前进程插入到兄弟链表中。
  • group_leader 指向其所在进程组的 learder 进程。
0x05 进程权限

一个任务能访问哪些文件,能访问哪些其他的任务,以及可以被哪些任务所访问,这些问题都是由权限系统来控制。

那么 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
    */
  • uid、gid 表示这个进程的启动者,谁启动的就是谁的id。
  • euid、egid 当一个进程要访问资源时,比较的就是这个用户和组是否有权限。
  • fsuid、fsgid 这个是对文件操作所需要的比较的权限。

一般来说,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
  • cap_permitted 表示进程能够使用的权限
  • cap_effective 实际起作用的权限
  • cap_inheritable 表示当可执行文件的设置了 inheritable 位时,调用exec执行该程序会继承调用者的inheritable 集合,并将其加入到permitted集合中。在非root用户下执行exec时 通常不会保存
  • cap_ambient 非root用户使用 exec 执行一个程序时,cap_ambient会被加入到 cap_permitted和cap_effective中。
  • cap_bset 进程中所有进程允许保留的集合。
0x06 内核态函数栈

在 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);
}

内核栈的大小为:

  • 在32位系统上 arch/x86/include/asm/page_32_types.h
#
    
     define
     THREAD_SIZE_ORDER	1
#
    
     define
     THREAD_SIZE		(PAGE_SIZE << THREAD_SIZE_ORDER)
  • 在64位系统上 arch/x86/include/asm/page_64_types.h
#
    
     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;
};

你可能感兴趣的:(linux,linux,进程)