linux进程描述符task_struct - 进程与线程(四)
Linux内核涉及到进程和程序的所有算法都围绕一个名为task_struct的数据结构建立,对于Linux内核把所有进程的进程描述符task_struct数据结构链成一个单链表(task_struct->tasks),该数据结构定义在include/sched.h中。这是系统的中主要的一个数据结构,其进程管理task_struct的结构图如下1 任务ID每一个任务都应用有一个ID,作为这个任
1. 各种进程ID
所谓进程其实就是执行中的程序而已,和静态的程序相比,进程是一个运行态的实体,拥有各种各样的资源:地址空间(未必使用全部地址空间,而是排布在地址空间上的一段段的memory mappings)、打开的文件、pending的信号、一个或者多个thread of execution,内核中数据实体(例如一个或者多个task_struct实体),内核栈(也是一个或者多个)等。针对进程,我们使用进程ID,也就是pid(process ID)。通过getpid和getppid可以获取当前进程的pid以及父进程的pid。
进程中的thread of execution被称作线程(thread),线程是进程中活跃状态的实体。一方面进程中所有的线程共享一些资源,另外一方面,线程又有自己专属的资源,例如有自己的PC值,用户栈、内核栈,有自己的hw context、调度策略等等。我们一般会说进程调度什么的,但是实际上线程才是是调度器的基本单位。对于Linux内核,线程的实现是一种特别的存在,和经典的unix都不一样。在linux中并不区分进程和线程,都是用task_struct来抽象,只不过支持多线程的进程是由一组task_struct来抽象,而这些task_struct会共享一些数据结构(例如内存描述符)。我们用thread ID来唯一标识进程中的线程,POSIX规定线程ID在所属进程中是唯一的,不过在linux kernel的实现中,thread ID是全系统唯一的,当然,考虑到可移植性,Application software不应该假设这一点。在用户空间,通过gettid函数可以获取当前线程的thread ID。对于单线程的进程,process ID和thread ID是一样的,对于支持多线程的进程,每个线程有自己的thread ID,但是所有的线程共享一个PID。
为了方便shell进行Job controll,我们需要把一组进程组织起来形成进程组。关于这方面的概念,在进程和终端文档中描述的很详细,这里就不赘述了。为了标识进程组,我们需要引入进程组ID的概念。我们一般把进程组中的第一个进程的ID作为进程组的ID,进程组中的所有进程共享一个进程组ID。在用户空间,通过setpgid、getpgid、setpgrp和getpgrp等接口函数可以访问process group ID。
经过thread ID、process ID、process group ID的层层递进,我们终于来到最顶层的ID,也就是session ID,这个ID实际上是用来标识计算机系统中的一次用户交互过程:用户登录入系统,不断的提交任务(即Job或者说是进程组)给计算机系统并观察结果,最后退出登录,销毁该session。关于session的概念,在进程和终端文档中描述的也很详细,大家可以参考那份文档,这里就不赘述了。在用户空间,我们可以通过getsid、setsid来操作session ID。
三、基础概念
1、用户空间如何看到process ID
我们用下面这个block diagram来描述用户空间和内核空间如何看待process ID的:
从用户空间来看,每一个进程都可以调用getpid来获取标识该进程的ID,我们称之PID,其类型是pid_t。因此,我们知道在用户空间可以通过一个正整数来唯一标识一个进程(我们称这个正整数为pid number)。在引入容器之后,事情稍微复杂一点,pid这个正整数只能是唯一标识容器内的进程。也就是说,如果有容器1和容器2存在于系统中,那么可以同时存在两个pid等于a的进程,分别位于容器1和容器2。当然,进程也可以不在容器里,例如进程x和进程y,它们就类似传统的linux系统中的进程。当然,你也可以认为进程x和进程y位于一个系统级别的顶层容器0,其中包括进程x和进程y以及两个容器。同样的概念,容器2中也可以嵌套一个容器,从而形成了一个container hierarchy。
容器(linux container)是一个OS级别的虚拟化方法,基本上是属于纯软件的方法来实现虚拟化,开销小,量级轻,当然也有自己的局限。linux container主要应用了内核中的cgroup和namespace隔离技术,当然这些内容不是我们这份文档关心的,我们这里主要关心pid namespace。
当一个进程运行在linux OS之上的时候,它拥有了很多的系统资源,例如pid、user ID、网络设备、协议栈、IP以及端口号、filesystem hierarchy。对于传统的linux,这些资源都是全局性的,一个进程umount了某一个文件系统挂载点,改变了自己的filesystem hierarchy视图,那么所有进程看到的文件系统目录结构都变化了(umount操作被所有进程感知到了)。有没有可能把这些资源隔离开呢?这就是namespace的概念,而PID namespace就是用来隔离pid的地址空间的。
进程是感知不到pid namespace的,它只是知道能够通过getpid获取自己的ID,并不知道自己实际上被关在一个pid namespace的牢笼。从这个角度看,用户空间是简单而幸福的,内核空间就没有这么幸运了,我们需要使用复杂的数据结构来抽象这些形成层次结构的PID。
最后顺便说一句,上面的描述是针对pid而言的,实际上,tid、pgid和sid都是一样的概念,原来直接使用这些ID就可以唯一标识一个实体,现在我们需要用(pid namespace,ID)来唯一标识一个实体。
2、内核空间如何看到process ID
虽然从用户空间看,一个pid用一个正整数表示就足够了,但是在内核空间,一个正整数肯定是不行的,我们用一个2个层次的pid namespace来描述(也就是上面图片的情形)。pid namespace 0是pid namespace 1和2的parent namespace,在pid namespace 1中的pid等于a的那进程,对应pid namespace 0中的pid等于m的那进程,也就是说,内核态实际需要两个不同namespace中的正整数来记录一个进程的ID信息。推广开来,我们可以这么描述,在一个n个level的pid namespace hieraray中,位于x level的进程需要x个正整数ID来表示该该进程。
除此之外,内核还有记录pid namespace之间的关系:谁是根,谁是叶,父子关系……
四、内核态的数据抽象
1、如何抽象pid number?
struct upid {
int nr;
struct pid_namespace *ns;
struct hlist_node pid_chain;
};
虽然用户空间使用一个正整数来表示各种IDs,但是对于内核,我们需要使用(pid namespace,ID number)这样的二元组来表示,因为单纯的pid number是没有意义的,必须限定其pid namespace,只有这样,那个ID number才是唯一的。这样,upid中的nr和ns成员就比较好理解了,分别对应ID number和pid namespace。此外,当userspace传递ID number参数进入内核请求服务的时候(例如向某一个ID发送信号),我们必须需要通过ID number快速找到其对应的upid数据对象,为了应对这样的需求,内核将系统内所有的upid保存在哈希表中,pid_chain成员是哈希表中的next node。
2、如何抽象tid、pid、sid、pgid?
struct pid
{
atomic_t count;
unsigned int level;
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
struct upid numbers[1];
};
虽然其名字是pid,不过实际上这个数据结构抽象了不仅仅是一个thread ID或者process ID,实际上还包括了进程组ID和session ID。由于多个task struct会共享pid(例如一个session中的所有的task struct都会指向同一个表示该session ID的struct pid数据对象),因此存在count这样的成员也就不奇怪了,表示该数据对象的引用计数。
在了解了pid namespace hierarchy之后,level成员也不难理解,任何一个系统分配的PID都是隶属于某一个namespace的,而这个namespace又是位于整个pid namespace hierarchy的某个层次上,pid->level指明了该PID所属的namespace的level。由于pid对其parent pid namespace也是可见的,因此,这个level值其实也就表示了这个pid对象在多少个pid namespace中可见。
在多少个pid namespace中可见,就会有多少个(pid namespace,pid number)对,numbers就是这样的一个数组,表示了在各个level上的pid number。tasks成员和使用该struct pid的task们关联,我们在下一节描述。
3、进程描述符中如何体现tid、pid、sid、pgid?
由于多个task共享ID(泛指上面说的四种ID),因此在设计数据结构的时候我们要考虑两种情况:
(1)从task struct快速找到对应的struct pid
(2)从struct pid能够遍历所有使用该pid的task
在这样的要求下,我们设计了一个辅助数据结构:
struct pid_link
{
struct hlist_node node;
struct pid *pid;
};
其中node是将task串接到struct pid的task struct链表中的节点,而pid指向具体的struct pid。这时候,我们可以在task struct中嵌入一个pid_link的数组:
struct task_struct {
……
struct pid_link pids[PIDTYPE_MAX];
……
}
Task struct中的pids成员是一个数组,分别表示该task的tid(pid)、pgid和sid。我们定义pid的类型如下:
enum pid_type
{
PIDTYPE_PID,
PIDTYPE_PGID,
PIDTYPE_SID,
PIDTYPE_MAX
};
一直以来我们都是说四种type,tid、pid、sid、pgid,为何这里少定义一种呢?其实开始版本的内核的确是定义了四种type的pid,但是后来为了节省内存,tid和pid合二为一了。OK,现在已经引入太多的数据结构,下面我们用一幅图片来描述数据结构之间的关系:
对于一个进程中的多个线程而言,每一个线程都可以通过task->pids[PIDTYPE_PID].pid找到该线程对应的表示thread ID的那个struct pid数据对象。当然,任何一个线程都有其所属的进程,也就是有表示其process id的那个struct pid数据对象。如何找到它呢?这需要一个桥梁,也就是task struct中定义的thread group 成员(task->group_leader),通过该指针,一个线程总是很容易的找到其对应的线程组leader,而线程组leader对应的pid就是该线程的process ID。因此,对于一个线程,其task->group_leader->pids[PIDTYPE_PID].pid就指向了表示其process id的那个struct pid数据对象。当然,对于线程组leader,其thread ID和process ID的struct pid数据对象是一个实体,对于非线程组leader的那些普通线程,其thread ID和process ID的struct pid数据对象指向不同的实体。
struct pid有三个链表头,如果该pid仅仅是标识一个thread ID,那么其pid链表头指向的链表中只有一个元素,就是使用该pid的task struct。如果该pid表示的是一个process ID,那么pid链表头指向的链表中多个task struct,每一个元素表示了属于该进程的线程的task struct,链表中第一个task struct是thread group leader。如果该pid并不表示一个process group ID或者session ID,那么struct pid中的pgid链表头和session链表头都是指向null。如果该pid表示一个process group ID的时候,其结构如下图所示:
对于那些multi-thread进程,内核有若干个task struct和进程对应,不过为了简单,在上面图片中,进程x 对应的task struct实际上是thread group leader对应的那个task struct。这些task struct的pgid指针(task->pids[PIDTYPE_PGID].pid)指向了该进程组对应的struct pid数据对象。而该pid中的pgid链表头串联了所有使用该pid的task struct(仅仅是串联thread group leader对应的那些task struct),而链表中的第一个节点就是进程组leader。
session pid的概念是类似的,大家可以自行了解学习。
4、如何抽象 pid namespace?
好吧,这个有点复杂,暂时TODO吧。
五、代码分析
1、如何根据一个task struct得到其对应的thread ID?
static inline struct pid *task_pid(struct task_struct *task)
{
return task->pids[PIDTYPE_PID].pid;
}
同样的道理,我们也可以很容易得到一个task对应的pgid和sid。process ID有一点绕,我们首先要找到该task的thread group leader对应的task,其实一个线程的thread group leader对应的那个task的thread ID就是该线程的process ID。
2、如何根据一个task struct得到当前的pid namespace?
struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
{
return ns_of_pid(task_pid(tsk));
}
这个操作可以分成两步,第一步首先找到其对应的thread ID,然后根据thread ID找到当前的pid namespace,代码如下:
static inline struct pid_namespace *ns_of_pid(struct pid *pid)
{
struct pid_namespace *ns = NULL;
if (pid)
ns = pid->numbers[pid->level].ns;
return ns;
}
一个struct pid实体是有层次的,对应了若干层次的(pid namespace,pid number)二元组,最顶层是root pid namespace,最底层(叶节点)是当前的pid namespace,pid->level表示了当前的层次,因此pid->numbers[pid->level].ns说明的就是当前的pid namespace。
3、getpid是如何实现的?
当陷入内核后,我们很容易获取当前的task struct(根据sp_svc的值),这是起点,后续的代码如下:
static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{
return pid_vnr(task_tgid(tsk));
}
通过task_tgid可以获取该task对应的thread group leader的thread ID,其实也就是process ID。此外,通过task_active_pid_ns亦可以获取当前的pid namespace,有了这两个参数,可以调用pid_nr_ns获取该task对应的pid number:
pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{
struct upid *upid;
pid_t nr = 0;if (pid && ns->level <= pid->level) {
upid = &pid->numbers[ns->level];
if (upid->ns == ns)
nr = upid->nr;
}
return nr;
}
一个pid可以贯穿多个pid namespace,但是并非所有的pid namespace都可以检视pid,获取相应的pid number。因此,在代码的开始会进行验证,如果pid namespace的层次(ns->level)低于pid当前的pid namespace的层次,那么直接返回0。如果pid namespace的level是OK的,那么要检查该namespace是不是pid当前的那个pid namespace,如果是,直接返回对应的pid number,否则,返回0。
对于gettid和getppid这两个接口,整体的概念是和getpid类似的,不再赘述。
4、给定线程ID number的情况下,如何找对应的task struct?
这里给定的条件包括ID number、当前的pid namespace,在这样的条件下如何找到对应的task呢?我们分成两个步骤,第一个步骤是先找到对应的struct pid,代码如下:
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
struct upid *pnr;hlist_for_each_entry_rcu(pnr,
&pid_hash[pid_hashfn(nr, ns)], pid_chain)
if (pnr->nr == nr && pnr->ns == ns)
return container_of(pnr, struct pid,
numbers[ns->level]);return NULL;
}
整个系统有那么多的struct pid数据对象,每一个pid又有多个level的(pid namespace,pid number)对,通过pid number和namespace来找对应的pid是一件非常耗时的操作。此外,这样的操作是一个比较频繁的操作,一个简单的例子就是通过kill向指定进程(pid number)发送信号。正是由于操作频繁而且耗时,系统建立了一个全局的哈希链表来解决这个问题,pid_hash指向了若干(具体head的数量和内存配置有关)哈希链表头。这个哈希表用来通过一个指定pid namespace和id number,来找到对应的struct upid。一旦找了upid,那么通过container_of找到对应的struct pid数据对象。
第二步是从struct pid找到task struct,代码如下:
struct task_struct *pid_task(struct pid *pid, enum pid_type type)
{
struct task_struct *result = NULL;
if (pid) {
struct hlist_node *first;
first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]),
lockdep_tasklist_lock_is_held());
if (first)
result = hlist_entry(first, struct task_struct, pids[(type)].node);
}
return result;
}
5、getpgid是如何实现的?
SYSCALL_DEFINE1(getpgid, pid_t, pid)
{
struct task_struct *p;
struct pid *grp;
int retval;rcu_read_lock();
if (!pid)
grp = task_pgrp(current);
else {
retval = -ESRCH;
p = find_task_by_vpid(pid);
if (!p)
goto out;
grp = task_pgrp(p);
if (!grp)
goto out;retval = security_task_getpgid(p);
if (retval)
goto out;
}
retval = pid_vnr(grp);
out:
rcu_read_unlock();
return retval;
}
当传入的pid number等于0的时候,getpgid实际上是获取当前进程的process groud ID number,通过task_pgrp可以获取该进程的使用的表示progress group ID对应的那个pid对象。如果调用getpgid的时候给出了非0的process ID number,那么getpgid实际上是想要获取指定pid number的gpid。这时候,我们需要调用find_task_by_vpid找到该pid number对应的task struct。一旦找到task struct结构,那么很容易得到其使用的pgid(该实体是struct pid类型)。至此,无论哪一种参数情况(传入的参数pid number等于0或者非0),我们都找到了该pid number对应的struct pid数据对象(pgid)。当然,最终用户空间需要的是pgid number,因此我们需要调用pid_vnr找到该pid在当前namespace中的pgid number。
getsid的代码逻辑和getpid是类似的,不再赘述。
2. task_struct
对于内核,进程相关的调度都是通过struct task_struct来实现的,一个容量只有 64 大小的数组,数组中的元素是 task_struct 结构。
struct task_struct *task[NR_TASKS]
struct task_struct
{
/* these are hardcoded - don't touch */
long state; /* -1 unrunnable, 0 runnable, >0 stopped */
long counter;
long priority;
long signal;
struct sigaction sigaction[32];
long blocked; /* bitmap of masked signals */
/* various fields */
int exit_code;
unsigned long start_code, end_code, end_data, brk, start_stack;
long pid, father, pgrp, session, leader;
unsigned short uid, euid, suid;
unsigned short gid, egid, sgid;
long alarm;
long utime, stime, cutime, cstime, start_time;
unsigned short used_math;
/* file system info */
int tty; /* -1 if no tty, so it must be signed */
unsigned short umask;
struct m_inode *pwd;
struct m_inode *root;
struct m_inode *executable;
unsigned long close_on_exec;
struct file *filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
struct desc_struct ldt[3];
/* tss for this task */
struct tss_struct tss;
};
Linux内核涉及到进程和程序的所有算法都围绕一个名为task_struct的数据结构建立,对于Linux内核把所有进程的进程描述符task_struct数据结构链成一个单链表(task_struct->tasks),该数据结构定义在include/sched.h中。这是系统的中主要的一个数据结构,其进程管理task_struct的结构图如下
1 任务ID
每一个任务都应用有一个ID,作为这个任务的唯一标识。pid是进程的ID,tgid是线程的group ID,group_leader是进程组的组长。
任何一个进程,如果只有主线程,那么pid是自己,tgid也是自己,group_leader 指向的还是自己。但是如果一个进程创建了其他线程,那么就会发生变化。线程有自己的pid,tgid就是进程的主线程的pid,group_leader指向是进程的主线程。
getpid系统调用返回当前进程的TGID,而不是线程的pid,因为一个多线程应用程序中所有的线程共享相同的pid
gettid()系统调用返回线程的PID
group_leader ,通过clone创建的所有线程的task_struct的group_leader 成员,会指向组长的task_struct实例
2 亲属关系
在Linux系统中,所有进程之间都有着直接或间接地联系,每个进程都有其父进程,也可能有零个或多个子进程。拥有同一父进程的所有进程具有兄弟关系。
成员 描述
real_parent 指向其父进程,如果创建它的父进程不再存在,则指向pid为1的Init进程
parent 指向其父进程,当他终止时,必须向他的父进程发送信号,它的值通常与real_parent相同
children 表示链表的头部,链表的所有元素都是它的子进程
sibling 用于把它当前进程插入到兄弟链表中
3 任务状态
成员 描述
state 用于记录进程的状态
exit_code、exit_signal、exit_state 用于存放进程退出值和终止信号,这样父进程就知道子进程的退出原因
flags 用于描述进程属性的一些标志位
记录进程状态
在进程的生命周期中,我们定义了各个状态,linux内核也为进程定义了5中状态,其状态如下所示
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
1
2
3
4
5
TASK_RUNNING(可运行态或就绪态或者正在运行态):这个状态的名字是正在运行的意思,可是在Linux内核里不一定是指正在运行,所以很容易被人误解。
它的意思是指进程处于可运行状态,或正在运行,或在就绪队列中等待运行
TASK_INTERRUPTIBLE(可中断睡眠状态):进程进入睡眠状态(被阻塞)来等待某些条件的达成或者某些资源的就位,一旦条件达成或者资源到位,内核就可以把这个进程的状态设置成TASK_RUNNING并加其加到就绪队列。
这是一种浅睡眠的状态,也就是说,虽然在睡眠,等待 I/O 完成,但是这个时候一个信号来的时候,进程还是要被唤醒。只不过唤醒后,不是继续刚才的操作,而是进行信号处理。当然程序员可以根据自己的意愿,来写信号处理函数,例如收到某些信号,就放弃等待这个 I/O 操作完成,直接退出,也可也收到某些信息,继续等待。
TASK_UNINTERRUPTIBLE(不可中断睡眠态):进程在睡眠等待时不受干扰,不可被信号唤醒,只能死等I/O操作完成。一旦I/O操作因为特殊原因不能完成,这个时候,谁也叫不醒这个进程。
通过ps命令看到的被标记为D状态的进程,就属于不可中断态的进程,不可以发送SIGKILL信号使它终止,也有人把这个状态称为深度睡眠状态
TASK_STOPPED(终止态):在进程接收到 SIGSTOP、SIGTTIN、SIGTSTP 或者 SIGTTOU 信号之后进入该状态。
EXIT_ZOMBIE(僵尸态):进程已经消亡,但是task_struct数据结构还没有释放,每个进程在它的声明周期中都要经历这几个状态,子进程退出时,父进程可以通过wait()或者waitpid()来获取子进程的消亡原因。
4. 进程权限
unix是诞生于20世纪70年代的分时多任务多用户操作系统,当时的场景是许多用户同时登陆到一台主机,运行多个各自的进程。
linux使用一个整数来标记运行进程的用户,这个整数倍称为user id,简称uid。人通常被分组,比如这几个人在研发工作,被分到研发组,那几个人做销售工作,被分配到销售组。linux用另外一个整数来标记用户组,这个被称为group id,简称gid。
uid和gid是操作系统自主访问控制的基础。
成员 描述
real_cred 说明谁能操作我这个进程
cred 说明这个进程能够操作谁
struct cred {
......
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 */
......
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 */
......
} __randomize_layout;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
5 运行统计
在进程的运行过程中,会有一些统计量,具体你可以看下面的列表。这里面有进程在用户态和内核态消耗的时间、上下文切换的次数等等。
成员 描述
utime 用户态消耗的CPU时间
stime 内核态消耗的CPU时间
nvcsw 自愿上下文切换计数
nivcsw 非资源上下文切换计数
start_time 进程启动时间,不包括睡眠时间
real_start_time 进程启动时间,包括睡眠时间
6 调度相关
进程的状态切换往往涉及调度,下面这些字段都是用于调度的。本次只是进行概要说明,后面进程调度会详细分析
成员 描述
on_rq 是否在运行队列上
prio 用于保存动态优先级
static_prio 用于保存静态优先级,可以通过nice系统调用来进行修改
normal_prio 取决于静态优先级和调度策略
sched_class 调度类
se 普通进程的调用实体,每个进程都有其中之一的实体
policy 调度策略
7 信号处理
主要定义了哪些信号被阻塞暂不处理(blocked),哪些信号尚等待处理(pending),哪些信号正在通过信号处理函数进行处理(sighand)。处理的结果可以是忽略,可以是结束进程等等。
成员 描述
signal 指向进程的信号描述符
sighand 指向进程的信号处理程序描述符
blocked 表示被阻塞信号的掩码,real_blocked表示临时掩码
pending 存放私有挂起信号的数据结构
8 内存管理
每个进程都有自己独立的虚拟内存空间,这需要有一个数据结构来表示,就是 mm_struct,之前在内存管理章节中有学习,详细见linux内存管理笔记(三十)----进程虚拟地址
成员 描述
mm 程所拥有的用户空间内存描述符,内核线程无的mm为NULL
active_mm active_mm指向进程运行时所使用的内存描述符, 对于普通进程而言,这两个指针变量的值相同。但是内核线程kernel thread是没有进程地址空间的,所以内核线程的tsk->mm域是空(NULL)。但是内核必须知道用户空间包含了什么,因此它的active_mm成员被初始化为前一个运行进程的active_mm值。
9 文件与文件系统
每个进程有一个文件系统的数据结构,还有一个打开文件的数据结构。这个我们放到文件系统那一节详细讲述。
成员 描述
fs 文件系统的数据结构
files 表示进程当前打开的文件数据结构
10 内核栈
在程序在程序执行过程中,一旦调用到系统调用,就需要进入内核继续执行。那如何将用户态的执行和内核态的执行串起来呢?这就需要以下两个重要的成员变量:
成员 描述
thread_info 是 task_struct 的补充, 存储于体系结构有关的内容
那如果一个当前在某个 CPU 上执行的进程,想知道自己的 task_struct 在哪里,又该怎么办呢?
struct thread_info {
unsigned long flags; /* low level flags */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
int preempt_count; /* 0 => preemptable, <0 => bug */
int cpu; /* cpu */
};
1
2
3
4
5
6
7
这里面有个成员变量 task 指向 task_struct,所以我们常用 current_thread_info()->task 来获取 task_struct,后面章节中会详细学习。
11 总结
通过这一节的学习,你会发现,一个进程的运行竟然要保存这么多信息,这些信息都可以通过命令行取出来,后面我们单独章节来学习怎么查看这些信息。
更多推荐
所有评论(0)