0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux的进程

strongerHuang 来源:嵌入式Hacker 作者:吴伟东 2020-11-29 09:51 次阅读

目的:

初步了解进程描述符 task_struct。

目录:

Linux的进程

Linux 的进程描述符

task_struct

内核如何找到 task_struct

task_struct 的分配和初始化

实验:打印 task_struct / thread_info / kernel mode stack

环境:

Linux-4.14 + ARMv7

1. Linux 的进程

进程的术语是 process,是 Linux 最基础的抽象,另一个基础抽象是文件。

最简单的理解,进程就是执行中 (executing, 不等于running) 的程序。

更准确一点的理解,进程包括执行中的程序以及相关的资源(包括cpu状态、打开的文件、挂起的信号、tty、内存地址空间等)。

一种简洁的说法:进程 = n*执行流 + 资源,n>=1。

Linux 进程的特点:

通过系统调用 fork() 创建进程,fork() 会复制现有进程来创建一个全新的进程。

内核里,并不严格区分进程和线程。

从内核的角度看,调度单位是线程 (即执行流)。可以把线程看做是进程里的一条执行流,1个进程里可以有1个或者多个线程。

内核里,常把进程称为 task 或者 thread,这样描述更准确,因为许多进程就只有1条执行流。

内核通过轻量级进程 (lightweight process) 来支持多线程。1个轻量级进程就对应1个线程,轻量级进程之间可以共享打开的文件、地址空间等资源。

2. Linux 的进程描述符

2.1 task_struct

内核里,通过 task_struct 结构体来描述一个进程,称为进程描述符 (process descriptor),它保存着支撑一个进程正常运行的所有信息

每一个进程,即便是轻量级进程(即线程),都有1个 task_struct。

sched.h(includelinux) structtask_struct{ structthread_infothread_info; volatilelongstate; void*stack; [...] structmm_struct*mm; [...] pid_tpid; [...] structtask_struct*parent; [...] charcomm[TASK_COMM_LEN]; [...] structfiles_struct*files; [...] structsignal_struct*signal; }

这是一个庞大的结构体,不仅有许多进程相关的基础字段,还有许多指向其他数据结构的指针。

它包含的字段能完整地描述一个正在执行的程序,包括 cpu 状态、打开的文件、地址空间、挂起的信号、进程状态等。

点击查看大图

作为初学者,先简单地了解部分字段就好::

struct thread_info thread_info:进程底层信息,平台相关,下面会详细描述。

long state:进程当前的状态,下面是几个比较重要的进程状态以及它们之间的转换流程。

点击查看大图

void *stack:指向进程内核栈,下面会解释。

struct mm_struct *mm:与进程地址空间相关的信息都保存在一个叫内存描述符 (memory descriptor) 的结构体 (mm_struct) 中。

点击查看大图

pid_t pid:进程标识符,本质就是一个数字,是用户空间引用进程的唯一标识。

struct task_struct *parent:父进程的 task_struct。

char comm[TASK_COMM_LEN]:进程的名称。

struct files_struct*files:打开的文件表。

struct signal_struct *signal:信号处理相关。

其他字段,等到有需要的时候再回过头来学习。

2.2 当发生系统调用或者进程切换时,内核如何找到 task_struct ?

对于 ARM 架构,答案是:通过内核栈 (kernel mode stack)。

为什么要有内核栈?

因为内核是可重入的,在内核中会有多条与不同进程相关联的执行路径。因此不同的进程处于内核态时,都需要有自己私有的进程内核栈 (process kernel stack)。

当进程从用户态切换到内核态时,所使用的栈会从用户栈切换到内核栈。

至于是如何切换的,关键词是系统调用,这不是本文关注的重点,先放一边,学习内核要懂得恰当的时候忽略细节。

当发生进程切换时,也会切换到目标进程的内核栈。

同上,关键词是硬件上下文切换 (hardware context switch),忽略具体实现。

无论何时,只要进程处于内核态,就会有内核栈可以使用,否则系统就离崩溃不远了。

ARM 架构的内核栈和 task_struct 的关系如下:

内核栈的长度是 THREAD_SIZE,对于 ARM 架构,一般是 2 个页框的大小,即 8KB。

内核将一个较小的数据结构 thread_info 放在内核栈的底部,它负责将内核栈和 task_struct 串联起来。thread_info 是平台相关的,在 ARM 架构中的定义如下:

//thread_info.h(archarmincludeasm) structthread_info{ unsignedlongflags;/*lowlevelflags*/ intpreempt_count;/*0=>preemptable,<0 =>bug*/ mm_segment_taddr_limit;/*addresslimit*/ structtask_struct*task;/*maintaskstructure*/ [...] structcpu_context_savecpu_context;/*cpucontext*/ [...] };

thread_info 保存了一个进程能被调度执行的最底层信息(low level task data),例如struct cpu_context_save cpu_context 会在进程切换时用来保存/恢复寄存器上下文。

内核通过内核栈的栈指针可以快速地拿到 thread_info:

//thread_info.h(includelinux) staticinlinestructthread_info*current_thread_info(void) { //current_stack_pointer是当前进程内核栈的栈指针 return(structthread_info*) (current_stack_pointer&~(THREAD_SIZE-1)); }

然后通过 thread_info 找到 task_struct:

//current.h(includeasm-generic) #definecurrent(current_thread_info()->task)

内核里通过 current 宏可以获得当前进程的 task_struct。

2.3 task_struct 的分配和初始化

当上层应用使用 fork() 创建进程时,内核会新建一个 task_struct。

进程的创建是个复杂的工作,可以延伸出无数的细节。这里我们只是简单地了解一下 task_struct 的分配和部分初始化的流程。

fork() 在内核里的核心流程:

dup_task_struct() 做了什么?

至于设置内核栈里做了什么,涉及到了进程的创建与切换,不在本文的关注范围内,以后再研究了。

3. 实验:打印 task_struct / thread_info / kernel mode stack

实验目的:

梳理 task_struct / thread_info / kernel mode stack 的关系。

实验代码:

#include #include #include staticvoidprint_task_info(structtask_struct*task) { printk(KERN_NOTICE"%10s%5dtask_struct(%p)/stack(%p~%p)/thread_info->task(%p)", task->comm, task->pid, task, task->stack, ((unsignedlong*)task->stack)+THREAD_SIZE, task_thread_info(task)->task); } staticint__inittask_init(void) { structtask_struct*task=current; printk(KERN_INFO"taskmoduleinit "); print_task_info(task); do{ task=task->parent; print_task_info(task); }while(task->pid!=0); return0; } module_init(task_init); staticvoid__exittask_exit(void) { printk(KERN_INFO"taskmoduleexit "); } module_exit(task_exit);

运行效果:

taskmoduleinit insmod3123task_struct(edb42580)/stack(ed46c000~ed474000)/thread_info->task(edb42580) bash2393task_struct(eda13e80)/stack(c9dda000~c9de2000)/thread_info->task(eda13e80) sshd2255task_struct(ee5c9f40)/stack(c9d2e000~c9d36000)/thread_info->task(ee5c9f40) sshd543task_struct(ef15f080)/stack(ee554000~ee55c000)/thread_info->task(ef15f080) systemd1task_struct(ef058000)/stack(ef04c000~ef054000)/thread_info->task(ef058000)

在程序里,我们通过 task_struct 找到 stack,然后通过 stack 找到 thread_info,最后又通过 thread_info->task 找到 task_struct。

4. 相关参考

Linux 内核设计与实现 / 第 3.1 章节

深入理解 Linux 内核 / 3

Linux 内核深度解析 / 2.5.1

深入Linux 内核架构 / 2.3

责任编辑:lq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10942

    浏览量

    206544
  • 多线程
    +关注

    关注

    0

    文章

    267

    浏览量

    19710
  • 进程
    +关注

    关注

    0

    文章

    190

    浏览量

    13867

原文标题:Linux 内核 / 进程管理 / 如何描述一个进程?

文章出处:【微信号:strongerHuang,微信公众号:strongerHuang】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    浅谈Linux进程

    进程和程序的区别: 进程是动态的,程序是静态的 一、进程的创建(fork()函数) int main(){ pid_t pid; pid=fork(); if(pid
    的头像 发表于 01-28 15:54 83次阅读
    浅谈<b class='flag-5'>Linux</b>的<b class='flag-5'>进程</b>

    Linux进程管理和启动流程方法

      一、计算用户ID总和 使用while read line和/etc/passwd,计算用户id总和。 #!/bin/bash # 初始化用户ID和总和变量 uid=0 sum=0 while read line; do # 从 /etc/passwd 文件中提取用户ID uid=$( echo " $line " | cut -d ':' -f3) # 将用户ID添加到总和中 sum=$((sum + uid)) done # 输出ID总和 echo "用户ID总和为: $sum " 上面的脚本首先定义了两个变量 uid 和 sum,分别用于存储当前行的用户ID和所有用户ID的总和。然后使用 while 循环逐行读取 /etc/passwd 文件的内容,每次循环提取当前行的第三个字段,也
    的头像 发表于 01-13 10:39 275次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>进程</b>管理和启动流程方法

    Linux进程地址空间详解

    RAM 的某些部分永久地分配给内核, 并用来存放内核代码以及静态内核数据结构. RAM 的其余部分称为动态内存 (dynamic memory). 动态内存不仅是进程所需的宝贵资源, 也是内核本身
    的头像 发表于 12-18 09:45 318次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>进程</b>地址空间详解

    如何实现一套linux进程间通信的机制

    我们知道linux进程的间通信的组件有管道,消息队列,socket, 信号量,共享内存等。但是我们如果自己实现一套进程间通信的机制的话,要怎么做?了解android 开发的可能会知道
    的头像 发表于 11-10 14:56 352次阅读
    如何实现一套<b class='flag-5'>linux</b><b class='flag-5'>进程</b>间通信的机制

    什么是Linux进程调度器

    片)。调度器使得我们同时执行多个程序成为可能,因此可以与具有各种需求的用户共享CPU。 内核必须提供一种方法, 在各个进程之间尽可能公平地共享CPU时间, 而同时又要考虑不同的任务优先级. 调度器的一个重要目标是有效地分配 CPU 时间片,同时
    的头像 发表于 11-09 09:05 239次阅读
    什么是<b class='flag-5'>Linux</b><b class='flag-5'>进程</b>调度器

    Linux进程间如何实现共享内存通信

    在上面的例程中,我们首先使用ftok()函数生成一个key值作为共享内存的标识符。然后使用shmget()函数创建共享内存区域,shmaddr指向共享内存区域的起始地址。
    发表于 06-19 09:55 403次阅读

    Linux进程的睡眠和唤醒

    Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为 TASK_RUNNING。一旦一个运行中的
    发表于 06-07 12:26 259次阅读

    进程控制4.2.3Linux进程控制(2)#操作系统

    操作系统
    学习硬声知识
    发布于 :2023年05月25日 18:40:58

    进程控制4.2.3Linux进程控制(1)#操作系统

    操作系统
    学习硬声知识
    发布于 :2023年05月25日 18:40:08

    Linux进程调度(2)#操作系统

    Linux
    学习硬声知识
    发布于 :2023年05月25日 17:10:48

    Linux进程调度(1)#操作系统

    Linux
    学习硬声知识
    发布于 :2023年05月25日 17:10:12

    Linux进程间共享内存通信时如何同步?

    今天我们来讲讲进程间使用共享内存通信时为了确保数据的正确,如何进行同步?
    的头像 发表于 05-11 18:25 1161次阅读

    Linux进程间如何实现共享内存通信

    这次我们来讲一下Linux进程通信中重要的通信方式:共享内存作为Linux软件开发攻城狮,进程间通信是必须熟练掌握的重要技能,而共享内存是在程序开发中常用的也是重要的一种
    发表于 04-26 17:14 565次阅读

    Linux进程管理命令介绍

    执行中的程序称作进程。当可执行文件存放在存储中,并且运行的时候,每个进程会被动态得分配系统资源、内存、安全属性和与之相关的状态。可以有多个进程关联到同一个程序,并同时执行不会互相干扰。操作系统会有效地管理和追踪所有运行着的
    发表于 04-15 11:55 311次阅读

    如何使用PS实时监控Linux进程状态?

    当有一种情况,我们需要像上面第四点中提到的通过CPU和内存的使用率来筛选进程,并且我们希望结果能够每秒刷新一次。为此,我们可以将ps命令和watch命令结合起来。
    发表于 04-04 09:23 742次阅读