你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

介绍一个进程在内核眼里的来龙去脉

[复制链接]
gaosmile 发布时间:2020-10-5 19:52
本篇算是进程管理的的揭幕篇,简单介绍一个进程在内核眼里的来龙去脉,为接下来的进程创建,进程调度,进程管理等篇章做好学习准备。

从程序到进程再到内核
啥是程序,啥是进程,一张图可以给我们解释:

微信图片_20201005195032.png

我们转换镜头,站在内核OS的视角看什么是程序,什么是进程。ELF可执行文件送给内核后,OS是如何看待它的呢?换句话讲,内核OS眼里只有进程:

微信图片_20201005195036.jpg



通过 top 命令我们可以看到 linux 的各种进程(即上右图)。

内核通过 task_struct 描述进程
用命令 pstree 可以让内核以树形的结构把进程之间的关系列出来,如下图:


微信图片_20201005195039.png


这是进程在内核中的结构形式,那么内核是如何来以树形结构管理描述这些进程的呢?用来描述进程的数据结构,可以理解为进程的属性。比如进程的状态、进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,一起来看下今天的主角—— task_struct 结构体。

  1. struct task_struct {
  2. volatile long state;  //说明了该进程是否可以执行,还是可中断等信息 -1 unrunnable, 0 runnable, >0 stopped
  3. unsigned long flags;  //Flage 是进程号,在调用fork()时给出
  4. int sigpending;    //进程上是否有待处理的信号
  5. mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
  6.                         //0-0xBFFFFFFF for user-thead
  7.                         //0-0xFFFFFFFF for kernel-thread
  8. //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
  9. volatile long need_resched;
  10. int lock_depth;  //锁深度
  11. long nice;       //进程的基本时间片
  12. //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
  13. unsigned long policy;
  14. struct mm_struct *mm; //进程内存管理信息
  15. int processor;
  16. //若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
  17. unsigned long cpus_runnable, cpus_allowed;
  18. struct list_head run_list; //指向运行队列的指针
  19. unsigned long sleep_time;  //进程的睡眠时间
  20. //用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
  21. struct task_struct *next_task, *prev_task;
  22. struct mm_struct *active_mm;
  23. struct list_head local_pages;       //指向本地页面      
  24. unsigned int allocation_order, nr_local_pages;
  25. struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
  26. int exit_code, exit_signal;
  27. int pdeath_signal;     //父进程终止时向子进程发送的信号
  28. unsigned long personality;
  29. //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
  30. int did_exec:1;
  31. pid_t pid;    //进程标识符,用来代表一个进程
  32. pid_t pgrp;   //进程组标识,表示进程所属的进程组
  33. pid_t tty_old_pgrp;  //进程控制终端所在的组标识
  34. pid_t session;  //进程的会话标识
  35. pid_t tgid;
  36. int leader;     //表示进程是否为会话主管
  37. struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
  38. struct list_head thread_group;   //线程链表
  39. struct task_struct *pidhash_next; //用于将进程链入HASH表
  40. struct task_struct **pidhash_pprev;
  41. wait_queue_head_t wait_chldexit;  //供wait4()使用
  42. struct completion *vfork_done;  //供vfork() 使用
  43. unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
  44. struct timer_list real_timer;   //指向实时定时器的指针
  45. struct tms times;      //记录进程消耗的时间
  46. unsigned long start_time;  //进程创建的时间
  47. //记录进程在每个CPU上所消耗的用户态时间和核心态时间
  48. long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];
  49. int swappable:1; //表示进程的虚拟地址空间是否允许换出
  50. int ngroups; //记录进程在多少个用户组中
  51. gid_t groups[NGROUPS]; //记录进程所在的组
  52. //进程的权能,分别是有效位集合,继承位集合,允许位集合
  53. kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
  54. int keep_capabilities:1;
  55. struct user_struct *user;
  56. struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
  57. unsigned short used_math;   //是否使用FPU
  58. char comm[16];   //进程正在运行的可执行文件名
  59. //文件系统信息
  60. int link_count, total_link_count;
  61. //NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
  62. struct tty_struct *tty;
  63. unsigned int locks;
  64. //进程间通信信息
  65. struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
  66. struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
  67. //进程的CPU状态,切换时,要保存到停止进程的task_struct中
  68. struct thread_struct thread;
  69.   //文件系统信息
  70. struct fs_struct *fs;
  71.   //打开文件信息
  72. struct files_struct *files;
  73.   //信号处理函数
  74. spinlock_t sigmask_lock;
  75. struct signal_struct *sig; //信号处理函数
  76. sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
  77. struct sigpending pending;  //进程上是否有待处理的信号
  78. ......
  79. };
复制代码
内核就是通过list_head链表把各个进程关系以树形结构管理起来的。
task_struct 结构体内容太多,这里只列出部分成员变量,感兴趣的读者可以去源码 include/linux/sched.h头文件查看。task_struct 中的主要信息分类:

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

这些信息每类都可以单独开个章节去讲解,这里先简单描述下任务状态的转换,以后篇章再深入介绍各个分类。

任务状态转换
上面可以看到变量定义后面的注释,它说明变量内容<0是不运行的,=0是运行状态,>0是停止状态。下面我们介绍几个常用的取值:


微信图片_20201005195042.png


任务状态在不同情况下的状态转换如下:


微信图片_20201005195045.png


内核如何存放 task_struct
我们知道一个进程所占的栈空间有用户栈和内核栈,用户栈的分布方式见之前的文章《C语言在ARM中函数调用时,栈是如何变化的?》。那么内核栈是如何存放进程描述符的呢?
内核栈对于应用程序是不可见的,因为它位于内核空间中。在应用程序执行过程中,如果发生异常、中断或系统调用的话,应用程序会被暂停,系统进入内核态,转去执行异常响应等代码,这个时候所使用的栈就是内核栈。


微信图片_20201005195049.png


为了节省空间,linux把内核栈和紧挨着task_struct的thread_info放在一起,如上所示,thread_info中存放了进程/线程(内核不大区分进程与线程)的一些数据,其中包括指向task_struct结构的指针。数组stack即内核栈,stack占据8K/4K(依配置不同)空间。

  1. union thread_union {
  2. #ifndef CONFIG_THREAD_INFO_IN_TASK
  3.   struct thread_info thread_info;
  4. #endif
  5.   unsigned long stack[THREAD_SIZE/sizeof(long)];
  6. };
复制代码


最后
到这里应该已经了解了一个程序如何转换为进程,内核如何描述进程,又如何存储进程,当然还有很多关于进程的描述没有介绍,比如进程的调度,优先级,内存管理等等,这些会在以后的文章里单独分开详细介绍。但这些所有的信息都存储在今天的主角里——task_struct。

收藏 评论0 发布时间:2020-10-5 19:52

举报

0个回答

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版