<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Linux系统 on codedump notes</title>
    <link>https://www.codedump.info/zh/tags/linux%E7%B3%BB%E7%BB%9F/</link>
    <description>Recent content in Linux系统 on codedump notes</description>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Sat, 20 Jun 2020 12:19:08 +0800</lastBuildDate>
    <atom:link href="https://www.codedump.info/zh/tags/linux%E7%B3%BB%E7%BB%9F/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>《面向应用开发者的系统指南》CPU篇之Linux系统平均负载</title>
      <link>https://www.codedump.info/zh/post/20200620-sgfap-loadavg/</link>
      <pubDate>Sat, 20 Jun 2020 12:19:08 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200620-sgfap-loadavg/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文是《面向应用开发者的系统指南》文档其中的一篇，完整的目录见&lt;a href=&#34;https://www.codedump.info/post/20200501-system-guide-for-application-programmer/&#34;&gt;《面向应用开发者的系统指南》导论&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Linux中可以使用uptime、top等命令来查看系统的平均负载情况，比如：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ uptime&#xA; 10:54:37 up 29 days,  1:35,  2 users,  load average: 0.81, 0.65, 0.64&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中的&lt;code&gt;load average: 0.81, 0.65, 0.64&lt;/code&gt;数据，给出了系统在最近1分钟、5分钟、15分钟的系统平均负载情况。&lt;/p&gt;&#xA;&lt;p&gt;这一节讲解系统平均负载这个数据的来源，内容包括以下几方面：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;系统平均负载值来源于哪里？&lt;/li&gt;&#xA;&lt;li&gt;平均负载包括了哪些指标？&lt;/li&gt;&#xA;&lt;li&gt;内核是如何计算平均负载值的？&lt;/li&gt;&#xA;&lt;li&gt;平均负载的意义是什么？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;平均负载值的来源&#34;&gt;&#xA;  平均负载值的来源&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%b9%b3%e5%9d%87%e8%b4%9f%e8%bd%bd%e5%80%bc%e7%9a%84%e6%9d%a5%e6%ba%90&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;通过uptime命令可以看到系统最近1分钟、5分钟以及15分钟的平均负载值，所以要知道这个值的来源，最简单的方式就是了解uptime命令是从哪里获取到这些数据的，一方面可以看uptime命令的代码实现，但是直觉告诉我们一般这类命令都是通过读取/proc文件系统来获取系统的一些指标，所以更简单的方式是strace一下uptime命令，看看都去读取了哪些/proc文件系统的文件，果然看到了如下一行：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openat(AT_FDCWD, &amp;#34;/proc/loadavg&amp;#34;, O_RDONLY) = 4&#xA;lseek(4, 0, SEEK_SET)                   = 0&#xA;read(4, &amp;#34;0.42 0.20 0.07 3/137 1322\n&amp;#34;, 8191) = 26&#xA;fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到读取了&lt;code&gt;/proc/loadavg&lt;/code&gt;文件，通过&lt;code&gt;man proc&lt;/code&gt;命令来看看关于这个文件的说明：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/proc/loadavg&#xA;&#x9;&#x9;The first three fields in this file are load average figures giving the number of jobs in the run queue (state R) or  wait‐&#xA;&#x9;&#x9;ing  for  disk  I/O  (state  D) averaged over 1, 5, and 15 minutes.  They are the same as the load average numbers given by&#xA;&#x9;&#x9;uptime(1) and other programs.  The fourth field consists of two numbers separated by a slash (/).  The first  of  these  is&#xA;&#x9;&#x9;the  number of currently runnable kernel scheduling entities (processes, threads).  The value after the slash is the number&#xA;&#x9;&#x9;of kernel scheduling entities that currently exist on the system.  The fifth field is the PID of the process that was  most&#xA;&#x9;&#x9;recently created on the system.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以上文档中说明了，系统负载的统计数据源，包括：&lt;/p&gt;</description>
    </item>
    <item>
      <title>《面向应用开发者的系统指南》CPU篇之软中断</title>
      <link>https://www.codedump.info/zh/post/20200522-sgfap-softirq/</link>
      <pubDate>Fri, 22 May 2020 21:52:58 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200522-sgfap-softirq/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文是《面向应用开发者的系统指南》文档其中的一篇，完整的目录见&lt;a href=&#34;https://www.codedump.info/post/20200501-system-guide-for-application-programmer/&#34;&gt;《面向应用开发者的系统指南》导论&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;中断（interrupt）通常被定义为一个事件，该事件改变处理器执行的指令顺序。中断分为同步和异步两种：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;同步中断在指令执行时由CPU控制单元产生，之所以称为同步，是因为只有在一条指令终止执行后CPU才发生中断。&lt;/li&gt;&#xA;&lt;li&gt;异步中断是由其他硬件设备依照CPU时钟信号随机产生的。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在Intel的处理器手册中，将同步中断称为“异常（exception）”，异步中断称为“中断”。&lt;/p&gt;&#xA;&lt;p&gt;异常通常由程序的错误产生，或者是由内核必须处理的异常条件产生的。比如程序中有除零异常，比如进程运行过程中产生的“缺页异常（pagefault）”等，都属于异常。&lt;/p&gt;&#xA;&lt;p&gt;而中断是由定时器和I/O设备产生的，比如用户的一次按键、网卡收到数据，都会产生中断。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;interrupt-type&#34; src=&#34;https://www.codedump.info/media/imgs/20200522-sgfap-softirq/interrupt-type.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; interrupt-type &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;处理器一旦收到中断，就必须打断当前的执行，转而去执行中断处理函数。中断处理函数，本身有一些缺陷：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;不能在进程上下文中执行，因此不能阻塞。&lt;/li&gt;&#xA;&lt;li&gt;中断处理程序会打断程序执行，为了避免这个打断的流程停止时间过长，所以应该执行的越短越好。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因为以上的原因，Linux内核将中断的处理分为了上下两部分，其中上半部就是前面提到的中断处理函数，这部分能够最快的响应中断，并且做一些中断后必须要做的事情，而一些可以在中断处理函数后继续执行的操作，则可以放在下半部中。&lt;/p&gt;&#xA;&lt;p&gt;以网卡接收到数据来举例，网卡通过中断告诉内核有数据可以接收，此时内核就会到网卡的中断处理程序中执行一些网卡硬件的必要设置，而对应的下半部就是处理网卡收到的数据，因为处理网卡数据没有必要在中断处理函数里面马上执行。&lt;/p&gt;&#xA;&lt;p&gt;两者的主要区别在于：中断不能被相同类型的中断打断，而下半部依然可以被中断打断；中断对于时间非常敏感，而下半部基本上都是一些可以延迟的工作。由于二者的这种区别，所以对于一个工作是放在上半部还是放在下半部去执行，可以参考下面4条：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;如果一个任务对时间非常敏感，将其放在中断处理程序中执行。&lt;/li&gt;&#xA;&lt;li&gt;如果一个任务和硬件相关，将其放在中断处理程序中执行。&lt;/li&gt;&#xA;&lt;li&gt;如果一个任务要保证不被其他中断（特别是相同的中断）打断，将其放在中断处理程序中执行。&lt;/li&gt;&#xA;&lt;li&gt;其他所有任务，考虑放在下半部去执行。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;有写内核任务需要延后执行，因此才有的下半部，进而实现了三种实现下半部的方法。这就是本文要讨论的软中断、tasklet和工作队列。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;软中断&#34;&gt;&#xA;  软中断&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bd%af%e4%b8%ad%e6%96%ad&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;软中断作为下半部机制的代表，是随着SMP（share memory processor）的出现应运而生的，它也是tasklet实现的基础（tasklet实际上只是在软中断的基础上添加了一定的机制）。软中断一般是“可延迟函数”的总称，有时候也包括了tasklet（请读者在遇到的时候根据上下文推断是否包含tasklet）。它的出现就是因为要满足上面所提出的上半部和下半部的区别，使得对时间不敏感的任务延后执行，而且可以在多个CPU上并行执行，使得总的系统效率可以更高。它的特性包括：&lt;/p&gt;&#xA;&lt;p&gt;产生后并不是马上可以执行，必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行)，只能被硬件中断打断（上半部）。&#xA;可以并发运行在多个CPU上（即使同一类型的也可以）。所以软中断必须设计为可重入的函数（允许多个CPU同时操作），因此也需要使用自旋锁来保其数据结构。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;数据结构&#34;&gt;&#xA;  数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;软中断由系统在启动的时候注册到内核中，由一个全局数组来维护软中断：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; softirq_action&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;void&lt;/span&gt;&#x9;(*action)(&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; softirq_action *);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以看到，本质上结构体softirq_action存储的是函数指针而已，软中断有以下类型：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;enum&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;HI_SOFTIRQ=0,     &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 处理高优先级的tasklet&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;TIMER_SOFTIRQ,    &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 定时器的下半部&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;NET_TX_SOFTIRQ,   &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 网卡发送数据包&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;NET_RX_SOFTIRQ,   &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 网卡接收数据包&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;BLOCK_SOFTIRQ,    &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// BLOCK装置     &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;IRQ_POLL_SOFTIRQ, &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;TASKLET_SOFTIRQ,  &lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 处理常规的tasklet&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;SCHED_SOFTIRQ,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;HRTIMER_SOFTIRQ, &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;RCU_SOFTIRQ,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;NR_SOFTIRQS&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;系统提供了open_softirq函数用于各个需要使用到软中断的系统注册对应的软中断处理函数。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《面向应用开发者的系统指南》CPU篇之系统调用</title>
      <link>https://www.codedump.info/zh/post/20200516-sgfap-syscall/</link>
      <pubDate>Sat, 16 May 2020 16:31:03 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200516-sgfap-syscall/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文是《面向应用开发者的系统指南》文档其中的一篇，完整的目录见&lt;a href=&#34;https://www.codedump.info/post/20200501-system-guide-for-application-programmer/&#34;&gt;《面向应用开发者的系统指南》导论&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;应用程序需要使用内核提供出来的一些功能，才能完成相应的操作，这个由内核提供出来给用户态程序调用的接口，就是“系统调用（system call）”。比如打开文件时需要调用&lt;code&gt;open&lt;/code&gt;系统调用，写文件时需要调用&lt;code&gt;write&lt;/code&gt;系统调用，等等。&lt;/p&gt;&#xA;&lt;p&gt;本节将简单描述Linux在X86下系统调用的工作原理，接着描述如何追踪用户层进程的系统调用。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;系统调用原理&#34;&gt;&#xA;  系统调用原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;传统系统调用legacy-system-calls&#34;&gt;&#xA;  传统系统调用（Legacy system calls）&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bc%a0%e7%bb%9f%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8legacy-system-calls&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;在这里先讨论系统调用的传统实现方式，在这里需要解决以下几个问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;用户态怎么触发系统调用？&lt;/li&gt;&#xA;&lt;li&gt;用户态怎么传递参数给系统调用？&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;内核预留了一个特殊的软中断号 128 (0x80)，用户空间程序使用它可以进入内核执行系统调用，在内核中定义了宏&lt;code&gt;IA32_SYSCALL_VECTOR&lt;/code&gt;与之对应：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// arch/x86/include/asm/irq_vectors.h&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#define IA32_SYSCALL_VECTOR&#x9;&#x9;0x80&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;触发给软中断时会调用到汇编编写的函数&lt;code&gt;&#x9;entry_INT80_32&lt;/code&gt;中：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// arch/x86/kernel/idt.h&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;SYSG&lt;/span&gt;(IA32_SYSCALL_VECTOR,&#x9;entry_INT80_32),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;entry_INT80_32&lt;/code&gt;函数在&lt;code&gt;arch/x86/entry/entry_32.S&lt;/code&gt;中实现，其最终会走到&lt;code&gt;do_int80_syscall_32&lt;/code&gt;函数中调用系统调用。&lt;/p&gt;&#xA;&lt;p&gt;以上解决了第一个问题，即用户态通过触发软中断&lt;code&gt;int 0x80&lt;/code&gt;来调用系统调用的，接下来的问题是，内核如何知道调用的是哪个系统调用，以及怎么解决给系统调用传递参数的问题。&lt;/p&gt;&#xA;&lt;p&gt;在函数&lt;code&gt;entry_INT80_32&lt;/code&gt;的注释中，看到有如下的描述：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// arch/x86/entry/entry_32.S&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;/*&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * Arguments:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * eax  system call number&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * ebx  arg1&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * ecx  arg2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * edx  arg3&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * esi  arg4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * edi  arg5&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; * ebp  arg6&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可见，寄存器&lt;code&gt;eax&lt;/code&gt;中存放的是系统调用编号，接下来的几个寄存器分别存放传递进来的参数。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《面向应用开发者的系统指南》CPU篇之使用systemtap分析进程的行为</title>
      <link>https://www.codedump.info/zh/post/20200503-sgfap-process-systemtap/</link>
      <pubDate>Sun, 03 May 2020 14:32:57 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200503-sgfap-process-systemtap/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文是《面向应用开发者的系统指南》文档其中的一篇，完整的目录见&lt;a href=&#34;https://www.codedump.info/post/20200501-system-guide-for-application-programmer/&#34;&gt;《面向应用开发者的系统指南》导论&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;以上描述进程的创建、执行、调度器的工作原理，有了这些准备之后，可以使用systemtap在系统中埋点进行一些跟踪，以便理解进程的行为。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;分析进程对cpu的占用&#34;&gt;&#xA;  分析进程对CPU的占用&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%88%86%e6%9e%90%e8%bf%9b%e7%a8%8b%e5%af%b9cpu%e7%9a%84%e5%8d%a0%e7%94%a8&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;简单回顾一下前面进程调度相关的内容：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;内核中使用就绪队列来维护当前所有处于可运行状态的进程，可运行状态不包括等待IO、休眠等状态的进程。&lt;/li&gt;&#xA;&lt;li&gt;进程调度器负责从就绪队列中选择处于可运行状态的进程来执行。&lt;/li&gt;&#xA;&lt;li&gt;而所有不处于可运行状态的进程，并不占用CPU资源，这些进程都等待被相关的事件比如网络IO唤醒，唤醒之后的进程更改状态为可运行状态，同时加入到就绪队列中，然后才能被调度器算法选择执行。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因此，一个进程的整个生命周期中，虽然看上去进程一直存在，但是并不是所有时候都占用CPU资源。根据CPU占用资源与否，或者说当前是否在运行，分为&lt;code&gt;on cpu&lt;/code&gt;和&lt;code&gt;off cpu&lt;/code&gt;状态：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;onoffcpu&#34; src=&#34;https://www.codedump.info/media/imgs/20200503-sgfap-process-systemtap/onoffcpu.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; onoffcpu &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上图中就一个进程执行的时间线做了简单的阶段划分，其中省略掉了进程被创建出来和最后退出时的情况，仅列出占用CPU资源状态的切换。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;进程占用CPU获得执行权的时候，称为&lt;code&gt;on cpu&lt;/code&gt;时间。&lt;/li&gt;&#xA;&lt;li&gt;进程因为各种原因（被其他进程抢占、自己调用了sleep系统调用主动进入睡眠状态、等待网络IO等）被剥夺了执行权的时候，首先会调用&lt;code&gt;deactivate_task&lt;/code&gt;函数从就绪队列中删除，接下来调用&lt;code&gt;context_switch&lt;/code&gt;函数进行进程的上下文切换，这个时候旧的进程失去CPU的执行权，此时正式进入&lt;code&gt;off cpu&lt;/code&gt;时间中。&lt;/li&gt;&#xA;&lt;li&gt;在此之后，进程由于各种原因被唤醒，唤醒之后首先会被再次调用&lt;code&gt;activate_task&lt;/code&gt;函数加入到就绪队列中，进入就绪队列的进程也并不是马上就能够获得执行权的，是由进程调度算法来决定哪一个在就绪队列中的进程来执行。这段时间又可以分为两个部分：&#xA;&lt;ul&gt;&#xA;&lt;li&gt;进程被切换出去直到重新进入就绪队列，这部分时间内进程等待被唤醒。&lt;/li&gt;&#xA;&lt;li&gt;进入就绪队列到被调度器选中执行，这部分时间内进程等待被调度执行。&lt;/li&gt;&#xA;&lt;li&gt;以上两部分时间的总和，加起来就是进程休眠的时间，即处于&lt;code&gt;off cpu&lt;/code&gt;状态的时间。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;从这里看出来，一个进程虽然看上去一直存在，但并不是所有时间都在执行，跟进一个程序的运行时间时，需要区分其&lt;code&gt;on&lt;/code&gt;和&lt;code&gt;off&lt;/code&gt; cpu的时间，如果off的时间过长，那需要看看是什么原因导致了进程一直没有被唤醒执行。&lt;/p&gt;&#xA;&lt;p&gt;另外需要注意的是，进程处于就绪状态，并不一定就是在运行，有可能还在就绪队列中等待被调度执行；但是反之则不然，一个占用CPU在执行的进程，其状态一定是就绪状态。即：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;进程处于就绪状态的时间 = 进程在就绪队列的时间 + 进程在执行的时间&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关于&lt;code&gt;off cpu&lt;/code&gt;这一概念，&lt;a href=&#34;http://www.brendangregg.com/offcpuanalysis.html&#34;&gt;Off-CPU Analysis&lt;/a&gt;一文中有更多的讲述。&lt;/p&gt;&#xA;&lt;p&gt;有了上面对&lt;code&gt;on cpu&lt;/code&gt;和&lt;code&gt;off cpu&lt;/code&gt;的介绍，下面来看看使用systemtap如何跟踪这些状态以及所处的时间。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;off-cpu&#34;&gt;&#xA;  off CPU&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#off-cpu&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;systemtap中自带的tapset中，有一个scheduler.stp文件，里面定义了与调度器相关的一些probe。&lt;/p&gt;&#xA;&lt;p&gt;其中跟踪&lt;code&gt;off cpu&lt;/code&gt;的probe是&lt;code&gt;scheduler.cpu_off&lt;/code&gt; ：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;probe scheduler.cpu_off =&#xA;&#x9;kernel.trace(&amp;#34;sched_switch&amp;#34;) !,&#xA;&#x9;kernel.function(&amp;#34;context_switch&amp;#34;)&#xA;{&#xA;    name = &amp;#34;cpu_off&amp;#34;&#xA;    task_prev = $prev&#xA;    task_next = $next&#xA;    idle = __is_idle()&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;结合代码和最开始的示意图，可以知道该probe事件是针对内核trace事件&lt;code&gt;sched_switch&lt;/code&gt;以及内核函数&lt;code&gt;context_switch&lt;/code&gt;的封装，这两个事件都在进程上下文切换时触发。&lt;/p&gt;&#xA;&lt;p&gt;在该probe事件中，能获取到的参数是：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;task_prev：保存切换之前的进程&lt;code&gt;task_struct&lt;/code&gt;结构体。&lt;/li&gt;&#xA;&lt;li&gt;task_next：保存切换之后的进程&lt;code&gt;task_struct&lt;/code&gt;结构体。&lt;/li&gt;&#xA;&lt;li&gt;idle：表示当前CPU是否空闲。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;因为这个probe事件记录了进程切换前后的信息，因此可以用来完成类似记录系统切换最多的进程跟踪的功能：&lt;/p&gt;</description>
    </item>
    <item>
      <title>《面向应用开发者的系统指南》CPU篇之进程调度</title>
      <link>https://www.codedump.info/zh/post/20200503-sgfap-process-schedule/</link>
      <pubDate>Sun, 03 May 2020 09:53:34 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200503-sgfap-process-schedule/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;本文是《面向应用开发者的系统指南》文档其中的一篇，完整的目录见&lt;a href=&#34;https://www.codedump.info/post/20200501-system-guide-for-application-programmer/&#34;&gt;《面向应用开发者的系统指南》导论&lt;/a&gt;。&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;一种资源，如果本身数量有限，需要多个资源需求方来使用的情况下，就涉及到&lt;code&gt;资源调度&lt;/code&gt;的问题。在内核中，CPU就是一种有限的资源，同时在系统中处于运行状态的进程数量有很多，此时就需要设计出一种方法，尽可能的保证这种资源被公平的分配到进程中间。&lt;/p&gt;&#xA;&lt;p&gt;Linux内核中的进程调度，涉及到以下几个重要概念：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;核心调度器：核心调度器可以认为是内核中进程调度模块，对外提供了周期性调度（定时触发）以及主调度器两个接口。&lt;/li&gt;&#xA;&lt;li&gt;就绪队列：所有当前运行的进程都在这个队列中维护，需要选择出下一个执行的进程也从这个队列中选举。&lt;/li&gt;&#xA;&lt;li&gt;调度优先级：给予不同的进程不同的优先级，这样分配到的时间就不一样。&lt;/li&gt;&#xA;&lt;li&gt;调度算法：不同类型的进程使用不同的调度算法来选择执行进程。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;以下来简单阐述这几个组件如何一起作用完成进程调度的工作。&lt;/p&gt;&#xA;&lt;p&gt;每个CPU维护自己的就绪队列，就绪队列由结构体&lt;code&gt;rq&lt;/code&gt;来表示，队列中的每个元素都是前面提到的描述进程信息的结构体&lt;code&gt;task_struct&lt;/code&gt;。这里需要注意的是，虽然称之为“队列”，内部的实现中，根据不同的调度算法，使用了不同的数据结构来保存进程，比如CFS调度器使用了红黑树来保存进程，这一点在后面展开阐述，目前为止，暂且认为就绪队列是一个维护CPU所有当前就绪进程的容器。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;runqueue&#34; src=&#34;https://www.codedump.info/media/imgs/20200503-sgfap-process-schedule/runqueue.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; runqueue &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;不同的调度器算法，无论内部如何实现，其最终都是从就绪队列中选择下一个可执行的进程来运行。&#xA;在这个版本的内核中一共实现了如下几种调度器算法，它们统一由结构体&lt;code&gt;sched_class&lt;/code&gt;来表示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;sched_class&#34; src=&#34;https://www.codedump.info/media/imgs/20200503-sgfap-process-schedule/sched_class.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; sched_class &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;调度器&lt;/th&gt;&#xA;          &lt;th&gt;描述&lt;/th&gt;&#xA;          &lt;th&gt;对应调度策略&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;dl_sched_class&lt;/td&gt;&#xA;          &lt;td&gt;deadline调度器&lt;/td&gt;&#xA;          &lt;td&gt;SCHED_DEADLINE&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;rt_sched_class&lt;/td&gt;&#xA;          &lt;td&gt;实时调度器&lt;/td&gt;&#xA;          &lt;td&gt;SCHED_FIFO、SCHED_RR&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;fair_sched_class&lt;/td&gt;&#xA;          &lt;td&gt;完全公平调度器&lt;/td&gt;&#xA;          &lt;td&gt;SCHED_NORMAL、SCHED_BATCH&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;idle_sched_class&lt;/td&gt;&#xA;          &lt;td&gt;idle调度器&lt;/td&gt;&#xA;          &lt;td&gt;SCHED_IDLE&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;以上列举了进程的几种调度器及对应的调度策略，其优先级依次递减。在下面的内容中，将详细介绍完全公平调度器（Completely Fair Scheduler，简称CFS），因为这是最普遍的进程调度器。&lt;/p&gt;&#xA;&lt;p&gt;从以上的介绍可以看到，内核的调度器负责维护就绪队列，即提供了调度进程所需的数据来源；而不同的调度器算法则根据自己的实现来从就绪队列中选择进程来执行，那么选择的依据又是什么？答案是进程的优先级。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;schedule&#34; src=&#34;https://www.codedump.info/media/imgs/20200503-sgfap-process-schedule/schedule.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; schedule &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;以上简单阐述了Linux进程调度中涉及到的四个最重要的要素，下面将展开讨论。&lt;/p&gt;&#xA;&lt;p&gt;首先将介绍进程的优先级，通过这个值如何计算得到进程的权重，进一步得到&lt;code&gt;CFS&lt;/code&gt;调度器算法中所需的虚拟运行时间。&lt;/p&gt;&#xA;&lt;p&gt;紧接着介绍与进程调度相关的数据结构，以及内核中进程调度的核心调度器的实现。&lt;/p&gt;&#xA;&lt;p&gt;最后就是详细展开&lt;code&gt;CFS&lt;/code&gt;调度器内部的实现。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;优先级权重和虚拟运行时间&#34;&gt;&#xA;  优先级、权重和虚拟运行时间&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bc%98%e5%85%88%e7%ba%a7%e6%9d%83%e9%87%8d%e5%92%8c%e8%99%9a%e6%8b%9f%e8%bf%90%e8%a1%8c%e6%97%b6%e9%97%b4&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;优先级&#34;&gt;&#xA;  优先级&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e4%bc%98%e5%85%88%e7%ba%a7&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Linux通过nice命令设置进程的静态优先级，进程的nice值在[-20,19]之间，值越小优先级越高。而内核本身，选择范围[0,139]在内部表示优先级，同样是数值越低优先级越高：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;nice&#34; src=&#34;https://www.codedump.info/media/imgs/20200503-sgfap-process-schedule/nice.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; nice &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;对于普通的进程，可以认为优先级不会发生变化，而实时进程则不然：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// kernel/sched/core.c&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;effective_prio&lt;/span&gt;(&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;struct&lt;/span&gt; task_struct *p)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;p-&amp;gt;normal_prio = &lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;normal_prio&lt;/span&gt;(p);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// 如果不是实时进程，返回前面normal_prio的计算结果&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;if&lt;/span&gt; (!&lt;span style=&#34;color:#666;font-weight:bold;font-style:italic&#34;&gt;rt_prio&lt;/span&gt;(p-&amp;gt;prio))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; p-&amp;gt;normal_prio;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;font-weight:bold;text-decoration:underline&#34;&gt;return&lt;/span&gt; p-&amp;gt;prio;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;由于在这里不讨论实时进程，仅讨论普通进程，因此可以认为进程优先级就是静态不变的。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;cpu时间权重&#34;&gt;&#xA;  CPU时间权重&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#cpu%e6%97%b6%e9%97%b4%e6%9d%83%e9%87%8d&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;CFS调度器的设计理念，就是能够实现理想、精确的多任务CPU进程调度。与以往的调度器不同的是，CFS调度器没有时间片的概念，使用的是分配CPU时间的比例。通过进程的优先级，就可以计算出来一个进程在就绪队列中所占时间的权重了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《面向应用开发者的系统指南》CPU篇之进程</title>
      <link>https://www.codedump.info/zh/post/20200502-sgfap-process/</link>
      <pubDate>Sat, 02 May 2020 14:41:22 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200502-sgfap-process/</guid>
      <description>&lt;p&gt;本文是《面向应用开发者的系统指南》文档其中的一篇，完整的目录见&lt;a href=&#34;https://www.codedump.info/post/20200501-system-guide-for-application-programmer/&#34;&gt;《面向应用开发者的系统指南》导论&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概论&#34;&gt;&#xA;  概论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;进程即处于执行状态的程序。操作系统执行进程时，大体经历了以下步骤：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;为进程分配空间及其他相关资源。&lt;/li&gt;&#xA;&lt;li&gt;加载进程可执行程序到内存中。&lt;/li&gt;&#xA;&lt;li&gt;由调度器选择进程来占用CPU资源执行。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;从上面的描述可以看到，进程并不是仅仅只有可执行程序二进制文件就可以运行起来，还需要执行时所需要的资源（内存、CPU等）、进程执行时需要的其他共享库等。&lt;/p&gt;&#xA;&lt;p&gt;在现代操作系统中，进程提供了针对两种资源的虚拟机制：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;虚拟处理器。&lt;/li&gt;&#xA;&lt;li&gt;虚拟内存。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;虽然在同一时间有多个进程在执行，即分享处理器资源，但是虚拟处理器提供了一种假象：让这些进程认为自己都在独占处理器执行，这里涉及到进程调度部分的内容，在后面进程调度篇再展开讨论。&lt;/p&gt;&#xA;&lt;p&gt;同时，进程使用的内存实际上虚拟内存，虚拟内存机制使进程以为自己拥有整个4G空间（32位处理器下）而不必关心其他进程的内存空间，这部分内容在内存篇中讲解。&lt;/p&gt;&#xA;&lt;p&gt;程序本身并不是进程，进程是在执行的程序以及相关资源的总称。&lt;/p&gt;&#xA;&lt;p&gt;本篇从进程开始讲起，涉及内核管理进程的数据结构、与进程创建和执行相关的系统调用、进程的状态。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;数据结构&#34;&gt;&#xA;  数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Linux内核使用&lt;code&gt;task_struct&lt;/code&gt;结构体来描述一个进程的所有信息。考虑这个结构体的内容太多，这里并不打算全部列举出来，到讲解需要涉及到的时候才提出其中的某些成员详细说明，这里只列出最关键的几个成员：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;task_struct&#34; src=&#34;https://www.codedump.info/media/imgs/20200502-sgfap-process/task_struct.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; task_struct &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;其中：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;state：用于表示进程的状态，下面将展开讨论。&lt;/li&gt;&#xA;&lt;li&gt;pid：每个进程都有一个pid与之对应。&lt;/li&gt;&#xA;&lt;li&gt;mm：类型为&lt;code&gt;mm_struct&lt;/code&gt;，用于表示进程的内存地址信息，后面内存部分将展开讨论。&lt;/li&gt;&#xA;&lt;li&gt;fs：类型为&lt;code&gt;fs_struct&lt;/code&gt;，用于表示文件系统信息，后面IO部分将展开讨论。&lt;/li&gt;&#xA;&lt;li&gt;files：类型为&lt;code&gt;files_struct&lt;/code&gt;，用于表示进程打开文件的信息，后面IO部分将展开讨论。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;进程的状态&#34;&gt;&#xA;  进程的状态&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bf%9b%e7%a8%8b%e7%9a%84%e7%8a%b6%e6%80%81&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;task_struct&lt;/code&gt;中的&lt;code&gt;state&lt;/code&gt;成员，用于表示当前进程的状态，进程的状态必然处于以下五种状态之一：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TASK_RUNNING：进程是可执行的（Runnable），表示进程要么正在执行，要么正要准备执行（已经就绪），等待cpu时间片的调度。&lt;/li&gt;&#xA;&lt;li&gt;TASK_INTERRUPTIBLE：进程因为等待一些条件而被挂起（阻塞）而所处的状态。这些条件主要包括：硬中断、资源、一些信号等，一旦等待的条件成立，进程就会从该状态（阻塞）迅速转化成为就绪状态TASK_RUNNING。&lt;/li&gt;&#xA;&lt;li&gt;TASK_UNINTERRUPTIBLE：此进程状态类似于&lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt;，只是它不会处理信号。中断处于这种状态的进程是不合适的，因为它可能正在完成某些重要的任务。 当它所等待的事件发生时，进程将被显式的唤醒呼叫唤醒。&lt;/li&gt;&#xA;&lt;li&gt;TASK_TRACED：正被调试程序等其它进程监控时，进程将进入这种状态。&lt;/li&gt;&#xA;&lt;li&gt;TASK_STOPPED：进程被停止执行，当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这几个状态之间，转换关系如下图所示：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;taskstate&#34; src=&#34;https://www.codedump.info/media/imgs/20200502-sgfap-process/taskstate.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; taskstate &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;上面的状态转换图中，休眠状态（SLEEPING）包括了&lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt;和&lt;code&gt;TASK_UNINTERRUPTIBLE&lt;/code&gt;，并没有做区分；另外，按照前面的讲述，&lt;code&gt;TASK_RUNNING&lt;/code&gt;状态区分了就绪以及在运行状态，由于这两者都是&lt;code&gt;TASK_RUNNING&lt;/code&gt;状态，所以分到了同一组里，又因为需要细化这两者之间的状态，内部也有状态之间的迁移。&lt;/p&gt;&#xA;&lt;p&gt;根据上面的状态转换图，进程状态的转换有这几种情况：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;正在运行的进程，由于需要等待某些事件（比如网络IO、磁盘IO等），进入休眠状态。&lt;/li&gt;&#xA;&lt;li&gt;正在运行的进程，由于时间片用完或者被其他更高优先级的进程抢占等因素，虽然还是就绪状态，但是被剥夺了执行权，进入就绪队列等待下一次被唤醒执行。&lt;/li&gt;&#xA;&lt;li&gt;处于休眠状态的进程，由于等待的事件满足被唤醒，进入就绪队列中等待被调度运行。&lt;/li&gt;&#xA;&lt;li&gt;处于就绪队列中的进程，被调度器分配CPU时间调度执行。&lt;/li&gt;&#xA;&lt;li&gt;在运行的进程退出。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;除了上面几种状态以外，还有僵尸（zombie）状态（内核使用&lt;code&gt;EXIT_ZOMBIE&lt;/code&gt;宏表示），用于表示进程已经不再执行，等待被回收的状态。&lt;/p&gt;&#xA;&lt;p&gt;在使用&lt;code&gt;ps aux&lt;/code&gt;命令时，可以查询到系统中进程所处的状态，与上面描述的内核中进程状态一一对应：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;S：休眠状态（sleeping），对应&lt;code&gt;TASK_INTERRUPTIBLE&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;R：等待运行（runable）对应&lt;code&gt;TASK_RUNNING&lt;/code&gt;，进程处于运行或就绪状态。&lt;/li&gt;&#xA;&lt;li&gt;I：空闲状态（idle）。&lt;/li&gt;&#xA;&lt;li&gt;Z：僵尸状态（zombie），对应&lt;code&gt;EXIT_ZOMBIE&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;T：暂停或跟踪状态（Traced），对应&lt;code&gt;TASK_TRACED&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;li&gt;D: 不可中断的睡眠状态，对应&lt;code&gt;TASK_UNINTERRUPTIBLE&lt;/code&gt;。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;在这里，需要再次强调的是，进程处于&lt;code&gt;Runnable&lt;/code&gt;状态时，并不代表就在执行，而是处于就绪可执行状态，由调度器最终决定进程执行。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;进程的创建&#34;&gt;&#xA;  进程的创建&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e8%bf%9b%e7%a8%8b%e7%9a%84%e5%88%9b%e5%bb%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Unix系统将进程的执行放在两个不同的函数中执行：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;fork：fork函数拷贝父进程来创建一个子进程，fork函数调用后会分别在父子进程中各返回一次，区别在于：父进程中的返回值是所创建的子进程的进程pid，而子进程则是返回0表示创建成功。&lt;/li&gt;&#xA;&lt;li&gt;exec函数组：在fork调用返回后，子进程就创建完成了，如果需要运行一个与父进程不同的可执行文件，就通过&lt;code&gt;exec&lt;/code&gt;函数组来完成这个工作。如果不调用&lt;code&gt;exec&lt;/code&gt;，那么也就意味着父子进程运行的是同一份可执行文件代码。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;其他操作系统，有一些把以上两步合在一个函数中完成，即在同一个函数中既完成子进程的创建，也完成子进程的执行，Unix系统将以上两步分开成两个步骤，为shell执行程序提供了方便，因为shell可以在&lt;code&gt;fork&lt;/code&gt;创建进程之后，调用&lt;code&gt;exec&lt;/code&gt;来执行程序之前改变子进程的一些行为。比如让shell方便的实现类似重定向（redirect）的功能：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wc test.txt &amp;gt; stat&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在上面的脚本中，希望将&lt;code&gt;wc&lt;/code&gt;命令的输出结果重定向到文件&lt;code&gt;stat&lt;/code&gt;中。shell在&lt;code&gt;fork&lt;/code&gt;创建了子进程之后，在&lt;code&gt;exec&lt;/code&gt;执行之前，关闭该子进程的标准输出，然后打开文件&lt;code&gt;stat&lt;/code&gt;，这样打开的文件就获得了刚刚关闭的标准输出的fd，执行&lt;code&gt;wc&lt;/code&gt;命令的子进程结果就写入到了文件&lt;code&gt;stat&lt;/code&gt;中。&lt;/p&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;写时复制机制&#34;&gt;&#xA;  写时复制机制&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%86%99%e6%97%b6%e5%a4%8d%e5%88%b6%e6%9c%ba%e5%88%b6&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;前面提到过，fork函数为子进程创建一个父进程地址空间的副本，复制属于父进程的页面。然而，考虑到许多子进程在创建之后立即调用系统调用exec函数组来执行另外的程序代码，父进程地址空间的复制可能没有必要。&lt;/p&gt;</description>
    </item>
    <item>
      <title>《面向应用开发者的系统指南》导论</title>
      <link>https://www.codedump.info/zh/post/20200501-system-guide-for-application-programmer/</link>
      <pubDate>Fri, 01 May 2020 21:43:43 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200501-system-guide-for-application-programmer/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;导论&#34;&gt;&#xA;  导论&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%af%bc%e8%ae%ba&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;我想整理这份文档很久了。&lt;/p&gt;&#xA;&lt;p&gt;做为一个主要工作在应用层、用户态的开发者，我看了不少的所谓“内核资料”。我身边不少与我相似工作背景的人，也在不停的看“内核”、写各种内核代码分析。另一方面，在遇到很多系统方面的知识时，并没有太多能够系统解答这部分内容的地方，比如如下几个问题：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;free&lt;/code&gt;命令中的&lt;code&gt;buffer&lt;/code&gt;和&lt;code&gt;cache&lt;/code&gt;分别是什么？&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;uptime&lt;/code&gt;命令中显示的数据来源是什么？&lt;/li&gt;&#xA;&lt;li&gt;用户态进程的CPU时间，都由哪些部分组成？&lt;/li&gt;&#xA;&lt;li&gt;&amp;hellip;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这就是这两个维度层面的断档：内核资料大部分是写给在内核态工作的人看的，并没有从用户态的角度去解释一些系统相关的概念，导致了用户态开发者看各种内核文档时云里雾里，最后并没有给自己理解和解决系统问题带来太多的帮助。&lt;/p&gt;&#xA;&lt;p&gt;所以，我想从应用开发者的角度，抽出系统中最重要的那些概念，结合一些不那么复杂的内核代码解读（毕竟复杂的我也不会）、相关命令指标的数据来源（比如前面的&lt;code&gt;free&lt;/code&gt;命令）、&lt;code&gt;systemtap&lt;/code&gt;脚本等等手段，帮助工作在Linux上的应用开发者来更好的理解系统。&lt;/p&gt;&#xA;&lt;p&gt;如果打一个可能不是很恰当的比方，内核文档在我看来就是写给数学系的《数学分析》，但是工科学生并不需要了解的过于深入，他们需要的是一本面向工科生、实际解决他们遇到的问题的《高等数学》即可。&lt;/p&gt;&#xA;&lt;p&gt;这份文档就想能够写一本给应用开发者的“《高等数学》”，最后完成时，不一定能尽善尽美，但是希望能打开一扇窗户：应用开发者不应该在过多的内核细节中深入，而是应该从自己遇到的问题出发，带着问题抽丝剥茧在内核中去掉不重要的细节，寻找自己问题的答案。&lt;/p&gt;&#xA;&lt;p&gt;这份文档将逐渐更新（希望最后不会太监），将主要分为CPU篇、内存篇、IO篇，本来还应该有个网络篇，不过我还是先完成前面这个小目标再继续吧。&lt;/p&gt;&#xA;&lt;p&gt;本文档基于Ubuntu16、内核版本4.15进行讲解，但是其中大部分的概念、内容应该都不会随着版本有太大的变动。&lt;/p&gt;&#xA;&lt;p&gt;今后的文档，将更新到下面这个目录中。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;目录&#34;&gt;&#xA;  目录&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e7%9b%ae%e5%bd%95&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;基础篇&#34;&gt;&#xA;  基础篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%9f%ba%e7%a1%80%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;因为后续会使用&lt;code&gt;systemtap&lt;/code&gt;脚本来理解内核的一些行为，所以&lt;code&gt;systemtap&lt;/code&gt;的相关的原理和使用放在基础篇中&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200128-systemtap-by-example/&#34;&gt;通过实例快速入门Systemtap&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200218-linux-traceevent/&#34;&gt;Systemtap中内核trace事件的实现&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;cpu篇&#34;&gt;&#xA;  CPU篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#cpu%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200502-sgfap-process/&#34;&gt;进程&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200503-sgfap-process-schedule/&#34;&gt;进程调度&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200503-sgfap-process-systemtap/&#34;&gt;使用systemtap分析进程的行为&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200516-sgfap-syscall/&#34;&gt;系统调用&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.codedump.info/post/20200522-sgfap-softirq/&#34;&gt;软中断&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;内存篇&#34;&gt;&#xA;  内存篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%86%85%e5%ad%98%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;io篇&#34;&gt;&#xA;  IO篇&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#io%e7%af%87&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;</description>
    </item>
    <item>
      <title>Systemtap中内核trace事件的实现</title>
      <link>https://www.codedump.info/zh/post/20200218-linux-traceevent/</link>
      <pubDate>Tue, 18 Feb 2020 17:37:01 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200218-linux-traceevent/</guid>
      <description>&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;内核中定义了一系列的trace point，这些trace point在特定的内核函数中被触发调用时被记录，而对应到systemtap中就是&lt;code&gt;kernel.trace&lt;/code&gt;类型的probe事件，可以使用命令来查看系统所有的trace point：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ sudo stap -L &amp;#39;kernel.trace(&amp;#34;*&amp;#34;)&amp;#39; | more&#xA;kernel.trace(&amp;#34;9p:9p_client_req&amp;#34;) $clnt:struct p9_client* $type:int8_t $tag:int&#xA;kernel.trace(&amp;#34;9p:9p_client_res&amp;#34;) $clnt:struct p9_client* $type:int8_t $tag:int $err:int&#xA;kernel.trace(&amp;#34;9p:9p_protocol_dump&amp;#34;) $clnt:struct p9_client* $pdu:struct p9_fcall*&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;换言之，通过systemtap能够对这些已经静态注册的内核调用记录点进行监控、跟踪。&lt;/p&gt;&#xA;&lt;p&gt;以下来解释trace point在内核的实现以及与systemtap相关的内容。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;数据结构&#34;&gt;&#xA;  数据结构&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;内核通过&lt;code&gt;DECLARE_TRACE&lt;/code&gt;来声明一个trace point：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;DECLARE_TRACE(subsys_eventname,&#xA;&#x9;TP_PROTO(int firstarg, struct task_struct *p),&#xA;&#x9;TP_ARGS(firstarg, p));&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在这里：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;subsys_eventname是定义trace事件的唯一字符串，又能拆解成两部分：subsys就是子系统的名称，而eventname是事件名称。比如下面将作为实例的&lt;code&gt;softirq_entry&lt;/code&gt;，就定义了一个在&lt;code&gt;softirq&lt;/code&gt;子系统中的&lt;code&gt;entry&lt;/code&gt;事件。&lt;/li&gt;&#xA;&lt;li&gt;TP_PROTO(int firstarg, struct task_struct *p)：定义了传入trace函数的参数原型。&lt;/li&gt;&#xA;&lt;li&gt;TP_ARGS(firstarg, p)：定义了参数名称，其类型与TP_PROTO中的类型一一对应。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;这个宏的定义如下：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-C&#34; data-lang=&#34;C&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;// include/linux/tracepoint.h&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-style:italic&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;#define DECLARE_TRACE(name, proto, args)&#x9;&#x9;&#x9;&#x9;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&#x9;__DECLARE_TRACE(name, PARAMS(proto), PARAMS(args),&#x9;&#x9;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&#x9;&#x9;&#x9;cpu_online(raw_smp_processor_id()),&#x9;&#x9;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&#x9;&#x9;&#x9;PARAMS(void *__data, proto),&#x9;&#x9;&#x9;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;&#x9;&#x9;&#x9;PARAMS(__data, args))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中的宏&lt;code&gt;__DECLARE_TRACE&lt;/code&gt;定义如下：&lt;/p&gt;</description>
    </item>
    <item>
      <title>通过实例快速入门Systemtap</title>
      <link>https://www.codedump.info/zh/post/20200128-systemtap-by-example/</link>
      <pubDate>Tue, 28 Jan 2020 11:56:56 +0800</pubDate>
      <guid>https://www.codedump.info/zh/post/20200128-systemtap-by-example/</guid>
      <description>&lt;p&gt;我这段时间好好学习了一下Systemtap相关的使用，这篇文章算是学习过程中总结的一些笔记，我另外在github上创建了一个&lt;a href=&#34;https://github.com/lichuang/awesome-systemtap-cn&#34;&gt;awesome-systemtap-cn&lt;/a&gt;项目，收集systemtap相关的优秀学习资源，欢迎提供其他更好的参考资料。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;概述&#34;&gt;&#xA;  概述&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e6%a6%82%e8%bf%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;systemtap是一款“动态跟踪（dynamic tracing）”工具，为什么需要这类工具？打一个比方，这类工具就好比医生的听诊器，病人就好比是在运行的系统，很多时候查看一些问题需要在系统在运行的时候来观察，这时候就需要这类动态跟踪工具。与之对应的是，类似gdb这样的调试工具，其工作原理是让进程在某些断点暂停下来，查看进程的行为，这种技术称为“静态调试”。&lt;/p&gt;&#xA;&lt;p&gt;关于动态跟踪技术，推荐阅读&lt;a href=&#34;https://openresty.org/posts/dynamic-tracing/&#34;&gt;《动态追踪技术漫谈》&lt;/a&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;Smileytap.svg&#34; src=&#34;https://www.codedump.info/media/imgs/20200128-systemtap-by-example/Smileytap.svg_.png&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; systemtap &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;本文旨在通过实例，快速解释systemtap脚本语言的最常见用法和语法。&lt;/p&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;工作原理&#34;&gt;&#xA;  工作原理&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;如下图，systemtap使用.stp脚本语言，由命令行&lt;code&gt;stap&lt;/code&gt;编译生成对应的内核模块，动态放入内核中执行：&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;figure class=&#34;&#34;&gt;&#xA;&#xA;    &lt;div&gt;&#xA;        &lt;img loading=&#34;lazy&#34; alt=&#34;systemtap&#34; src=&#34;https://www.codedump.info/media/imgs/20200128-systemtap-by-example/systemtap.gif&#34; &gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &#xA;    &lt;div class=&#34;caption-container&#34;&gt;&#xA;        &lt;figcaption&gt; systemtap &lt;/figcaption&gt;&#xA;    &lt;/div&gt;&#xA;    &#xA;&lt;/figure&gt;&#xA;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;stap 流程从将脚本转换成解析树开始 (pass 1)。&lt;/li&gt;&#xA;&lt;li&gt;然后使用细化（elaboration）步骤 (pass 2) 中关于当前运行的内核的符号信息解析符号。&lt;/li&gt;&#xA;&lt;li&gt;接下来，转换流程将解析树转换成 C 源代码 (pass 3) 并使用解析后的信息和 tapset 脚本（SystemTap 定义的库，包含有用的功能）。&lt;/li&gt;&#xA;&lt;li&gt;stap 的最后步骤是构造使用本地内核模块构建进程的内核模块 (pass 4)。&lt;/li&gt;&#xA;&lt;li&gt;有了可用的内核模块之后，stap 完成了自己的任务，并将控制权交给其他两个实用程序 SystemTap：staprun 和 stapio。这两个实用程序协调工作，负责将模块安装到内核中并将输出发送到 stdout (pass 5)。如果在 shell 中按组合键 Ctrl-C 或脚本退出，将执行清除进程，这将导致卸载模块并退出所有相关的实用程序。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h1 class=&#34;heading&#34; id=&#34;stap命令行参数&#34;&gt;&#xA;  stap命令行参数&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#stap%e5%91%bd%e4%bb%a4%e8%a1%8c%e5%8f%82%e6%95%b0&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;h2 class=&#34;heading&#34; id=&#34;-x-pid&#34;&gt;&#xA;  -x PID&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#-x-pid&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;-x用于传递PID参数给systemtap脚本，这样在脚本内部可以通过target()函数拿到这个传递进来的参数：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// $ sudo stap x-param.stp -x 10&#xA;// 输出：pid:10&#xA;probe begin&#xA;{&#xA;  printf(&amp;#34;pid:%d\n&amp;#34;, target())&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h2 class=&#34;heading&#34; id=&#34;-t-seconds&#34;&gt;&#xA;  -T seconds&#xA;  &lt;a class=&#34;anchor&#34; href=&#34;#-t-seconds&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;-T 参数后面可以带上秒数，这样脚本在这个时间之后自动退出，这样可以设置脚本执行的时间。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
