17

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

 3 years ago
source link: https://blog.popkx.com/linux-learning-10-process-descriptor-and-task-structure/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

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

2e25d794aa31e3fc5c9877ea88aab446.png

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

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

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

06f973b77675bf7046ac58e2e969dda6.png

链表算是 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结构体的地址。

04981ba605b455c4a247f24562b90e06.png

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,表示进程停止执行,不能被投入运行。
550f94bb43dd2c9bdf90f66b4f6933c9.png

在 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 里。

31783c2720fbb9a052ba17103413bc86.png

使用该宏遍历进程的进程非常简单:
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 是系统的第一个进程,所以此处是遍历重点。


Recommend

  • 69

  • 44
    • 微信 mp.weixin.qq.com 5 years ago
    • Cache

    RPC 服务器之多进程描述符传递高阶模型

    今天老师要给大家介绍一个比较特别的 RPC 服务器模型,这个模型不同于 Nginx、不同于 Redis、不同于 Apache、不同于 Tornado、不同于 Netty,它的原型是 Node Cluster 的多进程并发模型。 Nginx 并发模型 ...

  • 22

    前言 最近Mr汤进er在学习PRD的写作。直接的感触就是:写PRD是一个技术活,也是一个细心活。PRD的主要阅览用户就是开发工程师,为了能够和开放人员进行高效的沟通,一份优秀的PRD文档应该满足的基本要求包括:完整、准确、清晰、简洁和稳定。其中”完整

  • 14

    说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案: 在 Linux 系统中,进程和线程几乎没有区别 。 Linux 中的进程其实就是一个数据结构,顺带可以理解文件描述符、重定...

  • 3

    还记得刚开始接触 C 语言的时候,为了描述一个平行四边形的边长和对角线长,我定义了四个变量:短边长 a,长边长 b,对角线1长 d1,对角线2长 d2。在写代码的过程中,发现又要定义一个平行四边形,于是我不得不又定义了四个变量:a2,b2,d12,d22,结果变量又...

  • 4

    Linux学习笔记 1 Linux基本操作及其文件系统结构 Published at: 2013-11-19   |   Reading: 690 words ~2min   |   PV/UV: /

  • 2
    • iminto.github.io 2 years ago
    • Cache

    进程Socket描述符的那些事

    前几天看到有人发的一个面试题,问的是MySQL连接的进程描述符的问题。 在Linux里,一切皆文件,那进程描述符,实际就是文件描述符了。 我们还知道Linux 内核提供了一种通过 proc文件系统,/proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方...

  • 6

    关闭子进程打开的文件描述符 2022-08-30...

  • 5
    • www.51cto.com 1 year ago
    • Cache

    Linux中查看进程描述符

    Linux中查看进程描述符 作者:inkfoxer 2023-04-06 15:22:15 在 Linux 中,可以使用 ls -l /proc/<PID>/fd 命令来查看指定进程的所有打开的文件描述符。其中 <PID> 是要查看的进程的进程标识符。

  • 3

    Linux之从进程角度来理解文件描述符 文件描述符...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK