• 好好学习,天天向上~
  • 欢迎欢迎~

linux学习10,进程描述符及其任务结构

linux笔记 lcc 291℃ 0评论

第8节提到 linux 中,进程不仅是执行期的程序,它实际上是各种资源混杂的集合,那么“各种资源”究竟是指什么呢?这里不打算一句一句描述出“各种资源”,因为直接查看代码更加清楚。

用于描述 linux 进程的 task_struct 结构体

linux 内核中的进程都是用task_struct结构体描述的,进程的“各种资源”就包含在这个结构体里。借助《上一节》配置好的的 vim 阅读器,轻易就能定位出task_struct结构体的 C 语言代码:

task_struct 结构体定义在 include/linux/sched.h 里。

这个结构体相当巨大,有数 KB,查看 task_struct 结构体的各个成员,就知道 linux 的进程包含的“各种资源”包括:打开的文件,进程的地址空间,挂起的信号,进程的状态,等等。其他更多信息,可以自行查看源码。

linux 内核常常需要管理不止一个进程,所以就会有多个 task_struct 结构体需要管理,内核是通过双向循环链表管理的,如下图:

链表算是 C 语言中一种非常重要的数据类型了,我的文章还没有介绍过,后面会介绍的,敬请关注!!

task_struct结构体记录着进程的信息,所以 linux 内核会比较频繁用到这个结构体,怎样才能快速找到它呢?对于寄存器比较富足的硬件体系结构,这是相当简单的事情,分配一个寄存器记录task_struct结构体的地址就行了。但是对于 x86 这样寄存器较少的硬件体系结构,就只能另想他法了。

不仅是 x86 结构,其他寄存器较少的硬件体系结构,linux 内核都是间接计算task_struct结构体地址的。我们知道,每个进程都有自己的栈空间,不同于用户空间的进程, linux 内核进程的栈空间都是固定大小的,所以栈顶或者栈底的地址都是可知的。因此,linux 在这里创建了 thread_info 结构体,该结构体的定义如下:

struct thread_info {
    struct task_struct  *task;      /* main task structure */
    struct exec_domain  *exec_domain;   /* execution domain */
    __u32           flags;      /* low level flags */
    __u32           status;     /* thread synchronous flags */
    __u32           cpu;        /* current CPU */
    int             preempt_count;  /* 0 => preemptable,
                           <0 => BUG */
    mm_segment_t        addr_limit;
    struct restart_block    restart_block;
#ifdef CONFIG_IA32_EMULATION
    void __user     *sysenter_return;
#endif
};

它记录着task_struct结构体的地址,所以 linux 内核可以通过进程栈计算出thread_info的地址,进而得到task_struct结构体的地址。

thread_info 结构体定义在 include/asm/thread_info.h 里。

进程的状态

我们再回到 linux 内核的task_struct结构体,它的第一个成员 state 记录了进程的当前状态,它一共只有 5 种状态,也必定是这 5 种状态之一:

  • TASK_RUNNING,表示进程是可执行的,或者正在运行,或者正在运行队列里排队等待运行。
  • TASK_INTERRUPTIBLE,表示进程正在睡眠,并且可能随时被唤醒信号唤醒,唤醒后,进程会被设置为 TASK_RUNNING。
  • TASK_UNINTERRUPTIBLE,表示进程正在睡眠,不会被信号唤醒。
  • ‘__TASK_TRACED,表示进程正在被其他进程跟踪,例如正在被 gdb 调试的进程就会是这个状态。
  • ‘__TASK_STOPPED,表示进程停止执行,不能被投入运行。

在 linux 内核中,可以通过 set_task_state(task, state) 宏设置进程状态,它的定义如下:

#define set_task_state(tsk, state_value)        \
    set_mb((tsk)->state, (state_value))
// 继续跟踪
#define set_mb(var, value) do { var = value; barrier(); } while (0)

可以看出,这个宏其实就是简单的赋值,只是多了内存屏障用于强制其他处理器做重新排序。

set_task_state 宏位于 include/linux/sched.h 里。

进程的家族树

在 linux 中,所有进程都有着明显的“家族关系”,所有进程都有父进程,也能有自己的子进程树,拥有同一个父进程的进程们是“兄弟进程”。事实上,用于描述进程的 task_struct 结构体有一个 parent 指针,指向它的父进程,还包含一个子进程链表指针 children,用于维护它的子进程们。因此查询当前进程的父进程和子进程非常容易:

struct task_struct* parent = current->parent;
struct task_struct* children = current->children;

遍历内核的全部进程也非常简单,实际上,linux 内核提供了用于遍历进程的宏 list_for_each,它的定义如下:

#define list_for_each(pos, head) \
    for (pos = (head)->next; prefetch(pos->next), pos != (head); \
            pos = pos->next)

list_for_each 宏定义位于 include/linux/list.h 里。


使用该宏遍历进程的进程非常简单:

struct task_struct* task;
struct list_head* list;
list_for_each(list, &current->children){
    task = list_entry(list, struct task_struct, sibling);
}

因为进程们的“家族关系”,依次遍历进程的父进程也非常简单:

for(task=current; task!=&init_task; task=task->parent);

因为 init_task 是系统的第一个进程,所以此处是遍历重点。


linux学习10,进程描述符及其任务结构 - 刘冲的博客(https://blog.popkx.com) 原创版权所有,转载请注明出处。
点赞 (0)分享 (0)
发表我的评论
取消评论
表情     9 + 7 = ? (必填)