linux进程概念(中)

1. task_struct结构体, 结构体中的各个字段的含义.
为了管理进程,操作系统必须对每个进程所做的事情进行清楚的描述,为此,操作系统使用数据结构来代表处理不同的实体,这个数据结构就是通常所说的进程描述符或进程控制块(PCB)。
在linux操作系统下这就是task_struct结构 ,所属的头文件# include “sched.h” 每个进程都会被分配一个task _struct结构,它包含了这个进程的所有信息,在任何时候操作系统都能够跟踪这个结构的信息。
1.1 task_struct是PCB的一种
- 在Linux中描述进程的结构体叫做task_struct ;
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
1.2 task_struct内容分类
可以参考《操作系统精髓与设计原理》

  • 标示符:描述本进程的唯一标示符,用于区别其他进程。
  • 状态:任务状态,退出代码,推出信号等。
  • 优先级:相对于其他进程的优先级。
  • 程序计数器:程序中即将被执行的下一条指令的地址。
  • 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 上下文数据:进程执行时处理器的寄存器中的数据。
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间x限制,记账号等。
  • 其他信息:

1.3 task_struct结构体中成员分类
关于task_struct的定义, 位于/usr/include/linux/sched.h文件(centos6.5下)中
task_struct所定义在的头文件sched.h的链接:
http://lxr.free-electrons.com/source/include/linux/sched.h
1.3.1 进程状态
一个进程是执行状态还是睡眠状态还是阻塞状态,将在下边的成员中进行描述。

volatile long state;  /* -1 unrunnable, 0 runnable, >0 stopped */

state的可能取值为:

/*
  * Task state bitmask. NOTE! These bits are also
  * encoded in fs/proc/array.c: get_task_state().
  *
  * We have two separate sets of flags: task->state
  * is about runnability, while task->exit_state are
  * about the task exiting. Confusing, but this way
  * modifying one set can't modify the other one by
  * mistake.
  */
 #define TASK_RUNNING        0//进程要么正在执行,要么准备执行
 #define TASK_INTERRUPTIBLE  1 //可中断的睡眠,可以通过一个信号唤醒
 #define TASK_UNINTERRUPTIBLE    2 //不可中断睡眠,不可以通过信号进行唤醒
 #define __TASK_STOPPED      4 //进程停止执行
 #define __TASK_TRACED       8 //进程被追踪

/* in tsk->exit_state */
 #define EXIT_DEAD               16//进程的最终状态,进程死亡。
 #define EXIT_ZOMBIE             32//僵尸状态的进程,表示进程被终止,但是父进程还没有获取它的终止信息,比如进程有没有执行完等信息。 
 #define EXIT_TRACE              (EXIT_ZOMBIE | EXIT_DEAD)

/* in tsk->state again */
 #define TASK_DEAD       64 //死亡
 #define TASK_WAKEKILL       128 //唤醒并杀死的进程
 #define TASK_WAKING     256 //唤醒进程 
 #define TASK_PARKED             512
 #define TASK_NOLOAD             1024
 #define TASK_STATE_MAX          2048
 /* Convenience macros for the sake of set_task_state */
#define TASK_KILLABLE           (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED            (TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED             (TASK_WAKEKILL | __TASK_TRACED)
状态描述
TASK_RUNNING表示进程正在执行或者处于准备执行的状态
TASK_INTERRUPTIBLE进程因为等待某些条件处于阻塞(挂起的状态),一旦等待的条件成立,进程便会从该状态转化成就绪状态
TASK_UNINTERRUPTIBLA意思与TASK_INTERRUPTIBLE类似,但是我们传递任意信号等不能唤醒他们,只有它所等待的资源可用的时候,他才会被唤醒。
TASK_STOPPED进程被停止执行
TASK_TRACED进程被debugger等进程所监视
EXIT_ZOMBIE进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息,此时该进程称为僵尸进程
EXIT_DEAD进程被杀死,即进程的最终状态
TASK_KILLABLE当进程处于这种可以终止的新睡眠状态中,它的运行原理类似于 TASK_UNINTERRUPTIBLE,只不过可以响应致命信号

内核如何将进程置为睡眠状态
Linux 内核提供了两种方法将进程置为睡眠状态。
将进程置为睡眠状态的普通方法是将进程状态设置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 并调用调度程序的 schedule() 函数。这样会将进程从 CPU 运行队列中移除。
- 如果进程处于可中断模式的睡眠状态(通过将其状态设置为 TASK_INTERRUPTIBLE),那么可以通过显式的唤醒呼叫(wakeup_process())或需要处理的信号来唤醒它。
- 但是,如果进程处于非可中断模式的睡眠状态(通过将其状态设置为 TASK_UNINTERRUPTIBLE),那么只能通过显式的唤醒呼叫将其唤醒。除非万不得已,否则我们建议您将进程置为可中断睡眠模式,而不是不可中断睡眠模式(比如说在设备 I/O 期间,处理信号非常困难时)。
当处于可中断睡眠模式的任务接收到信号时,它需要处理该信号(除非它已被屏弊),离开之前正在处理的任务(此处需要清除代码),并将 -EINTR 返回给用户空间。再一次,检查这些返回代码和采取适当操作的工作将由程序员完成。
因此,懒惰的程序员可能比较喜欢将进程置为不可中断模式的睡眠状态,因为信号不会唤醒这类任务。
但需要注意的一种情况是,对不可中断睡眠模式的进程的唤醒呼叫可能会由于某些原因不会发生,这会使进程无法被终止,从而最终引发问题,因为惟一的解决方法就是重启系统。一方面,需要考虑一些细节,因为不这样做会在内核端和用户端引入 bug。另一方面,可能会生成永远不会停止的进程(被阻塞且无法终止的进程)。
现在,在内核中实现了一种新的睡眠方法
Linux Kernel 2.6.25 引入了一种新的进程睡眠状态,

状态描述
TASK_KILLABLE当进程处于这种可以终止的新睡眠状态中,它的运行原理类似于 TASK_UNINTERRUPTIBLE,只不过可以响应致命信号

TASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE。
而TASK_WAKEKILL 用于在接收到致命信号时唤醒并杀死进程,新的睡眠状态允许 TASK_UNINTERRUPTIBLE 响应致命信号
1.3.2 进程标识符(PID)

 pid_t pid;//进程的唯一标识
 pid_t tgid;// 线程组的领头线程的pid成员的值

pid是进程的唯一表示,范围是0~32767,可以表示32768个进程。
在Linux系统中,一个线程组的所有线程使用和该线程组的领头线程相同的PID,并被存放在tgid成员中。(线程是程序运行的最小单位,进程是程序运行的基本单位。)只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用返回的是当前进程的tgid值而不是pid值。
1.3.3 进程标记符

unsigned int flags; /* per process flags, defined below */ 

flags反映进程的状态信息,用于内核识别当前进程的状态。
flags成员的可能取值如下,这些宏以PF(ProcessFlag)开头

参见
http://lxr.free-electrons.com/source/include/linux/sched.h?v4.5#L2083
例如
PF_FORKNOEXEC 进程刚创建,但还没执行。
PF_SUPERPRIV 超级用户特权。
PF_DUMPCORE dumped core。
PF_SIGNALED 进程被信号(signal)杀出。
PF_EXITING 进程开始关闭。

 /*
* Per process flags
*/
#define PF_EXITING      0x00000004      /* getting shut down */
#define PF_EXITPIDONE   0x00000008      /* pi exit done on shut down */
#define PF_VCPU         0x00000010      /* I'm a virtual CPU */
#define PF_WQ_WORKER    0x00000020      /* I'm a workqueue worker */
#define PF_FORKNOEXEC   0x00000040      /* forked but didn't exec */
#define PF_MCE_PROCESS  0x00000080      /* process policy on mce errors */
#define PF_SUPERPRIV    0x00000100      /* used super-user privileges */
#define PF_DUMPCORE     0x00000200      /* dumped core */
#define PF_SIGNALED     0x00000400      /* killed by a signal */
#define PF_MEMALLOC     0x00000800      /* Allocating memory */
#define PF_NPROC_EXCEEDED 0x00001000    /* set_user noticed that RLIMIT_NPROC was exceeded */
#define PF_USED_MATH    0x00002000      /* if unset the fpu must be initialized before use */
#define PF_USED_ASYNC   0x00004000      /* used async_schedule*(), used by module init */
#define PF_NOFREEZE     0x00008000      /* this thread should not be frozen */
#define PF_FROZEN       0x00010000      /* frozen for system suspend */
#define PF_FSTRANS      0x00020000      /* inside a filesystem transaction */
#define PF_KSWAPD       0x00040000      /* I am kswapd */
#define PF_MEMALLOC_NOIO 0x00080000     /* Allocating memory without IO involved */
#define PF_LESS_THROTTLE 0x00100000     /* Throttle me less: I clean memory */
#define PF_KTHREAD      0x00200000      /* I am a kernel thread */
#define PF_RANDOMIZE    0x00400000      /* randomize virtual address space */
#define PF_SWAPWRITE    0x00800000      /* Allowed to write to swap */
#define PF_NO_SETAFFINITY 0x04000000    /* Userland is not allowed to meddle with cpus_allowed */
#define PF_MCE_EARLY    0x08000000      /* Early kill for mce process policy */
#define PF_MUTEX_TESTER 0x20000000      /* Thread belongs to the rt mutex tester */
#define PF_FREEZER_SKIP 0x40000000      /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000      /* this thread called freeze_processes and should not be frozen */

1.3.4 表示进程亲属关系的成员

/*
 * pointers to (original) parent process, youngest child, younger sibling,
 * older sibling, respectively.  (p->father can be replaced with
 * p->real_parent->pid)
 */
struct task_struct __rcu *real_parent; /* real parent process */
struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
/*
 * children/sibling forms the list of my natural children
 */
struct list_head children;      /* list of my children */
struct list_head sibling;       /* linkage in my parent's children list */
struct task_struct *group_leader;       /* threadgroup leader */
字段描述
real_parent指向其父进程,如果创建它的父进程不再存在,则指向PID为1的init进程
parent指向其父进程,当它终止时,必须向它的父进程发送信号。它的值通常与real_parent相同
children位于链表的头部,链表中的所有元素都是它的子进程
sibling用于把当前进程插入到兄弟链表中
group_leader指向其所在进程组的领头进程

1.3.5 ptrace系统调用
Ptrace提供了一种父进程,它可以被用来控制子进程的运行,常被用来进行断点调试,当它被设置为0时表示不需要追踪。一个被跟踪的进程运行中,直到发生一个信号。则进程被中止,并且通知其父进程。在进程中止的状态下,进程的内存空间可以被读写。父进程还可以使子进程继续执行,并选择是否是否忽略引起中止的信号。

unsigned int ptrace;
 ptraced is the list of tasks this task is using ptrace on.
* This includes both natural children and PTRACE_ATTACH targets.
* p->ptrace_entry is p's link on the p->parent->ptraced list.
*/
struct list_head ptraced;
struct list_head ptrace_entry;

unsigned long ptrace_message;
siginfo_t *last_siginfo; /* For ptrace use.  */

成员ptrace被设置为0时表示不需要被跟踪,它的可能取值如下:

/*
 * Ptrace flags
 *
 * The owner ship rules for task->ptrace which holds the ptrace
 * flags is simple.  When a task is running it owns it's task->ptrace
 * flags.  When the a task is stopped the ptracer owns task->ptrace.
 */

#define PT_SEIZED       0x00010000      /* SEIZE used, enable new behavior */
#define PT_PTRACED      0x00000001
#define PT_DTRACE       0x00000002      /* delayed trace (used on m68k, i386) */
#define PT_PTRACE_CAP   0x00000004      /* ptracer can follow suid-exec */

#define PT_OPT_FLAG_SHIFT       3
/* PT_TRACE_* event enable flags */
#define PT_EVENT_FLAG(event)    (1 << (PT_OPT_FLAG_SHIFT + (event)))
#define PT_TRACESYSGOOD         PT_EVENT_FLAG(0)
#define PT_TRACE_FORK           PT_EVENT_FLAG(PTRACE_EVENT_FORK)
#define PT_TRACE_VFORK          PT_EVENT_FLAG(PTRACE_EVENT_VFORK)
#define PT_TRACE_CLONE          PT_EVENT_FLAG(PTRACE_EVENT_CLONE)
#define PT_TRACE_EXEC           PT_EVENT_FLAG(PTRACE_EVENT_EXEC)
#define PT_TRACE_VFORK_DONE     PT_EVENT_FLAG(PTRACE_EVENT_VFORK_DONE)
#define PT_TRACE_EXIT           PT_EVENT_FLAG(PTRACE_EVENT_EXIT)
#define PT_TRACE_SECCOMP        PT_EVENT_FLAG(PTRACE_EVENT_SECCOMP)

#define PT_EXITKILL             (PTRACE_O_EXITKILL << PT_OPT_FLAG_SHIFT)
#define PT_SUSPEND_SECCOMP      (PTRACE_O_SUSPEND_SECCOMP << PT_OPT_FLAG_SHIFT)

/* single stepping state bits (used on ARM and PA-RISC) */
#define PT_SINGLESTEP_BIT       31
#define PT_SINGLESTEP           (1<<PT_SINGLESTEP_BIT)
#define PT_BLOCKSTEP_BIT        30
#define PT_BLOCKSTEP            (1<<PT_BLOCKSTEP_BIT)

1.3.6 进程调度
1)优先级

int prio, static_prio, normal_prio;
unsigned int rt_priority;
字段描述
prio用于保存动态优先级
static_prio用于保存静态优先级,可以通过nice系统调用来进行修改
normal_prio其值取决于静态优先级和调度策略
rt_priority用于保存实时优先级

实时优先级范围是0到MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围是从MAX_RT_PRIO到MAX_PRIO-1(即100到139)。值越大静态优先级越低。

/*  http://lxr.free-electrons.com/source/include/linux/sched/prio.h#L21  */
#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO     MAX_USER_RT_PRIO

/* http://lxr.free-electrons.com/source/include/linux/sched/prio.h#L24  */
#define MAX_PRIO        (MAX_RT_PRIO + 40)
#define DEFAULT_PRIO        (MAX_RT_PRIO + 20)

2)调度策略相关字段

/*  http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L1426  */
unsigned int policy;

/*  http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.5#L1409  */
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;

cpumask_t cpus_allowed;

|字段|描述|
|:——|:——|
|policy|调度策略|
|sched_class|调度类|
|se|普通进程的调用实体,每个进程都有其中之一的实体|
|rt|实时进程的调用实体,每个进程都有其中之一的实体|
|cpus_allowed|用于控制进程可以在哪里处理器上运行|
3)调度策略
policy表示进程的调度策略,目前主要有以下五种:

#define SCHED_NORMAL        0//按照优先级进行调度(有些地方也说是CFS调度器)
#define SCHED_FIFO        1//先进先出的调度算法
#define SCHED_RR        2//时间片轮转的调度算法
#define SCHED_BATCH        3//用于非交互的处理机消耗型的进程
#define SCHED_IDLE        5//系统负载很低时的调度算法
#define SCHED_RESET_ON_FORK     0x40000000
字段描述所在调度器类
SCHED_NORMAL也叫SCHED_OTHER,用于普通进程,通过CFS调度器实现。SCHED_BATCH用于非交互的处理器消耗型进程。SCHED_IDLE是在系统负载很低时使用CFS
SCHED_BATCHSCHED_NORMAL普通进程策略的分化版本。采用分时策略,根据动态优先级(可用nice()API设置),分配 CPU 运算资源。注意:这类进程比上述实时进程优先级低,换言之,在有实时进程存在时,实时进程优先调度。但针对吞吐量优化CFS
SCHED_IDLE优先级最低,在系统空闲时才跑这类进程(如利用闲散计算机资源跑地外文明搜索,蛋白质结构分析等任务,是此调度策略的适用者)CFS
SCHED_FIFO先入先出调度算法(实时调度策略),相同优先级的任务先到先服务,高优先级的任务可以抢占低优先级的任务RT
SCHED_RR轮流调度算法(实时调度策略),后 者提供 Roound-Robin 语义,采用时间片,相同优先级的任务当用完时间片会被放到队列尾部,以保证公平性,同样,高优先级的任务可以抢占低优先级的任务。不同要求的实时任务可以根据需要用sched_setscheduler()API 设置策略RT
SCHED_DEADLINE新支持的实时进程调度策略,针对突发型计算,且对延迟和完成时间高度敏感的任务适用。基于Earliest Deadline First (EDF) 调度算法

4)调度类
sched_class结构体表示调度类,目前内核中有实现以下四种:

extern const struct sched_class stop_sched_class;
extern const struct sched_class dl_sched_class;
extern const struct sched_class rt_sched_class;
extern const struct sched_class fair_sched_class;
extern const struct sched_class idle_sched_class;
调度器类描述
stop_sched_class优先级最高的线程,会中断所有其他线程,且不会被其他任务打断。作用:1.发生在cpu_stop_cpu_callback 进行cpu之间任务migration;2.HOTPLUG_CPU的情况下关闭任务。
rt_sched_classRT,作用:实时线程
fair_sched_classCFS(公平),作用:一般常规线程
idle_sched_class每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class,所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump

目前系統中,Scheduling Class的优先级顺序为StopTask > RealTime > Fair > IdleTask
开发者可以根据己的设计需求,來把所属的Task配置到不同的Scheduling Class中.
1.3.7 进程地址空间
进程都拥有自己的资源,这些资源指的就是进程的地址空间,每个进程都有着自己的地址空间,在task_struct中,有关进程地址空间的定义如下:

struct mm_struct *mm, *active_mm;
/* per-thread vma caching */
u32 vmacache_seqnum;
struct vm_area_struct *vmacache[VMACACHE_SIZE];
#if defined(SPLIT_RSS_COUNTING)
struct task_rss_stat    rss_stat;
#endif

/*  http://lxr.free-electrons.com/source/include/linux/sched.h?V=4.5#L1484  */
#ifdef CONFIG_COMPAT_BRK
unsigned brk_randomized:1;
#endif
成员描述
mm进程所拥有的内存空间描述符,对于内核线程的mm为NULL
active_mm指进程运行时所使用的进程描述符,对于普通进程而言,这两个指针变量的值相同。但是内核线程kernel thread是没有进程地址空间的,所以内核线程的tsk->mm域是空(NULL)。但是内核必须知道用户空间包含了什么,因此它的active_mm成员被初始化为前一个运行进程的active_mm值。
rss_stat被用来记录缓冲信息
brk_randomized用来确定对随机堆内存的探测

如果当前内核线程被调度之前运行的也是另外一个内核线程时候,那么其mm和avtive_mm都是NULL
判断标志

int exit_code, exit_signal;
int pdeath_signal;  /*  The signal sent when the parent dies  */
unsigned long jobctl;   /* JOBCTL_*, siglock protected */

/* Used for emulating ABI behavior of previous Linux versions */
unsigned int personality;

/* scheduler bits, serialized by scheduler locks */
unsigned sched_reset_on_fork:1;
unsigned sched_contributes_to_load:1;
unsigned sched_migrated:1;
unsigned :0; /* force alignment to the next boundary */

/* unserialized, strictly 'current' */
unsigned in_execve:1; /* bit to tell LSMs we're in execve */
unsigned in_iowait:1;
字段描述
exit_code用于设置进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)
exit_signal被置为-1时表示是某个线程组中的一员。只有当线程组的最后一个成员终止时,才会产生一个信号,以通知线程组的领头进程的父进程
pdeath_signal用于判断父进程终止时发送信号。
personality用于处理不同的ABI
in_execve用于通知LSM是否被do_execve()函数所调用
in_iowait用于判断是否进行iowait计数
sched_reset_on_fork用于判断是否恢复默认的优先级或调度策略

1.3.8 进程的内核栈

void *stack;//用来维护进程的内核栈。

Linux内核是通过以下的结构体来表示进程的内核栈:

union thread_union {
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

1)内核栈与线程描述符
对每个进程,Linux内核都把两个不同的数据结构紧凑的存放在一个单独为进程分配的内存区域中:
- 一个是内核态的进程堆栈;
- 另一个是紧挨着进程描述符的小数据结构thread_info,叫做线程描述符。
Linux把thread_info(线程描述符)和内核态的线程堆栈存放在一起,这块区域通常是8192K(占两个页框),其实地址必须是8192的整数倍。
在linux/arch/x86/include/asm/page_32_types.h中,

#define THREAD_SIZE_ORDER    1
#define THREAD_SIZE        (PAGE_SIZE << THREAD_SIZE_ORDER)

出于效率考虑,内核让这8K空间占据连续的两个页框并让第一个页框的起始地址是213的倍数。
内核态的进程访问处于内核数据段的栈,这个栈不同于用户态的进程所用的栈。
用户态进程所用的栈,是在进程线性地址空间中;
而内核栈是当进程从用户空间进入内核空间时,特权级发生变化,需要切换堆栈,那么内核空间中使用的就是这个内核栈。因为内核控制路径使用很少的栈空间,所以只需要几千个字节的内核态堆栈。
注:内核态堆栈仅用于内核例程,Linux内核另外为中断提供了单独的硬中断栈和软中断栈
但是较新的内核代码中,进程描述符task_struct结构中没有直接指向thread_info结构的指针,而是用一个void指针类型的成员表示,然后通过类型转换来访问thread_info结构。
相关代码在include/linux/sched.h中

#define task_thread_info(task)  ((struct thread_info *)(task)->stack)

2)内核栈数据结构描述thread_info和thread_union
thread_info是体系结构相关的,结构的定义在thread_info.h中

架构定义链接
x86linux-4.5/arch/x86/include/asm/thread_info.h, line 55
armlinux-4.5arch/arm/include/asm/thread_info.h, line 49
arm64linux/4.5/arch/arm64/include/asm/thread_info.h, line 47

Linux内核中使用一个联合体来表示一个进程的线程描述符和内核栈:

union thread_union
{
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};

3)获取当前在CPU上正在运行进程的thread_info

下面来说说如何通过esp栈指针来获取当前在CPU上正在运行进程的thread_info结构。
实际上,上面提到,thread_info结构和内核态堆栈是紧密结合在一起的,占据两个页框的物理内存空间。而且,这两个页框的起始起始地址是213对齐的。
早期的版本中,不需要对64位处理器的支持,所以,内核通过简单的屏蔽掉esp的低13位有效位就可以获得thread_info结构的基地址了。
我们在下面对比了,获取正在运行的进程的thread_info的实现方式

架构版本定义链接实现方式思路解析
x863.14current_thread_info(void)return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));屏蔽了esp的低十三位,最终得到的是thread_info的地址
x863.15current_thread_info(void)ti = (void *)(this_cpu_read_stable(kernel_stack) + KERNEL_STACK_OFFSET - THREAD_SIZE);
x864.1current_thread_info(void)(struct thread_info *)(current_top_of_stack() - THREAD_SIZE);

早期版本
当前的栈指针(current_stack_pointer == sp)就是esp,
THREAD_SIZE为8K,二进制的表示为0000 0000 0000 0000 0010 0000 0000 0000。
~(THREAD_SIZE-1)的结果刚好为1111 1111 1111 1111 1110 0000 0000 0000,低十三位是全为零,也就是刚好屏蔽了esp的低十三位,最终得到的是thread_info的地》址。

进程最常用的是进程描述符结构task_struct而不是thread_info结构的地址。为了获取当前CPU上运行进程的task_struct结构,内核提供了current宏,由于task_struct *task在thread_info的起始位置,该宏本质上等价于current_thread_info()->task,在include/asm-generic/current.h中定义:

#define get_current() (current_thread_info()->task)
#define current get_current()

这个定义与体系结构无关的,当然linux也为各个体系结构定义了更加方便或者快速的current
分配和销毁thread_info
进程通过alloc_thread_info_node函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈。

# if THREAD_SIZE >= PAGE_SIZE
static struct thread_info *alloc_thread_info_node(struct task_struct *tsk,
                          int node)
{
    struct page *page = alloc_kmem_pages_node(node, THREADINFO_GFP,
                          THREAD_SIZE_ORDER);

    return page ? page_address(page) : NULL;
}

static inline void free_thread_info(struct thread_info *ti)
{
    free_kmem_pages((unsigned long)ti, THREAD_SIZE_ORDER);
}
# else
static struct kmem_cache *thread_info_cache;

static struct thread_info *alloc_thread_info_node(struct task_struct *tsk,
                          int node)
{
    return kmem_cache_alloc_node(thread_info_cache, THREADINFO_GFP, node);
}

static void free_thread_info(struct thread_info *ti)
{
    kmem_cache_free(thread_info_cache, ti);
}

其中,THREAD_SIZE_ORDER宏的定义请查看

架构版本定义链接实现方式思路解析
x864.5arch/x86/include/asm/page_32_types.h, line 20define THREAD_SIZE_ORDER 1__get_free_pages函数分配2个页的内存(它的首地址是8192字节对齐的)
x86_644.5arch/x86/include/asm/page_64_types.h, line 10define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)

1.3.9 时间数据成员

cputime_t utime, stime, utimescaled, stimescaled;
    cputime_t gtime;
    cputime_t prev_utime, prev_stime;//记录当前的运行时间(用户态和内核态)
    unsigned long nvcsw, nivcsw; //自愿/非自愿上下文切换计数
    struct timespec start_time;  //进程的开始执行时间    
    struct timespec real_start_time;  //进程真正的开始执行时间
    unsigned long min_flt, maj_flt;
    struct task_cputime cputime_expires;//cpu执行的有效时间
    struct list_head cpu_timers[3];//用来统计进程或进程组被处理器追踪的时间
    struct list_head run_list;
    unsigned long timeout;//当前已使用的时间(与开始时间的差值)
    unsigned int time_slice;//进程的时间片的大小
    int nr_cpus_allowed;

1.3.10 信号处理信息

    struct signal_struct *signal;//指向进程信号描述符
    struct sighand_struct *sighand;//指向进程信号处理程序描述符
    sigset_t blocked, real_blocked;//表示被阻塞信号的掩码,real_blocked表示临时掩码
    sigset_t saved_sigmask;    /* restored if set_restore_sigmask() was used */
    struct sigpending pending;//进程上还需要处理的信号,存放私有挂起信号的数据结构
    unsigned long sas_ss_sp;//是信号处理程序备用堆栈的地址
    size_t sas_ss_size;//表示堆栈的大小

1.3.11 文件系统信息

/* filesystem information */
    struct fs_struct *fs;//文件系统的信息的指针
/* open file information */
    struct files_struct *files;//打开文件的信息指针

对task_struct的定义及注释如下:

 struct task_struct {
volatile long state;  //说明了该进程是否可以执行,还是可中断等信息
unsigned long flags;  //Flage 是进程号,在调用fork()时给出
int sigpending;    //进程上是否有待处理的信号
mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
                        //0-0xBFFFFFFF for user-thead
                        //0-0xFFFFFFFF for kernel-thread
//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatile long need_resched;
int lock_depth;  //锁深度
long nice;       //进程的基本时间片
//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //进程内存管理信息
int processor;
//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向运行队列的指针
unsigned long sleep_time;  //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages;       //指向本地页面      
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal;     //父进程终止时向子进程发送的信号
unsigned long personality;
//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
int did_exec:1; 
pid_t pid;    //进程标识符,用来代表一个进程
pid_t pgrp;   //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp;  //进程控制终端所在的组标识
pid_t session;  //进程的会话标识
pid_t tgid;
int leader;     //表示进程是否为会话主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group;   //线程链表
struct task_struct *pidhash_next; //用于将进程链入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;  //供wait4()使用
struct completion *vfork_done;  //供vfork() 使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值

//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer;   //指向实时定时器的指针
struct tms times;      //记录进程消耗的时间
unsigned long start_time;  //进程创建的时间
//记录进程在每个CPU上所消耗的用户态时间和核心态时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];   //进程正在运行的可执行文件名
 //文件系统信息
int link_count, total_link_count;
//NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct *tty;
unsigned int locks;
//进程间通信信息
struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct thread_struct thread;
  //文件系统信息
struct fs_struct *fs;
  //打开文件信息
struct files_struct *files;
  //信号处理函数
spinlock_t sigmask_lock;
struct signal_struct *sig; //信号处理函数
sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending;  //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;

spinlock_t alloc_lock;
void *journal_info;
};

可参看:https://blog.csdn.net/gatieme/article/details/51383272

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值