Linux内核进程相关知识点总结

Linux内核进程相关知识点总结

Linux操作系统由Linux核心、shell(/bin/bash)、应用程序组成,三大核心功能是进程管理、内存管理和文件管理。这篇文章主要基于Linux4.0版本内核介绍进程,使得大家对进程有一个清晰的认识。后面将会陆续对3个核心功能进行介绍。

进程指的是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。线程是进程的一个执行单元,是进程内的调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级的进程。一个程序至少一个进程,一个进程至少一个线程。

进程利用进程控制块(Process Control Block,PCB)对其所拥有的资源进行抽象。进程控制块需要描述如下几类信息:

1进程的运行状态:包括就绪、等待阻塞、运行、僵尸等;

2.程序计数器:记录当前运行到的指令位置;

3.CPU寄存器;保存当前运行的上下文,便于调度出去之后还能调度回来接着运行;

4.CPU调度信息;包括进程优先级、调度队列等信息;

5.内存管理信息;进程使用的内存信息,比如进程的页表等;

6.统计信息;包含进程运行时间等相关的统计信息;

7.文件相关信息:包括进程打开的文件等。

进程控制块是用来描述进程运行状况以及控制进程运行所需要的全部信息,在Linux内核中,用task_struct这一结构体来描述进程控制块。这一结构体非常大,包含了进程的所有相关的信息和属性,可以简单归结为以下几类:进程的属性、进程间关系、进程调度相关信息、内存相关信息、文件管理相关信息、信号相关信息和资源限制相关信息。

进程的生命周期以及状态转移:

Linux内核进程相关知识点总结_第1张图片
TASK_INTERRUPTIBLE与TASK_UNINTERRUPTIBLE两个状态相似,不同之处在于后者在睡眠等待时不受干扰,对信号不做任何反应,称为不可中断的状态,不可以发送SIGKILL信号将其杀死,因为他们不响应信号。该状态被称为深度睡眠状态。利用ps命令看到标记为D状态的进程即为不可中断进程。前者进入睡眠状态之后一旦资源满足或者等待的事件发生即可将该进程状态设置为可运行状态并加入就绪队列中,称为浅睡眠状态。

进程标识:进程创建时会分配唯一的号码来标识,该号码即为进程标识符PID(Process Identifier)。Linux内核引入线程组的概念,一个线程组中的所有线程使用和线程组组长相同的PID,即该组中第一个进程的PID,会被存入task_struct数据结构的tgid成员中。举个栗子,一个进程创建后,线程组中只有这一个进程,它的PID和TGID是一样的。当这个进程创建了一个线程后,新线程有自己的PID,但是它的TGID仍指向父进程的TGID,因为它和父进程同属于一个线程组。

getpid()返回当前进程的tgid,而不是线程的pid值,因为一个多线程应用程序中所有线程都共享相同的PID。系统调用gettid会返回线程的PID。

获取当前进程的方式:Linux内核提供了current宏来找到当前正在运行的进程的task_struct数据结构的指针。这个宏的实现与具体的系统体系结构有关,有的体系结构的处理器会有专门的寄存器来存放指向当前进程task_struct数据结构的指针,但是ARM32系统结构没有,因此只能通过内核栈的尾端来创建thread_info结构,并通过计算偏移间接查找task_struct数据结构。

针对init进程,内核栈大小通常为8KB,存放在内核映像文件中的data段,在编译链接时预先分配好,其他进程的内核栈则是在进程派生时动态分配的。这个内核栈里存放一个thread_union联合数据结构,开始的地方存放了struct thread_info数据结构,顶部往下的空间用于内核栈的空间。ARM32处理器从汇编代码跳转到c语言的入口点在start_kernel()函数之前,设置了寄存器指向8KB内核栈顶部区域。因此current宏首先通过ARM32的sp寄存器来获取当前内核栈的地址,对齐后可以获取struct thread_info数据结构指针,最后通过thread_info->task成员获取task_thread数据结构。

thread_info数据结构保存了进程描述符中频繁访问和需要快速访问的字段以及和体系结构相关的部分,以前的内核是把整个task_struct数据结构存放在内核栈中。由于task_struct数据结构很大,浪费空间,后来改用thread_info数据结构来代替。如此一来,既能通过thread_info数据结构内嵌的task指针快速地得到task_struct数据结构指针,也可以实现体系结构相关部分和无关部分的分离设计。
thread_info数据结构定义如下:Linux内核进程相关知识点总结_第2张图片
进程的创建。用户空间创建进程由fork()和vfork(),创建线程一般为clone()。内核空间没有专门的线程这一概念,内核线程就是独立运行在内核空间的进程,使用kthread_create()来创建内核线程。
fork()函数在用户空间的c库函数定义如下:

 #include 
          pid_t fork(void)

函数有两次返回,父进程中返回子进程的ID,子进程中返回0。返回-1表示创建失败。
fork()通过系统调用进入内核,然后调用do_fork()实现创建。
Linux内核进程相关知识点总结_第3张图片
fork()函数只使用SIGCHLD标志位,在子进程终止后发送该信号通知父进程。fork()属于重量级调用,为子进程创建一个基于父进程的完整副本,然后子进程基于此运行。为了减少工作量,子进程采用写时复制技术,只是复制父进程的页表,不复制内容,内容以只读方式共享。只有在需要写入的时候才触发复制机制,为子进程创建副本。
vfork()函数与fork()函数类似,在fork()实现写时复制之前,为了避免fork()之后立即执行execve()函数造成的空间浪费和效率低下问题,题出vfork()函数,该函数的父进程会一直阻塞,直到子进程调用exit()函数或者execve()函数。
vfork()函数在用户空间的c库函数定义如下:

          #include 
          #include 
                   Pid_t vfork(void);

vfork()函数经过系统调用进入内核,然后调用do_fork()函数实现创建。
在这里插入图片描述
vfork()函数比fork()函数多了两个标志位,分别是CLONE_VFORK和CLONE_VM。前者表示父进程会被挂起,直到子进程释放虚拟资源。后者表示父子进程运行在相同的地址空间中。Vfork()的优势是省去了复制父进程的页表项。

clone()函数可传递多个参数,可以有选择地继承父进程的资源。可以和父进程共享地址空间,也可以不共享地址空间。
clone()函数在glibc库的封装如下:

          #include 
                   Int clone (int (*fn)(void *),void *child_stack, int flags, void *arg,…);

fn是子进程执行的函数指针,child_stack为子进程分配堆栈,flags设置clone标志位,表示需要从父进程继承哪些资源,arg是传递给子进程的参数。
clone()函数通过系统调用进入内核,然后调用do_fork()函数实现创建。
Linux内核进程相关知识点总结_第4张图片
内核线程是独立运行于内核空间的进程,和普通用户进程的区别在于没有独立的进程地址空间,即task_struct结构体中,mm指针指向了NULL。
Linux提供的创建内核线程的API如下:

    Kthread_create(threadfin,data, namefmt, arg…)

该函数创建的内核线程被命名为namefmt。新建的内核线程将运行threadfn函数。新建的内核线程处于不可运行状态,需要调用wake_up_process()函数来将其唤醒并加入到就绪队列中。
创建一个立即可以运行的进程用kthread_run()函数:

    kthread_run(threadfin, data, namefmt…)

内核线程最终还是通过do_fork()实现。
在这里插入图片描述
do_fork()函数定义如下:
在这里插入图片描述
clone_flags:创建进程的标志位集合;
stack_start:用户态栈的起始位置;
stack_size:用户态栈的大小,通常设置为0;
parent_tidptr和child_tidptr:指向用户空间中地址的两个指针,分别指向父子进程的PID。
do_fork()函数主要调用copy_process()函数创建子进程的task_struct数据结构,并完成相关内容的复制。
Linux内核进程相关知识点总结_第5张图片
进程的终止。进程的终止有两种方式:一种是自愿的终止,包括显式地调用exit()系统调用或者从某个程序的主函数返回;另一种方式是被动的接收到杀死的信号或者异常时被动地终止。前者主要有两种途径:
1)从main()函数返回,链接程序会自动添加对exit()系统调用;
2)主动调用exit()系统调用。
后者主要有三种途径:
1)进程收到一个自己不能处理的信号。
2)进程在内核态执行时产生了一个异常。
3)进程收到SIGKILL等信号。
进程终止时,Linux会释放其所有资源,并把这个不幸告知其父进程(SIGCHLD信号)。一个进程的终止有以下两种情况:
1)子进程先于父进程终止。此时子进程会变成僵尸进程,直到父进程调用wait()才算最终消亡,如果父进程未调用wait()/waitpid(),僵尸进程会一直保持下去直到系统重启,如果此时父进程异常终止,则子进程就会自动被init接管,那么它就不再是僵尸进程了。
2)父进程先于子进程终止。子进程变成孤儿进程,此时init进程成为子进程的新父进程,负责后续各种情况处理。
僵尸进程:一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占有的资源)的进程被称为僵尸进程(zombie)。子进程处于僵尸状态时,内核只保存进程的一些必要信息以备父进程所需。此时子进程始终占有着资源,同时也减少了系统可以创建的最大进程数。

进程0是Linux内核初始化阶段从无到有创建的一个内核线程,是所有进程的祖先,又称为idle进程或者swapper进程。该进程是通过静态变量init_task预先设定好的,由INIT_TASK宏来完成对task_struct的初始化。

Linux内核初始化函数start_kernel()在初始化完内核所需要的所有数据结构之后会创建另一个内核线程,这个内核线程就是进程1或init进程。Init进程的PID为1,与进程01共享进程所有的数据结构。

Init进程创建完毕后,进程0会执行cpu_idle()函数。当cpu上的就绪队列中没有其他可运行的进程时,调度器才会选择执行进程0,并让cpu进入idle状态。在SMP多核处理器中,每个cpu都有一个进程0。

进程1会执行kernel_init()函数,它会调用exevce()系统调用装入可执行程序init,最后进程1变成了一个普通进程。这些init程序就是常见的“bin/init”或者“bin/sh”等。

进程1从内核线程变成普通用户进程init后,主要作用是根据/etc/inittab文件的内容启动所需要的任务,包括初始化系统配置、启动一个登陆对话等。

欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.2.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

你可能感兴趣的:(Linux内核进程相关知识点总结)