<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Linux 操作系统 on 夜云泊</title>
    <link>https://lifeislife.cn/categories/linux-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/</link>
    <description>feedId:57980998056508425+userId:73222296380546048 Recent content in Linux 操作系统 on 夜云泊</description>
    <generator>Hugo -- 0.160.1</generator>
    <language>zh</language>
    <lastBuildDate>Tue, 31 Aug 2021 09:44:40 +0000</lastBuildDate>
    <atom:link href="https://lifeislife.cn/categories/linux-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Linux 操作系统-虚拟化</title>
      <link>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E8%99%9A%E6%8B%9F%E5%8C%96/</link>
      <pubDate>Tue, 31 Aug 2021 09:44:40 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E8%99%9A%E6%8B%9F%E5%8C%96/</guid>
      <description>&lt;h2 id=&#34;虚拟化&#34;&gt;虚拟化&lt;/h2&gt;
&lt;h3 id=&#34;虚拟机&#34;&gt;虚拟机&lt;/h3&gt;
&lt;h4 id=&#34;qemu-工作原理&#34;&gt;QEMU 工作原理&lt;/h4&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721140349.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721140349.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;单纯使用 qemu，采用的是完全虚拟化的模式。qemu 向 Guest OS 模拟 CPU，也模拟其他的硬件，GuestOS 认为自己和硬件直接打交道，其实是同 qemu 模拟出来的硬件打交道，qemu 会将这些指令转译给真正的硬件。由于所有的指令都要从 qemu 里面过一手，因而性能就会比较差。&lt;/p&gt;
&lt;p&gt;完全虚拟化是非常慢的，所以要使用硬件辅助虚拟化技术 Intel-VT，AMD-V，所以需要 CPU 硬件开启这个标志位，一般在 BIOS 里面设置。当确认开始了标志位之后，通过 KVM，GuestOS 的 CPU 指令不用经过 Qemu 转译，直接运行，大大提高了速度。所以，KVM 在内核里面需要有一个模块，来设置当前 CPU 是 Guest OS 在用，还是 Host OS 在用。&lt;/p&gt;
&lt;p&gt;可以通过如下命令查看内核模块中是否有 KVM&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;lsmod | grep kvm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;KVM 内核模块通过 &lt;code&gt;/dev/kvm&lt;/code&gt; 暴露接口，用户态程序可以通过 &lt;code&gt;ioctl&lt;/code&gt;来访问这个接口。Qemu 将 KVM 整合进来，将有关 CPU 指令的部分交由内核模块来做，就是 qemu-kvm (qemu-system-XXX)。&lt;/p&gt;
&lt;p&gt;qemu 和 kvm 整合之后，CPU 的性能问题解决了。另外 Qemu 还会模拟其他的硬件，如网络和硬盘。同样，全虚拟化的方式也会影响这些设备的性能。&lt;/p&gt;
&lt;p&gt;于是，qemu 采取半虚拟化的方式，让 Guest OS 加载特殊的驱动来做这件事情。&lt;/p&gt;
&lt;p&gt;例如，网络需要加载 &lt;code&gt;virtio_net&lt;/code&gt;，存储需要加载 &lt;code&gt;virtio_blk&lt;/code&gt;，Guest 需要安装这些半虚拟化驱动，GuestOS 知道自己是虚拟机，所以数据会直接发送给半虚拟化设备，经过特殊处理（例如排队、缓存、批量处理等性能优化方式），最终发送给真正的硬件。这在一定程度上提高了性能。&lt;/p&gt;
&lt;h3 id=&#34;计算虚拟化之-cpu&#34;&gt;计算虚拟化之 CPU&lt;/h3&gt;
&lt;h3 id=&#34;计算虚拟化之内存&#34;&gt;计算虚拟化之内存&lt;/h3&gt;
</description>
      <content:encoded><![CDATA[<h2 id="虚拟化">虚拟化</h2>
<h3 id="虚拟机">虚拟机</h3>
<h4 id="qemu-工作原理">QEMU 工作原理</h4>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721140349.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721140349.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>单纯使用 qemu，采用的是完全虚拟化的模式。qemu 向 Guest OS 模拟 CPU，也模拟其他的硬件，GuestOS 认为自己和硬件直接打交道，其实是同 qemu 模拟出来的硬件打交道，qemu 会将这些指令转译给真正的硬件。由于所有的指令都要从 qemu 里面过一手，因而性能就会比较差。</p>
<p>完全虚拟化是非常慢的，所以要使用硬件辅助虚拟化技术 Intel-VT，AMD-V，所以需要 CPU 硬件开启这个标志位，一般在 BIOS 里面设置。当确认开始了标志位之后，通过 KVM，GuestOS 的 CPU 指令不用经过 Qemu 转译，直接运行，大大提高了速度。所以，KVM 在内核里面需要有一个模块，来设置当前 CPU 是 Guest OS 在用，还是 Host OS 在用。</p>
<p>可以通过如下命令查看内核模块中是否有 KVM</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">lsmod | grep kvm
</span></span></code></pre></div><p>KVM 内核模块通过 <code>/dev/kvm</code> 暴露接口，用户态程序可以通过 <code>ioctl</code>来访问这个接口。Qemu 将 KVM 整合进来，将有关 CPU 指令的部分交由内核模块来做，就是 qemu-kvm (qemu-system-XXX)。</p>
<p>qemu 和 kvm 整合之后，CPU 的性能问题解决了。另外 Qemu 还会模拟其他的硬件，如网络和硬盘。同样，全虚拟化的方式也会影响这些设备的性能。</p>
<p>于是，qemu 采取半虚拟化的方式，让 Guest OS 加载特殊的驱动来做这件事情。</p>
<p>例如，网络需要加载 <code>virtio_net</code>，存储需要加载 <code>virtio_blk</code>，Guest 需要安装这些半虚拟化驱动，GuestOS 知道自己是虚拟机，所以数据会直接发送给半虚拟化设备，经过特殊处理（例如排队、缓存、批量处理等性能优化方式），最终发送给真正的硬件。这在一定程度上提高了性能。</p>
<h3 id="计算虚拟化之-cpu">计算虚拟化之 CPU</h3>
<h3 id="计算虚拟化之内存">计算虚拟化之内存</h3>
]]></content:encoded>
    </item>
    <item>
      <title>Linux 操作系统-进程管理</title>
      <link>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86/</link>
      <pubDate>Mon, 30 Aug 2021 09:41:50 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86/</guid>
      <description>&lt;h2 id=&#34;进程&#34;&gt;进程&lt;/h2&gt;
&lt;h3 id=&#34;源码&#34;&gt;源码&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-C++&#34; data-lang=&#34;C++&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//process.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;sys/types.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create_process&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;program&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arg_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create_process&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;program&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arg_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;pid_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;child_pid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;child_pid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fork&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;child_pid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;child_pid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;execvp&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;program&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arg_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;abort&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在这里，我们创建的子程序运行了一个最最简单的命令 &lt;code&gt;ls&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-C++&#34; data-lang=&#34;C++&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//createprocess.c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;sys/types.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#include&lt;/span&gt; &lt;span class=&#34;cpf&#34;&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;extern&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;create_process&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;program&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;**&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arg_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arg_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s&#34;&gt;&amp;#34;ls&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s&#34;&gt;&amp;#34;-l&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s&#34;&gt;&amp;#34;/etc/yum.repos.d/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nb&#34;&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;create_process&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ls&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arg_list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;编译&#34;&gt;编译&lt;/h3&gt;
&lt;p&gt;CPU 看不懂源码里的函数，命令，CPU 只认二进制数据，所以源码需要翻译成&lt;code&gt;01&lt;/code&gt;二进制数据，这个过程就是**编译（Compile）**的过程。&lt;/p&gt;
&lt;p&gt;编译出的文件好比一个公司的项目执行计划书，你要把一个项目执行好，计划书得有章法，有一定格式。在 Linux 下，二进制程序也有这样的格式，叫&lt;strong&gt;ELF&lt;/strong&gt;（Executeable and Linkable Format，可执行与可链接格式），这个格式可以根据编译的结果不同，分为不同的格式。&lt;/p&gt;
&lt;h3 id=&#34;elf-可重定位文件&#34;&gt;ELF-可重定位文件&lt;/h3&gt;
&lt;p&gt;下图展示了如何从源码到二进制文件的转化&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085445.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085445.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gcc -c -fPIC process.c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gcc -c -fPIC createprocess.c
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-fPIC&lt;/code&gt;作用于编译阶段，告诉编译器产生与位置无关代码 (Position-Independent Code)。产生的代码中，没有绝对地址，全部使用相对地址，故而代码可以被加载器加载到内存的任意位置，都可以正确的执行。&lt;/p&gt;
&lt;p&gt;在编译的时候，先做&lt;strong&gt;预处理&lt;/strong&gt;工作，例如将头文件嵌入到正文中，将定义的宏展开，然后就是真正的编译过程，最终编译成为&lt;code&gt;.o&lt;/code&gt;文件，这就是&lt;code&gt;ELF&lt;/code&gt;的第一种类型，&lt;strong&gt;可重定位文件&lt;/strong&gt;（Relocatable File）。文件格式如下，&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085520.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085520.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ELF&lt;/code&gt; 文件的头是用于描述整个文件的。这个文件格式在内核中有定义，分别为 &lt;code&gt;struct elf32_hdr&lt;/code&gt; 和 &lt;code&gt;struct elf64_hdr&lt;/code&gt;。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;section&lt;/th&gt;
          &lt;th&gt;内容&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;.text&lt;/td&gt;
          &lt;td&gt;放编译好的二进制可执行代码&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;.data&lt;/td&gt;
          &lt;td&gt;已经初始化好的全局变量（临时变量放在栈里）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;.rodata&lt;/td&gt;
          &lt;td&gt;只读数据，例如字符串常量、const 的变量&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;.bss&lt;/td&gt;
          &lt;td&gt;未初始化全局变量，运行时会置 0&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;.symtab&lt;/td&gt;
          &lt;td&gt;符号表，记录的则是函数和变量&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;.strtab&lt;/td&gt;
          &lt;td&gt;字符串表、字符串常量和变量名&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第一种 ELF 文件叫可重定位文件，为啥可重定位？我们可以想象一下，这个编译好的代码和变量，将来&lt;strong&gt;加载到内存&lt;/strong&gt;里面的时候，都是要加载到一定位置的。比如说，调用一个函数，其实就是跳到这个函数所在的代码位置执行；再比如修改一个全局变量，也是要到变量的位置那里去修改。但是现在这个时候，还是&lt;code&gt;.o&lt;/code&gt;文件，不是一个可以直接运行的程序，这里面只是部分代码片段。&lt;/p&gt;
&lt;p&gt;例如这里的 &lt;code&gt;create_process&lt;/code&gt;函数，将来被谁调用，在哪里调用都不清楚，就更别提确定位置了。所以，&lt;code&gt;.o&lt;/code&gt;里面的位置是不确定的，但是必须是可重新定位的，因为它将来是要做函数库的嘛，就是一块砖，哪里需要哪里搬，搬到哪里就重新定位这些代码、变量的位置。&lt;/p&gt;
&lt;h4 id=&#34;elf-可执行文件&#34;&gt;ELF-可执行文件&lt;/h4&gt;
&lt;p&gt;要让&lt;code&gt;create_process&lt;/code&gt;这个函数作为库文件重用，需要将其形成库文件，最简单的类型是静态链接库&lt;code&gt;.a&lt;/code&gt;文件，它将一系列&lt;code&gt;.o&lt;/code&gt;文件归档为一个文件。使用&lt;code&gt;ar&lt;/code&gt;命令创建&lt;code&gt;.a&lt;/code&gt;文件。&lt;a href=&#34;&#34;&gt;使用方法看这里&lt;/a&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ar cr libstaticprocess.a process.o
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;虽然这里 &lt;code&gt;libstaticprocess.a&lt;/code&gt; 里面只有一个&lt;code&gt;.o&lt;/code&gt;，但是实际情况可以有多个&lt;code&gt;.o&lt;/code&gt;。当有程序要使用这个静态连接库的时候，会将.o 文件提取出来，链接到程序中。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-L&lt;/code&gt;表示在当前目录下找&lt;code&gt;.a&lt;/code&gt;文件，&lt;code&gt;-lstaticprocess&lt;/code&gt; 会自动补全文件名，比如加前缀 &lt;code&gt;lib&lt;/code&gt;，后缀&lt;code&gt;.a&lt;/code&gt;，变成 &lt;code&gt;libstaticprocess.a&lt;/code&gt;，找到这个&lt;code&gt;.a&lt;/code&gt;文件后，将里面的 process.o 取出来，和 &lt;code&gt;createprocess.o&lt;/code&gt; 做一个链接，形成二进制执行文件 &lt;code&gt;staticcreateprocess&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在链接过程中，重定位就起作用了，在&lt;code&gt;createprocess.o&lt;/code&gt;里调用了&lt;code&gt;create_process&lt;/code&gt;函数，但是不能确定位置，现在将&lt;code&gt;process.o&lt;/code&gt;合并进来，就知道位置了。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810114727.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810114727.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;这个格式和&lt;code&gt;.o&lt;/code&gt; 文件大致相似，还是分成一个个的 &lt;code&gt;section&lt;/code&gt;，并且被节头表描述。只不过这些&lt;code&gt;section&lt;/code&gt; 是多个&lt;code&gt;.o&lt;/code&gt; 文件合并过的。但是这个时候，这个文件已经是马上就可以加载到内存里面执行的文件了，因而这些 &lt;code&gt;section&lt;/code&gt; 被分成了需要加载到内存里面的&lt;strong&gt;代码段&lt;/strong&gt;、&lt;strong&gt;数据段&lt;/strong&gt;和&lt;strong&gt;不需要加载&lt;/strong&gt;到内存里面的部分，将小的 &lt;code&gt;section&lt;/code&gt; 合成了大的段 &lt;code&gt;segment&lt;/code&gt;，并且在最前面加一个&lt;strong&gt;段头表&lt;/strong&gt;（Segment Header Table）。&lt;/p&gt;
&lt;p&gt;在代码里面的定义为 &lt;code&gt;struct elf32_phdr&lt;/code&gt;和 &lt;code&gt;struct elf64_phdr&lt;/code&gt;，这里面除了有对于段的描述之外，最重要的是 &lt;code&gt;p_vaddr&lt;/code&gt;，这个是这个段加载到内存的虚拟地址。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;ELF&lt;/code&gt; 头里面，有一项 &lt;code&gt;e_entry&lt;/code&gt;，也是个虚拟地址，是这个&lt;strong&gt;程序运行的入口&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id=&#34;elf-共享对象文件&#34;&gt;ELF-共享对象文件&lt;/h4&gt;
&lt;p&gt;静态库一旦被链接，代码和变量的&lt;code&gt;section&lt;/code&gt;会被合并，所以运行时不依赖静态库文件，但是缺点就是，相同代码段被多个程序使用，在&lt;strong&gt;内存里会有多份&lt;/strong&gt;，而且&lt;strong&gt;静态库更新需要重新编译&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因而就出现了另一种，&lt;strong&gt;动态链接库&lt;/strong&gt;（Shared Libraries），不仅仅是一组对象文件的简单归档，而是多个对象文件的重新组合，可被多个程序共享。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gcc -shared -fPIC -o libdynamicprocess.so process.o
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当一个动态链接库被链接到一个程序文件中的时候，最后的程序文件并不包括动态链接库中的代码，而仅仅包括对动态链接库的引用，并且不保存动态链接库的全路径，仅仅保存动态链接库的名称。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;当运行这个程序的时候，首先寻找动态链接库，然后加载它。默认情况下，系统在 &lt;code&gt;/lib&lt;/code&gt; 和&lt;code&gt;/usr/lib&lt;/code&gt; 文件夹下寻找动态链接库。如果找不到就会报错，我们可以设定 &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;环境变量，程序运行时会在此环境变量指定的文件夹下寻找动态链接库。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# export LD_LIBRARY_PATH=.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ./dynamiccreateprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# total 40&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rw&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--.&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1572&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Oct&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;24&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;38&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CentOS&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Base&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;......&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;动态链接库，就是&lt;code&gt;ELF&lt;/code&gt;的第三种类型，&lt;strong&gt;共享对象文件&lt;/strong&gt;（Shared Object）。&lt;/p&gt;
&lt;p&gt;文件格式和上两种文件稍有不同，首先，多了一个&lt;code&gt;.interp&lt;/code&gt;的 &lt;code&gt;Segment&lt;/code&gt;，这里面是 &lt;code&gt;ld-linux.so&lt;/code&gt;，这是动态链接器，也就是说，运行时的链接动作都是它做的。&lt;/p&gt;
&lt;p&gt;另外，&lt;code&gt;ELF&lt;/code&gt;文件中还多了两个&lt;code&gt;section&lt;/code&gt;，一个是&lt;code&gt;.plt&lt;/code&gt;，&lt;strong&gt;过程链接表&lt;/strong&gt;（Procedure Linkage Table，PLT），一个是。&lt;code&gt;got.plt&lt;/code&gt;，&lt;strong&gt;全局偏移量表&lt;/strong&gt;（Global Offset Table，GOT）。&lt;/p&gt;
&lt;h3 id=&#34;运行&#34;&gt;运行&lt;/h3&gt;
&lt;p&gt;在内核中，有&lt;code&gt;linux_binfmt elf_format&lt;/code&gt;数据结构定义了加载 ELF 的方法，使用&lt;code&gt;load_elf_binary&lt;/code&gt;加载二进制文件，该函数由&lt;code&gt;do_execve&lt;/code&gt;调用，学过系统调用知道&lt;code&gt;exec&lt;/code&gt;调用了&lt;code&gt;do_execve&lt;/code&gt;函数。所以流程为&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-gdscript3&#34; data-lang=&#34;gdscript3&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;exec&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;do_execve&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;load_elf_binary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;进程树&#34;&gt;进程树&lt;/h4&gt;
&lt;p&gt;所有进程都是从父进程 fork 来的，祖宗进程就是&lt;code&gt;init&lt;/code&gt; 进程。&lt;/p&gt;
&lt;p&gt;系统启动之后，&lt;code&gt;init&lt;/code&gt; 进程会启动很多的&lt;code&gt;daemon&lt;/code&gt; 进程，为系统运行提供服务，然后就是启动 &lt;code&gt;getty&lt;/code&gt;，让用户登录，登录后运行 &lt;code&gt;shell&lt;/code&gt;，用户启动的进程都是通过 &lt;code&gt;shell&lt;/code&gt;运行的，从而形成了一棵进程树。&lt;/p&gt;
&lt;p&gt;我们可以通过 &lt;code&gt;ps -ef&lt;/code&gt;命令查看当前系统启动的进程，我们会发现有三类进程。&lt;code&gt;PID 1&lt;/code&gt; 的进程就是我们的&lt;code&gt;init&lt;/code&gt;进程 &lt;code&gt;systemd&lt;/code&gt;，&lt;code&gt;PID 2&lt;/code&gt; 的进程是内核线程 &lt;code&gt;kthreadd&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;内核态进程的&lt;code&gt;PPID&lt;/code&gt;祖先进程都是 2 号进程，用户态进程祖先进程都是 1 号进程，&lt;code&gt;tty&lt;/code&gt;列是问号的，说明是后台服务进程。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810143343.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810143343.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h2 id=&#34;进程数据结构&#34;&gt;进程数据结构&lt;/h2&gt;
&lt;p&gt;在 Linux 里面，无论是进程还是线程，到了内核里面，我们统一都叫任务（Task），由一个统一的结构&lt;code&gt;task_struct&lt;/code&gt;进行管理。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826183622.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826183622.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;每个任务应该包含的字段：&lt;/p&gt;
&lt;h3 id=&#34;任务-id&#34;&gt;任务 ID&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pid_t pid; #process id
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pid_t tgid; #thread group ID
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;struct task_struct *group_leader; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;为何要有这么多 ID，一个不够吗？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以方便任务展示，比如在命令行中 ps 显示所有进程，只显示&lt;code&gt;pid_t pid&lt;/code&gt;，而不会把所有内部线程摊开展示，这样太碍眼。&lt;/li&gt;
&lt;li&gt;方便下达命令，当我 kill 一个进程时，我们是对整个进程发送信号，但是有时候一些命令只需要对某个线程发送信号。&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;信号处理&#34;&gt;信号处理&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* Signal handlers: */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;signal_struct&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;signal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sighand_struct&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sighand&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;sigset_t&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;blocked&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;sigset_t&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;real_blocked&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;sigset_t&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;saved_sigmask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sigpending&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;pending&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;sas_ss_sp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;sas_ss_size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;sas_ss_flags&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里定义了哪些信号被阻塞暂不处理（blocked），哪些信号尚等待处理（pending），哪些信号正在通过信号处理函数进行处理（sighand）。处理的结果可以是忽略，可以是结束进程等等。&lt;/p&gt;
&lt;h3 id=&#34;任务状态&#34;&gt;任务状态&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;volatile long state;    /* -1 unrunnable, 0 runnable, &amp;gt;0 stopped */
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;int exit_state;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;unsigned int flags;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;state&lt;/code&gt;可取值定义如下&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* Used in tsk-&amp;gt;state: */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_RUNNING                    0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_INTERRUPTIBLE              1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_UNINTERRUPTIBLE            2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define __TASK_STOPPED                  4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define __TASK_TRACED                   8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* Used in tsk-&amp;gt;exit_state: */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define EXIT_DEAD                       16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define EXIT_ZOMBIE                     32
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define EXIT_TRACE                      (EXIT_ZOMBIE | EXIT_DEAD)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* Used in tsk-&amp;gt;state again: */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_DEAD                       64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_WAKEKILL                   128
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_WAKING                     256
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_PARKED                     512
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_NOLOAD                     1024
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_NEW                        2048
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define TASK_STATE_MAX                  4096
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可以发现 Linux 通过 bitset 方式设置状态，当前什么状态，哪一位就置 1。&lt;/p&gt;
&lt;h3 id=&#34;进程调度&#34;&gt;进程调度&lt;/h3&gt;
&lt;p&gt;进程的状态切换往往涉及调度，下面这些字段都是用于调度的。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 是否在运行队列上
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;               &lt;span class=&#34;n&#34;&gt;on_rq&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 优先级
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;               &lt;span class=&#34;n&#34;&gt;prio&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;               &lt;span class=&#34;n&#34;&gt;static_prio&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;               &lt;span class=&#34;n&#34;&gt;normal_prio&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;rt_priority&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 调度器类
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sched_class&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sched_class&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 调度实体
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sched_entity&lt;/span&gt;       &lt;span class=&#34;n&#34;&gt;se&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sched_rt_entity&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;rt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sched_dl_entity&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;dl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 调度策略
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;policy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// 可以使用哪些 CPU
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;            &lt;span class=&#34;n&#34;&gt;nr_cpus_allowed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;cpumask_t&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;cpus_allowed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sched_info&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;sched_info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;运行统计信息&#34;&gt;运行统计信息&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;u64&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;utime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 用户态消耗的 CPU 时间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;u64&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;stime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 内核态消耗的 CPU 时间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;nvcsw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 自愿 (voluntary) 上下文切换计数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt;      &lt;span class=&#34;n&#34;&gt;nivcsw&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 非自愿 (involuntary) 上下文切换计数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;u64&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;start_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 进程启动时间，不包含睡眠时间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;u64&lt;/span&gt;        &lt;span class=&#34;n&#34;&gt;real_start_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// 进程启动时间，包含睡眠时间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;进程亲缘关系&#34;&gt;进程亲缘关系&lt;/h3&gt;
&lt;p&gt;进程有棵进程树，所以有亲缘关系。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;task_struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__rcu&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;real_parent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* real parent process */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;task_struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__rcu&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;parent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* recipient of SIGCHLD, wait4() reports */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;list_head&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;children&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;      &lt;span class=&#34;cm&#34;&gt;/* list of my children */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;list_head&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sibling&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;       &lt;span class=&#34;cm&#34;&gt;/* linkage in my parent&amp;#39;s children list */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通常情况下，&lt;code&gt;real_parent&lt;/code&gt; 和 &lt;code&gt;parent&lt;/code&gt; 是一样的，但是也会有另外的情况存在。例如，&lt;code&gt;bash&lt;/code&gt; 创建一个进程，那进程的 parent 和 &lt;code&gt;real_parent&lt;/code&gt; 就都是 &lt;code&gt;bash&lt;/code&gt;。如果在 bash 上使用 &lt;code&gt;GDB&lt;/code&gt; 来 &lt;code&gt;debug&lt;/code&gt; 一个进程，这个时候 &lt;code&gt;GDB&lt;/code&gt; 是 &lt;code&gt;real_parent&lt;/code&gt;，&lt;code&gt;bash&lt;/code&gt; 是这个进程的 &lt;code&gt;parent&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;进程权限&#34;&gt;进程权限&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* Objective and real subjective task credentials (COW): */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cred&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__rcu&lt;/span&gt;         &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;real_cred&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/* Effective (overridable) subjective task credentials (COW): */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cred&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__rcu&lt;/span&gt;   
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;real_cred&lt;/code&gt; 就是说明谁能操作我这个进程，而 &lt;code&gt;cred&lt;/code&gt; 就是说明我这个进程能够操作谁。&lt;/p&gt;
&lt;p&gt;总结到一起，&lt;code&gt;task_struct&lt;/code&gt;结构图如下，&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826190834.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826190834.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="进程">进程</h2>
<h3 id="源码">源码</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C++" data-lang="C++"><span class="line"><span class="cl"><span class="c1">//process.c
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="kt">int</span> <span class="nf">create_process</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">program</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">arg_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">create_process</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">program</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">arg_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">pid_t</span> <span class="n">child_pid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">child_pid</span> <span class="o">=</span> <span class="n">fork</span> <span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">child_pid</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">child_pid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span> 
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">execvp</span> <span class="p">(</span><span class="n">program</span><span class="p">,</span> <span class="n">arg_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">abort</span> <span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>在这里，我们创建的子程序运行了一个最最简单的命令 <code>ls</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C++" data-lang="C++"><span class="line"><span class="cl"><span class="c1">//createprocess.c
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="k">extern</span> <span class="kt">int</span> <span class="nf">create_process</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span> <span class="n">program</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span> <span class="n">arg_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span><span class="o">*</span> <span class="n">arg_list</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;ls&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;-l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;/etc/yum.repos.d/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nb">NULL</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_process</span> <span class="p">(</span><span class="s">&#34;ls&#34;</span><span class="p">,</span> <span class="n">arg_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="编译">编译</h3>
<p>CPU 看不懂源码里的函数，命令，CPU 只认二进制数据，所以源码需要翻译成<code>01</code>二进制数据，这个过程就是**编译（Compile）**的过程。</p>
<p>编译出的文件好比一个公司的项目执行计划书，你要把一个项目执行好，计划书得有章法，有一定格式。在 Linux 下，二进制程序也有这样的格式，叫<strong>ELF</strong>（Executeable and Linkable Format，可执行与可链接格式），这个格式可以根据编译的结果不同，分为不同的格式。</p>
<h3 id="elf-可重定位文件">ELF-可重定位文件</h3>
<p>下图展示了如何从源码到二进制文件的转化</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085445.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085445.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">gcc -c -fPIC process.c
</span></span><span class="line"><span class="cl">gcc -c -fPIC createprocess.c
</span></span></code></pre></div><p><code>-fPIC</code>作用于编译阶段，告诉编译器产生与位置无关代码 (Position-Independent Code)。产生的代码中，没有绝对地址，全部使用相对地址，故而代码可以被加载器加载到内存的任意位置，都可以正确的执行。</p>
<p>在编译的时候，先做<strong>预处理</strong>工作，例如将头文件嵌入到正文中，将定义的宏展开，然后就是真正的编译过程，最终编译成为<code>.o</code>文件，这就是<code>ELF</code>的第一种类型，<strong>可重定位文件</strong>（Relocatable File）。文件格式如下，</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085520.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810085520.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p><code>ELF</code> 文件的头是用于描述整个文件的。这个文件格式在内核中有定义，分别为 <code>struct elf32_hdr</code> 和 <code>struct elf64_hdr</code>。</p>
<table>
  <thead>
      <tr>
          <th>section</th>
          <th>内容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>.text</td>
          <td>放编译好的二进制可执行代码</td>
      </tr>
      <tr>
          <td>.data</td>
          <td>已经初始化好的全局变量（临时变量放在栈里）</td>
      </tr>
      <tr>
          <td>.rodata</td>
          <td>只读数据，例如字符串常量、const 的变量</td>
      </tr>
      <tr>
          <td>.bss</td>
          <td>未初始化全局变量，运行时会置 0</td>
      </tr>
      <tr>
          <td>.symtab</td>
          <td>符号表，记录的则是函数和变量</td>
      </tr>
      <tr>
          <td>.strtab</td>
          <td>字符串表、字符串常量和变量名</td>
      </tr>
  </tbody>
</table>
<p>第一种 ELF 文件叫可重定位文件，为啥可重定位？我们可以想象一下，这个编译好的代码和变量，将来<strong>加载到内存</strong>里面的时候，都是要加载到一定位置的。比如说，调用一个函数，其实就是跳到这个函数所在的代码位置执行；再比如修改一个全局变量，也是要到变量的位置那里去修改。但是现在这个时候，还是<code>.o</code>文件，不是一个可以直接运行的程序，这里面只是部分代码片段。</p>
<p>例如这里的 <code>create_process</code>函数，将来被谁调用，在哪里调用都不清楚，就更别提确定位置了。所以，<code>.o</code>里面的位置是不确定的，但是必须是可重新定位的，因为它将来是要做函数库的嘛，就是一块砖，哪里需要哪里搬，搬到哪里就重新定位这些代码、变量的位置。</p>
<h4 id="elf-可执行文件">ELF-可执行文件</h4>
<p>要让<code>create_process</code>这个函数作为库文件重用，需要将其形成库文件，最简单的类型是静态链接库<code>.a</code>文件，它将一系列<code>.o</code>文件归档为一个文件。使用<code>ar</code>命令创建<code>.a</code>文件。<a href="">使用方法看这里</a>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">ar cr libstaticprocess.a process.o
</span></span></code></pre></div><p>虽然这里 <code>libstaticprocess.a</code> 里面只有一个<code>.o</code>，但是实际情况可以有多个<code>.o</code>。当有程序要使用这个静态连接库的时候，会将.o 文件提取出来，链接到程序中。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">gcc -o staticcreateprocess createprocess.o -L. -lstaticprocess
</span></span></code></pre></div><p><code>-L</code>表示在当前目录下找<code>.a</code>文件，<code>-lstaticprocess</code> 会自动补全文件名，比如加前缀 <code>lib</code>，后缀<code>.a</code>，变成 <code>libstaticprocess.a</code>，找到这个<code>.a</code>文件后，将里面的 process.o 取出来，和 <code>createprocess.o</code> 做一个链接，形成二进制执行文件 <code>staticcreateprocess</code>。</p>
<p>在链接过程中，重定位就起作用了，在<code>createprocess.o</code>里调用了<code>create_process</code>函数，但是不能确定位置，现在将<code>process.o</code>合并进来，就知道位置了。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810114727.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810114727.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>这个格式和<code>.o</code> 文件大致相似，还是分成一个个的 <code>section</code>，并且被节头表描述。只不过这些<code>section</code> 是多个<code>.o</code> 文件合并过的。但是这个时候，这个文件已经是马上就可以加载到内存里面执行的文件了，因而这些 <code>section</code> 被分成了需要加载到内存里面的<strong>代码段</strong>、<strong>数据段</strong>和<strong>不需要加载</strong>到内存里面的部分，将小的 <code>section</code> 合成了大的段 <code>segment</code>，并且在最前面加一个<strong>段头表</strong>（Segment Header Table）。</p>
<p>在代码里面的定义为 <code>struct elf32_phdr</code>和 <code>struct elf64_phdr</code>，这里面除了有对于段的描述之外，最重要的是 <code>p_vaddr</code>，这个是这个段加载到内存的虚拟地址。</p>
<p>在 <code>ELF</code> 头里面，有一项 <code>e_entry</code>，也是个虚拟地址，是这个<strong>程序运行的入口</strong>。</p>
<h4 id="elf-共享对象文件">ELF-共享对象文件</h4>
<p>静态库一旦被链接，代码和变量的<code>section</code>会被合并，所以运行时不依赖静态库文件，但是缺点就是，相同代码段被多个程序使用，在<strong>内存里会有多份</strong>，而且<strong>静态库更新需要重新编译</strong>。</p>
<p>因而就出现了另一种，<strong>动态链接库</strong>（Shared Libraries），不仅仅是一组对象文件的简单归档，而是多个对象文件的重新组合，可被多个程序共享。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">gcc -shared -fPIC -o libdynamicprocess.so process.o
</span></span></code></pre></div><p>当一个动态链接库被链接到一个程序文件中的时候，最后的程序文件并不包括动态链接库中的代码，而仅仅包括对动态链接库的引用，并且不保存动态链接库的全路径，仅仅保存动态链接库的名称。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">gcc -o dynamiccreateprocess createprocess.o -L. -ldynamicprocess
</span></span></code></pre></div><p>当运行这个程序的时候，首先寻找动态链接库，然后加载它。默认情况下，系统在 <code>/lib</code> 和<code>/usr/lib</code> 文件夹下寻找动态链接库。如果找不到就会报错，我们可以设定 <code>LD_LIBRARY_PATH</code>环境变量，程序运行时会在此环境变量指定的文件夹下寻找动态链接库。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="c1"># export LD_LIBRARY_PATH=.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ./dynamiccreateprocess</span>
</span></span><span class="line"><span class="cl"><span class="c1"># total 40</span>
</span></span><span class="line"><span class="cl"><span class="o">-</span><span class="n">rw</span><span class="o">-</span><span class="n">r</span><span class="o">--</span><span class="n">r</span><span class="o">--.</span> <span class="mi">1</span> <span class="n">root</span> <span class="n">root</span> <span class="mi">1572</span> <span class="n">Oct</span> <span class="mi">24</span> <span class="mi">18</span><span class="p">:</span><span class="mi">38</span> <span class="n">CentOS</span><span class="o">-</span><span class="n">Base</span><span class="o">.</span><span class="n">repo</span>
</span></span><span class="line"><span class="cl"><span class="o">......</span>
</span></span></code></pre></div><p>动态链接库，就是<code>ELF</code>的第三种类型，<strong>共享对象文件</strong>（Shared Object）。</p>
<p>文件格式和上两种文件稍有不同，首先，多了一个<code>.interp</code>的 <code>Segment</code>，这里面是 <code>ld-linux.so</code>，这是动态链接器，也就是说，运行时的链接动作都是它做的。</p>
<p>另外，<code>ELF</code>文件中还多了两个<code>section</code>，一个是<code>.plt</code>，<strong>过程链接表</strong>（Procedure Linkage Table，PLT），一个是。<code>got.plt</code>，<strong>全局偏移量表</strong>（Global Offset Table，GOT）。</p>
<h3 id="运行">运行</h3>
<p>在内核中，有<code>linux_binfmt elf_format</code>数据结构定义了加载 ELF 的方法，使用<code>load_elf_binary</code>加载二进制文件，该函数由<code>do_execve</code>调用，学过系统调用知道<code>exec</code>调用了<code>do_execve</code>函数。所以流程为</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="cl"><span class="n">exec</span><span class="o">-&gt;</span><span class="n">do_execve</span><span class="o">-&gt;</span><span class="n">load_elf_binary</span>
</span></span></code></pre></div><h4 id="进程树">进程树</h4>
<p>所有进程都是从父进程 fork 来的，祖宗进程就是<code>init</code> 进程。</p>
<p>系统启动之后，<code>init</code> 进程会启动很多的<code>daemon</code> 进程，为系统运行提供服务，然后就是启动 <code>getty</code>，让用户登录，登录后运行 <code>shell</code>，用户启动的进程都是通过 <code>shell</code>运行的，从而形成了一棵进程树。</p>
<p>我们可以通过 <code>ps -ef</code>命令查看当前系统启动的进程，我们会发现有三类进程。<code>PID 1</code> 的进程就是我们的<code>init</code>进程 <code>systemd</code>，<code>PID 2</code> 的进程是内核线程 <code>kthreadd</code>。</p>
<p>内核态进程的<code>PPID</code>祖先进程都是 2 号进程，用户态进程祖先进程都是 1 号进程，<code>tty</code>列是问号的，说明是后台服务进程。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810143343.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210810143343.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h2 id="进程数据结构">进程数据结构</h2>
<p>在 Linux 里面，无论是进程还是线程，到了内核里面，我们统一都叫任务（Task），由一个统一的结构<code>task_struct</code>进行管理。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826183622.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826183622.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>每个任务应该包含的字段：</p>
<h3 id="任务-id">任务 ID</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">pid_t pid; #process id
</span></span><span class="line"><span class="cl">pid_t tgid; #thread group ID
</span></span><span class="line"><span class="cl">struct task_struct *group_leader; 
</span></span></code></pre></div><p>为何要有这么多 ID，一个不够吗？</p>
<ul>
<li>可以方便任务展示，比如在命令行中 ps 显示所有进程，只显示<code>pid_t pid</code>，而不会把所有内部线程摊开展示，这样太碍眼。</li>
<li>方便下达命令，当我 kill 一个进程时，我们是对整个进程发送信号，但是有时候一些命令只需要对某个线程发送信号。</li>
<li></li>
</ul>
<h3 id="信号处理">信号处理</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* Signal handlers: */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">signal_struct</span>    <span class="o">*</span><span class="n">signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sighand_struct</span>    <span class="o">*</span><span class="n">sighand</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">sigset_t</span>      <span class="n">blocked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">sigset_t</span>      <span class="n">real_blocked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">sigset_t</span>      <span class="n">saved_sigmask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sigpending</span>    <span class="n">pending</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">long</span>      <span class="n">sas_ss_sp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">size_t</span>        <span class="n">sas_ss_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">int</span>      <span class="n">sas_ss_flags</span><span class="p">;</span>
</span></span></code></pre></div><p>这里定义了哪些信号被阻塞暂不处理（blocked），哪些信号尚等待处理（pending），哪些信号正在通过信号处理函数进行处理（sighand）。处理的结果可以是忽略，可以是结束进程等等。</p>
<h3 id="任务状态">任务状态</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">volatile long state;    /* -1 unrunnable, 0 runnable, &gt;0 stopped */
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">int exit_state;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">unsigned int flags;
</span></span></code></pre></div><p><code>state</code>可取值定义如下</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* Used in tsk-&gt;state: */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define TASK_RUNNING                    0
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_INTERRUPTIBLE              1
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_UNINTERRUPTIBLE            2
</span></span></span><span class="line"><span class="cl"><span class="cp">#define __TASK_STOPPED                  4
</span></span></span><span class="line"><span class="cl"><span class="cp">#define __TASK_TRACED                   8
</span></span></span><span class="line"><span class="cl"><span class="cm">/* Used in tsk-&gt;exit_state: */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define EXIT_DEAD                       16
</span></span></span><span class="line"><span class="cl"><span class="cp">#define EXIT_ZOMBIE                     32
</span></span></span><span class="line"><span class="cl"><span class="cp">#define EXIT_TRACE                      (EXIT_ZOMBIE | EXIT_DEAD)
</span></span></span><span class="line"><span class="cl"><span class="cm">/* Used in tsk-&gt;state again: */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#define TASK_DEAD                       64
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_WAKEKILL                   128
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_WAKING                     256
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_PARKED                     512
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_NOLOAD                     1024
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_NEW                        2048
</span></span></span><span class="line"><span class="cl"><span class="cp">#define TASK_STATE_MAX                  4096
</span></span></span></code></pre></div><p>可以发现 Linux 通过 bitset 方式设置状态，当前什么状态，哪一位就置 1。</p>
<h3 id="进程调度">进程调度</h3>
<p>进程的状态切换往往涉及调度，下面这些字段都是用于调度的。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// 是否在运行队列上
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span>               <span class="n">on_rq</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 优先级
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span>               <span class="n">prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span>               <span class="n">static_prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span>               <span class="n">normal_prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">int</span>      <span class="n">rt_priority</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 调度器类
</span></span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">sched_class</span>  <span class="o">*</span><span class="n">sched_class</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 调度实体
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sched_entity</span>       <span class="n">se</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sched_rt_entity</span>    <span class="n">rt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sched_dl_entity</span>    <span class="n">dl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 调度策略
</span></span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">int</span>      <span class="n">policy</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 可以使用哪些 CPU
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span>            <span class="n">nr_cpus_allowed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">cpumask_t</span>      <span class="n">cpus_allowed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sched_info</span>    <span class="n">sched_info</span><span class="p">;</span>
</span></span></code></pre></div><h3 id="运行统计信息">运行统计信息</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">u64</span>        <span class="n">utime</span><span class="p">;</span><span class="c1">// 用户态消耗的 CPU 时间
</span></span></span><span class="line"><span class="cl"><span class="n">u64</span>        <span class="n">stime</span><span class="p">;</span><span class="c1">// 内核态消耗的 CPU 时间
</span></span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">long</span>      <span class="n">nvcsw</span><span class="p">;</span><span class="c1">// 自愿 (voluntary) 上下文切换计数
</span></span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">long</span>      <span class="n">nivcsw</span><span class="p">;</span><span class="c1">// 非自愿 (involuntary) 上下文切换计数
</span></span></span><span class="line"><span class="cl"><span class="n">u64</span>        <span class="n">start_time</span><span class="p">;</span><span class="c1">// 进程启动时间，不包含睡眠时间
</span></span></span><span class="line"><span class="cl"><span class="n">u64</span>        <span class="n">real_start_time</span><span class="p">;</span><span class="c1">// 进程启动时间，包含睡眠时间
</span></span></span></code></pre></div><h3 id="进程亲缘关系">进程亲缘关系</h3>
<p>进程有棵进程树，所以有亲缘关系。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">task_struct</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">real_parent</span><span class="p">;</span> <span class="cm">/* real parent process */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">task_struct</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">parent</span><span class="p">;</span> <span class="cm">/* recipient of SIGCHLD, wait4() reports */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">list_head</span> <span class="n">children</span><span class="p">;</span>      <span class="cm">/* list of my children */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">list_head</span> <span class="n">sibling</span><span class="p">;</span>       <span class="cm">/* linkage in my parent&#39;s children list */</span>
</span></span></code></pre></div><p>通常情况下，<code>real_parent</code> 和 <code>parent</code> 是一样的，但是也会有另外的情况存在。例如，<code>bash</code> 创建一个进程，那进程的 parent 和 <code>real_parent</code> 就都是 <code>bash</code>。如果在 bash 上使用 <code>GDB</code> 来 <code>debug</code> 一个进程，这个时候 <code>GDB</code> 是 <code>real_parent</code>，<code>bash</code> 是这个进程的 <code>parent</code>。</p>
<h3 id="进程权限">进程权限</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* Objective and real subjective task credentials (COW): */</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">cred</span> <span class="n">__rcu</span>         <span class="o">*</span><span class="n">real_cred</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* Effective (overridable) subjective task credentials (COW): */</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="k">struct</span> <span class="n">cred</span> <span class="n">__rcu</span>   
</span></span></code></pre></div><p><code>real_cred</code> 就是说明谁能操作我这个进程，而 <code>cred</code> 就是说明我这个进程能够操作谁。</p>
<p>总结到一起，<code>task_struct</code>结构图如下，</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826190834.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210826190834.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux 操作系统-系统初始化</title>
      <link>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E7%B3%BB%E7%BB%9F%E5%88%9D%E5%A7%8B%E5%8C%96/</link>
      <pubDate>Tue, 24 Aug 2021 09:45:57 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E7%B3%BB%E7%BB%9F%E5%88%9D%E5%A7%8B%E5%8C%96/</guid>
      <description>&lt;h2 id=&#34;系统初始化&#34;&gt;系统初始化&lt;/h2&gt;
&lt;h3 id=&#34;x86-架构概述&#34;&gt;x86 架构概述&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CPU（Central Processing Unit）&lt;/strong&gt;：中央处理器，计算机所有设备都围绕它展开工作。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;运算单元：只管算，例如做加法、做位移等等。但是，它不知道应该算哪些数据，运算结果应该放在哪里。&lt;/li&gt;
&lt;li&gt;数据单元：运算单元计算的数据如果每次都要经过总线，到内存里面现拿，这样就太慢了，所以就有了数据单元。数据单元包括 CPU 内部的缓存和寄存器组，空间很小，但是速度飞快，可以暂时存放数据和运算结果。&lt;/li&gt;
&lt;li&gt;控制单元：有了放数据的地方，也有了算的地方，还需要有个指挥到底做什么运算的地方，这就是控制单元。控制单元是一个统一的指挥中心，它可以获得下一条指令，然后执行这条指令。这个指令会指导运算单元取出数据单元中的某几个数据，计算出个结果，然后放在数据单元的某个地方。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;内存（Memory）&lt;/strong&gt;：CPU 本身不能保存大量数据，许多复杂的计算需要将中间结果保存下来就必须用到内存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总线（Bus）&lt;/strong&gt;：CPU 和其他设备连接，就靠总线，其实就是主板上密密麻麻的集成电路，这些东西组成了 CPU 和其他设备的高速通道。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;地址总线：传输地址数据（我想拿内存中哪个位置的数据）&lt;/li&gt;
&lt;li&gt;数据总线：传输真正的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总线就像 CPU 和内存之间的高速公路，总线多少位就类似高速公路多少个车道，但两种总线的位数意义不同。&lt;/p&gt;
&lt;p&gt;地址总线的位数决定了访问地址范围有多广，数据总线位数决定了一次能拿多少数据进来。那么 CPU 中总线的位数有没有标准呢？如果没有标准，那操作系统作为软件就很难办了，因为软件层没办法实现通用的运算逻辑。早期每家公司的 CPU 架构都不同，后来历史将 x86 平台推到了&lt;strong&gt;开放，统一，兼容&lt;/strong&gt;的位置。&lt;/p&gt;
&lt;h4 id=&#34;8086-架构图&#34;&gt;8086 架构图&lt;/h4&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721092854.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721092854.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据单元：&lt;/strong&gt; 8086 处理器内部共有 8 个 16 位的通用寄存器，分别是 数据寄存器（AX、BX、CX、DX）、指针寄存器（SP、BP）、变址寄存器（SI、DI）。其中 AX、BX、CX、DX 可以分成两个 8 位的寄存器来使用，分别是 AH、AL、BH、BL、CH、CL、DH、DL，其中 H 就是 High（高位），L 就是 Low（低位）的意思。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;控制单元：&lt;/strong&gt; IP 寄存器（Instruction Pointer Register）就是指令指针寄存器，它指向代码段中下一条指令的位置。CPU 会根据它来不断地将指令从内存的代码段中，加载到 CPU 的指令队列中，然后交给运算单元去执行。&lt;/p&gt;
&lt;p&gt;如果需要切换进程呢？每个进程都分代码段和数据段，为了指向不同进程的地址空间，有四个 16 位的段寄存器，分别是 CS、DS、SS、ES。&lt;/p&gt;
&lt;p&gt;其中，&lt;strong&gt;CS&lt;/strong&gt; 就是代码段寄存器（Code Segment Register），通过它可以找到代码在内存中的位置；&lt;strong&gt;DS&lt;/strong&gt; 是数据段的寄存器（Data Segment Register），通过它可以找到数据在内存中的位置。&lt;strong&gt;SS&lt;/strong&gt; 是栈寄存器（Stack Register）。栈是程序运行中一个特殊的数据结构，数据的存取只能从一端进行，秉承后进先出的原则。&lt;strong&gt;ES&lt;/strong&gt;是扩展段寄存器（Extra Segment Register）顾名思义。&lt;/p&gt;
&lt;p&gt;如果 CPU 运算中需要加载内存中的数据，需要通过 DS 找到内存中的数据，加载到通用寄存器中，应该如何加载呢？对于一个段，有一个起始的地址，而段内的具体位置，我们称为&lt;strong&gt;偏移量（Offset）&lt;/strong&gt;。在 CS 和 DS 中都存放着一个段的起始地址。&lt;strong&gt;代码段的偏移量在 IP 寄存器中&lt;/strong&gt;，&lt;strong&gt;数据段的偏移量会放在通用寄存器中&lt;/strong&gt;。因为段寄存器都是 16 位的，而地址总线是 20 位的，所以通过 *&lt;em&gt;起始地址 &lt;em&gt;16+ 偏移量&lt;/em&gt;&lt;/em&gt; 的方式，将寻址位数都变成 20 位，也就是将 CS 和 DS 的值左移 4 位。&lt;/p&gt;
&lt;p&gt;对于只有 20 位地址总线的 8086 来说，寻址空间最大也就是$2^{20}=1\text{M}$，超过这个位置就访问不到了，一个段因为偏移量只有 16 位，所以一个段最大是$2^{16}=64\text{k}$。&lt;/p&gt;
&lt;h4 id=&#34;32-位处理器&#34;&gt;32 位处理器&lt;/h4&gt;
&lt;p&gt;随着计算机发展，内存越来越大，总线也越来越宽。在 32 位处理器中，有 32 根地址总线，可以访问 $2^{32}=4\text{G}$ 的内存。使用原来的模式肯定不行了，但是又不能完全抛弃原来的模式，因为这个架构是&lt;strong&gt;开放的&lt;/strong&gt;。那么在开发架构的基础上如何保持兼容呢？&lt;/p&gt;
&lt;p&gt;首先，通用寄存器有扩展，可以将 8 个 16 位的扩展到 8 个 32 位的，但是依然可以保留 16 位的和 8 位的使用方式。其中，指向下一条指令的指令指针寄存器 IP，就会扩展成 32 位的，同样也兼容 16 位的。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721103205.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721103205.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;段寄存器改动较大，新的段寄存器都改成了 32 位的，每个寄存器又分为&lt;strong&gt;段描述符缓存器（Segment Descriptor）&lt;/strong&gt;，和&lt;strong&gt;段选择子寄存器（Selector）&lt;/strong&gt; ,现在的段寄存器不在是段的起始地址，段的起始地址保存在表格一样的段描述符缓冲器中，段选择子寄存器保存地址在段描述符缓存器中的哪一项。这样，将一个从段寄存器直接拿到的段起始地址，就变成了&lt;strong&gt;先间接地从段寄存器找到表格中的一项，再从表格中的一项中拿到段起始地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;虽然现在的这种模式和之前的模式不兼容，但是后面这种模式灵活的非常高，可以保持一直兼容下去。在 32 位的系统架构下，将前一种模式称为&lt;strong&gt;实模式（Real Pattern）&lt;/strong&gt;，后一种模式称为&lt;strong&gt;保护模式（Protected Pattern）&lt;/strong&gt; 。当系统刚刚启动的时候，CPU 是处于实模式的，这个时候和原来的模式是兼容的。也就是说，哪怕你买了 32 位的 CPU，也支持在原来的模式下运行。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721104550.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721104550.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;汇编命令学习
mov,
call, jmp, int, ret, add, or, xor, shl, shr, push, pop, inc, dec, sub, cmp。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;bios-与-bootloader&#34;&gt;BIOS 与 BootLoader&lt;/h3&gt;
&lt;p&gt;BIOS：基本输入输出系统&lt;/p&gt;
&lt;p&gt;ROM：只读存储器&lt;/p&gt;
&lt;p&gt;RAM：随机存取存储器&lt;/p&gt;
&lt;p&gt;在我们按下电脑电源键的那一刻，主板就加电了，CPU 就要开始执行指令了，但是刚开始操作系统都没，CPU 执行什么指令呢？这就有了&lt;code&gt;BIOS&lt;/code&gt;，它相当于一个指导手册，告诉 CPU 接下来要干啥。&lt;/p&gt;
&lt;p&gt;刚开机时，系统初始化代码从 ROM 读取，将 &lt;code&gt;CS&lt;/code&gt; 设置为 &lt;code&gt;0xFFFF&lt;/code&gt;，将 &lt;code&gt;IP&lt;/code&gt; 设置为 &lt;code&gt;0x0000&lt;/code&gt;，所以第一条指令就会指向 &lt;code&gt;0xFFFF0&lt;/code&gt;，初始化完成后确定访问指令位置。&lt;/p&gt;
&lt;p&gt;接下来 BIOS 会检查各个硬件是否正常，检测内容显卡等关键部件的存在于工作状态，设备初始化，执行系统 BIOS 进行系统检测，更新 CMOS 中的扩展系统配置数据 ESCD。这期间也会建立中断向量表和中断服务程序，因为要使用键盘鼠标都需要中断进行。&lt;/p&gt;
&lt;p&gt;下一步 BIOS 就得要找操作系统了，操作系统一般安装在硬盘上，但是 BIOS 得先找到启动盘，启动盘一般安装在第一个扇区，占 512 字节，会包含启动的相关代码。在 Linux 中，可以通过&lt;code&gt;Grub2&lt;/code&gt;配置这些代码。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;grub2-mkconfig -o /boot/grub2/grub.cfg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;grub2&lt;/code&gt;第一个要安装的就是&lt;code&gt;boot.img&lt;/code&gt;。它由 &lt;code&gt;boot.S&lt;/code&gt;编译而成，一共 &lt;code&gt;512&lt;/code&gt; 字节，正式安装到启动盘的第一个扇区。这个扇区通常称为&lt;code&gt;MBR&lt;/code&gt;（Master Boot Record，主引导记录 / 扇区）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BIOS&lt;/code&gt; 完成任务后，会将 &lt;code&gt;boot.img&lt;/code&gt; 从硬盘加载到内存中的 &lt;code&gt;0x7c00&lt;/code&gt;来运行。&lt;/p&gt;
&lt;p&gt;由于 &lt;code&gt;512&lt;/code&gt; 个字节实在有限，&lt;code&gt;boot.img&lt;/code&gt; 做不了太多的事情。它能做的最重要的一个事情就是加载&lt;code&gt;grub2&lt;/code&gt; 的另一个镜像 &lt;code&gt;core.img&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;core.img&lt;/code&gt; 由&lt;code&gt;lzma_decompress.img&lt;/code&gt;、&lt;code&gt;diskboot.img&lt;/code&gt;、&lt;code&gt;kernel.img&lt;/code&gt; 和一系列的模块组成，功能比较丰富，能做很多事情。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;boot.img&lt;/code&gt; 先加载的是 &lt;code&gt;core.img&lt;/code&gt; 的第一个扇区。如果从硬盘启动的话，这个扇区里面是&lt;code&gt;diskboot.img&lt;/code&gt;，对应的代码是 &lt;code&gt;diskboot.S&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;boot.img&lt;/code&gt; 将控制权交给 &lt;code&gt;diskboot.img&lt;/code&gt; 后，&lt;code&gt;diskboot.img&lt;/code&gt; 的任务就是将&lt;code&gt;core.img&lt;/code&gt; 的其他部分加载进来，先是解压缩程序 &lt;code&gt;lzma_decompress.img&lt;/code&gt;，再往下是 &lt;code&gt;kernel.img&lt;/code&gt;，最后是各个模块&lt;code&gt;module&lt;/code&gt;对应的映像。这里需要注意，它不是 Linux 的内核，而是&lt;code&gt;grub&lt;/code&gt; 的内核。&lt;/p&gt;
&lt;p&gt;在这之前，我们所有遇到过的程序都非常非常小，完全可以在实模式下运行，但是随着我们加载的东西越来越大，实模式这&lt;code&gt;1M&lt;/code&gt; 的地址空间实在放不下了，所以在真正的解压缩之前，&lt;code&gt;lzma_decompress.img&lt;/code&gt; 做了一个重要的决定，就是调用 &lt;code&gt;real_to_prot&lt;/code&gt;，切换到&lt;strong&gt;保护模式&lt;/strong&gt;，这样就能在更大的寻址空间里面，加载更多的东西。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BIOS&lt;/code&gt;将加载程序从硬盘的引导扇区加载到指定位置，再跳转到指定位置，将控制权转交给加载程序。加载程序将操作系统代码读取到内存，并将控制权转到操作系统。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Q：BIOS-操作系统，中间经过加载程序。为何不直接读取？
A：磁盘文件系统多种多样，硬盘出厂时不能限制只能用一种文件系统，而 BIOS 也不能加上识别所有文件系统的代码。所有为了灵活性只读取磁盘的一块，由加载程序来识别磁盘的文件系统。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;切换到保护模式后，将会做以下这些事，大多数都与内存访问方式有关。&lt;/p&gt;
&lt;p&gt;首先&lt;strong&gt;启动分段&lt;/strong&gt;，就是在内存里面&lt;strong&gt;建立段描述符表&lt;/strong&gt;，将寄存器里面的段寄存器变成段选择子，指向某个段描述符，这样就能实现不同进程的切换了。&lt;/p&gt;
&lt;p&gt;接着是&lt;strong&gt;启动分页&lt;/strong&gt;。能够管理的内存变大了，就需要将内存分成相等大小的块。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;打开 Gate20&lt;/strong&gt;，也就是第 21 根地址线的控制线。因为在实模式 8086 下，一共就 20 根地址线，最大访问&lt;code&gt;1M&lt;/code&gt;的地址空间。切换保护模式的函数&lt;code&gt;DATA32 call real_to_prot&lt;/code&gt;会打开&lt;code&gt;Gate A20&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;现在好了，有的是空间了。接下来我们要对压缩过的 &lt;strong&gt;kernel.img 进行解压缩&lt;/strong&gt;，然后跳转到 &lt;code&gt;kernel.img&lt;/code&gt; 开始运行。&lt;/p&gt;
&lt;h3 id=&#34;内核初始化&#34;&gt;内核初始化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;start_kernel()
&lt;ul&gt;
&lt;li&gt;INIT_TASK(init_task)&lt;/li&gt;
&lt;li&gt;trap_init()&lt;/li&gt;
&lt;li&gt;mm_init()&lt;/li&gt;
&lt;li&gt;sched_init()&lt;/li&gt;
&lt;li&gt;rest_init()
&lt;ul&gt;
&lt;li&gt;kernel_thread(kernel_init, NULL,CLONE_FS)&lt;/li&gt;
&lt;li&gt;kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内核的启动从入口函数&lt;code&gt;start_kernel()&lt;/code&gt; 开始。在 &lt;code&gt;init/main.c&lt;/code&gt; 文件中，&lt;code&gt;start_kernel&lt;/code&gt; 相当于内核的 &lt;code&gt;main&lt;/code&gt; 函数。打开这个函数，我们会发现，里面是各种各样初始化函数 &lt;code&gt;XXXX_init&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;在操作系统里面，先要有个&lt;strong&gt;创始进程&lt;/strong&gt;，有一行指令 &lt;code&gt;set_task_stack_end_magic(&amp;amp;init_task)&lt;/code&gt;。这里面有一个参数 &lt;code&gt;init_task&lt;/code&gt;，它的定义是 &lt;code&gt;struct task_struct init_task = INIT_TASK(init_task)&lt;/code&gt;。它是系统创建的第一个进程，我们称为&lt;code&gt;0&lt;/code&gt;号进程。这是唯一一个没有通过&lt;code&gt;fork&lt;/code&gt; 或者&lt;code&gt;kernel_thread&lt;/code&gt; 产生的进程，是进程列表的第一个。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;trap_init()&lt;/code&gt;里设置了很多**中断门 (Interrupt Gate)**处理各种中断。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mm_init()&lt;/code&gt;初始化内存管理模块，&lt;code&gt;sched_init()&lt;/code&gt;初始化调度模块。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vfs_caches_init()&lt;/code&gt; 会用来初始化基于内存的文件系统 &lt;code&gt;rootfs&lt;/code&gt;。在这个函数里面，会调用 &lt;code&gt;mnt_init()-&amp;gt;init_rootfs()&lt;/code&gt;。这里面有一行代码，&lt;code&gt;register_filesystem(&amp;amp;rootfs_fs_type)&lt;/code&gt;。在 VFS 虚拟文件系统里面注册了一种类型，我们定义为 &lt;code&gt;struct file_system_type rootfs_fs_type&lt;/code&gt;。为了兼容各种各样的文件系统，我们需要将文件的相关数据结构和操作抽象出来，形成一个抽象层对上提供统一的接口，这个抽象层就是 &lt;code&gt;VFS（Virtual File System）&lt;/code&gt;，虚拟文件系统。&lt;/p&gt;
&lt;p&gt;最后&lt;code&gt;start_kernel()&lt;/code&gt;调用&lt;code&gt;rest_init()&lt;/code&gt;来做其他方面的初始化，如初始化 1 号进程，内核态与用户态转化等。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rest_init&lt;/code&gt; 的第一大工作是，用 &lt;code&gt;kernel_thread(kernel_init, NULL, CLONE_FS)&lt;/code&gt;创建第二个进程，这个是&lt;strong&gt;1 号进程&lt;/strong&gt;。这对操作系统意义非凡，因为他将运行第一个用户进程，一旦有了用户进程，运行模式也将发生改变，之前所有资源都是给一个进程用，现在有了用户进程，就会出现抢夺资源的现象。资源也分核心和非核心资源，具有不同权限的进程可以获取不同的资源。&lt;code&gt;x86&lt;/code&gt;提供了分层的权限机制，分成四个&lt;code&gt;Ring&lt;/code&gt;，越往里权限越高。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210727192141.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210727192141.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;操作系统很好地利用了这个机制，将能够访问关键资源的代码放在 &lt;code&gt;Ring0&lt;/code&gt;，我们称为&lt;strong&gt;内核态&lt;/strong&gt;（Kernel Mode）；将普通的程序代码放在 &lt;code&gt;Ring3&lt;/code&gt;，我们称为&lt;strong&gt;用户态&lt;/strong&gt;（User Mode）。&lt;/p&gt;
&lt;p&gt;继续探究&lt;code&gt;kernel_thread()&lt;/code&gt;这个函数，它的一个参数有一个函数&lt;code&gt;kernel_init&lt;/code&gt;，在这个函数里会调用&lt;code&gt;kernel_init_freeable()&lt;/code&gt;，里面有这样一段代码&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ramdisk_execute_command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;ramdisk_execute_command&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;/init&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;先不管&lt;code&gt;ramdisk&lt;/code&gt; 是啥，我们回到 &lt;code&gt;kernel_init&lt;/code&gt; 里面。这里面有这样的代码块：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ramdisk_execute_command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;run_init_process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ramdisk_execute_command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;....&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;....&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;try_to_run_init_process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/sbin/init&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;try_to_run_init_process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/etc/init&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;try_to_run_init_process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/bin/init&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;try_to_run_init_process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/bin/sh&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们可以发现，1 号进程运行的是一个文件，如果我们打开&lt;code&gt;run_init_process&lt;/code&gt;函数，会发现它调用的是&lt;code&gt;do_execve&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;前面讲系统调用的时候，&lt;code&gt;execve&lt;/code&gt; 是一个系统调用，它的作用是运行一个执行文件。加一个 &lt;code&gt;do_&lt;/code&gt; 的往往是内核系统调用的实现。没错，这就是一个系统调用，它会尝试运行 &lt;code&gt;ramdisk&lt;/code&gt; 的“&lt;code&gt;/init”&lt;/code&gt;，或者普通文件系统上的&lt;code&gt;“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”&lt;/code&gt;。不同版本的 Linux 会选择不同的文件启动，但是只要有一个起来了就可以。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;run_init_process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;init_filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;argv_init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;init_filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;do_execve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;getname_kernel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;init_filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;argv_init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;envp_init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如何利用执行 &lt;code&gt;init&lt;/code&gt; 文件的机会，从内核态回到用户态呢？&lt;/p&gt;
&lt;p&gt;我们从系统调用的过程可以得到启发，“用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态”，然后接着运行。而咱们刚才运行&lt;code&gt;init&lt;/code&gt;，是调用 &lt;code&gt;do_execve&lt;/code&gt;，正是上面的过程的后半部分，从内核态执行系统调用开始。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;do_execve-&amp;gt;do_execveat_common-&amp;gt;exec_binprm-&amp;gt;search_binary_handler&lt;/code&gt;，这里面会调用这段内容：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;search_binary_handler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;linux_binprm&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bprm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;......&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;linux_binfmt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;......&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;retval&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;load_binary&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;bprm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;......&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是说，我要运行一个程序，需要加载这个二进制文件，这就是我们常说的项目执行计划书。它是有一定格式的。Linux 下一个常用的格式是 ELF（Executable and Linkable Format，可执行与可链接格式）。于是我们就有了下面这个定义：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;linux_binfmt&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;elf_format&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;module&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;THIS_MODULE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;load_binary&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;load_elf_binary&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;load_shlib&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;load_elf_library&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;core_dump&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;elf_core_dump&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;min_coredump&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ELF_EXEC_PAGESIZE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这其实就是先调用 &lt;code&gt;load_elf_binary&lt;/code&gt;，最后调用 &lt;code&gt;start_thread&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;start_thread&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pt_regs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;new_ip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;long&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;new_sp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;set_user_gs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fs&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ds&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__USER_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;es&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__USER_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ss&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__USER_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cs&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;__USER_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ip&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;new_ip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sp&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;new_sp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;regs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flags&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;X86_EFLAGS_IF&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;force_iret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;EXPORT_SYMBOL_GPL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;start_thread&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;struct pt_regs&lt;/code&gt;，看名字里的 &lt;code&gt;register&lt;/code&gt;，就是寄存器啊！这个结构就是在系统调用的时候，内核中保存用户态运行上下文的，里面将用户态的代码段 &lt;code&gt;CS&lt;/code&gt;设置为 &lt;code&gt;__USER_CS&lt;/code&gt;，将用户态的数据段 &lt;code&gt;DS&lt;/code&gt; 设置为 &lt;code&gt;__USER_DS&lt;/code&gt;，以及&lt;code&gt;指令指针寄存器 IP&lt;/code&gt;、&lt;code&gt;栈指针寄存器 SP&lt;/code&gt;。这里相当于补上了原来系统调用里，保存寄存器的一个步骤。&lt;/p&gt;
&lt;p&gt;最后的 &lt;code&gt;iret&lt;/code&gt; 是干什么的呢？它是用于从系统调用中返回。这个时候会恢复寄存器。从哪里恢复呢？按说是从进入系统调用的时候，保存的寄存器里面拿出。好在上面的函数补上了寄存器。&lt;code&gt;CS&lt;/code&gt; 和指令指针寄存器 &lt;code&gt;IP&lt;/code&gt; 恢复了，指向用户态下一个要执行的语句。&lt;code&gt;DS&lt;/code&gt; 和函数栈指针 &lt;code&gt;SP&lt;/code&gt; 也被恢复了，指向用户态函数栈的栈顶。所以，下一条指令，就从用户态开始运行了。&lt;/p&gt;
&lt;p&gt;init 终于从内核到用户态了。一开始到用户态的是 ramdisk 的 init，后来会启动真正根文件系统上的 init，成为所有用户态进程的祖先。&lt;/p&gt;
&lt;p&gt;为什么会有 ramdisk 这个东西呢？还记得上一节咱们内核启动的时候，配置过这个参数：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;initrd16 /boot/initramfs-3.10.0-862.el7.x86_64.img
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;就是这个东西，这是一个基于内存的文件系统。为啥会有这个呢？&lt;/p&gt;
&lt;p&gt;是因为刚才那个 init 程序是在文件系统上的，文件系统一定是在一个存储设备上的，例如硬盘。Linux 访问存储设备，要有驱动才能访问。如果存储系统数目很有限，那驱动可以直接放到内核里面，反正前面我们加载过内核到内存里了，现在可以直接对存储系统进行访问。&lt;/p&gt;
&lt;p&gt;但是存储系统越来越多了，如果所有市面上的存储系统的驱动都默认放进内核，内核就太大了。这该怎么办呢？&lt;/p&gt;
&lt;p&gt;我们只好先弄一个基于内存的文件系统。内存访问是不需要驱动的，这个就是 &lt;code&gt;ramdisk&lt;/code&gt;。这个时候，&lt;code&gt;ramdisk&lt;/code&gt; 是根文件系统。&lt;/p&gt;
&lt;p&gt;然后，我们开始运行 &lt;code&gt;ramdisk&lt;/code&gt; 上的 &lt;code&gt;/init&lt;/code&gt;。等它运行完了就已经在用户态了。&lt;code&gt;/init&lt;/code&gt; 这个程序会先根据存储系统的类型加载驱动，有了驱动就可以设置真正的根文件系统了。有了真正的根文件系统，&lt;code&gt;ramdisk&lt;/code&gt;上的 &lt;code&gt;/init&lt;/code&gt; 会启动文件系统上的 &lt;code&gt;init&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接下来就是各种系统的初始化。启动系统的服务，启动控制台，用户就可以登录进来了。&lt;/p&gt;
&lt;p&gt;至此，用户态进程有了一个祖宗，那内核态的进程呢？这就是&lt;code&gt;rest_init&lt;/code&gt;接下来要做的是，&lt;strong&gt;创建 2 号线程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)&lt;/code&gt;又一次使用 &lt;code&gt;kernel_thread&lt;/code&gt; 函数创建进程。这里的函数 &lt;code&gt;kthreadd&lt;/code&gt;，负责所有内核态的线程的调度和管理，是内核态所有线程运行的祖先。&lt;/p&gt;
&lt;h3 id=&#34;系统调用&#34;&gt;系统调用&lt;/h3&gt;
&lt;p&gt;Linux 提供了&lt;code&gt;glibc&lt;/code&gt;这个库封装了系统调用，方便用户使用。那么在打开一个文件时，&lt;code&gt;glibc&lt;/code&gt;是如何调用内核的&lt;code&gt;open&lt;/code&gt;的呢？&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;glibc&lt;/code&gt; 的源代码中，有个文件&lt;code&gt;syscalls.list&lt;/code&gt;，里面列着所有 &lt;code&gt;glibc&lt;/code&gt; 的函数对应的系统调用，就像下面这个样子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# File name Caller  Syscall name    Args    Strong name Weak names
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;open    -  open    Ci:siv  __libc_open __open open
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;另外，&lt;code&gt;glibc&lt;/code&gt; 还有一个脚本 &lt;code&gt;make-syscall.sh&lt;/code&gt;，可以根据上面的配置文件，对于每一个封装好的系统调用，生成一个文件。这个文件里面定义了一些宏，例如 &lt;code&gt;#define SYSCALL_NAME open&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;glibc&lt;/code&gt; 还有一个文件 &lt;code&gt;syscall-template.S&lt;/code&gt;，使用上面这个宏，定义了这个系统调用的调用方式。&lt;/p&gt;
&lt;p&gt;对于任何一个系统调用，会调用&lt;code&gt;DO_CALL&lt;/code&gt;。这也是一个宏，这个宏 32 位和 64 位的定义是不一样的。&lt;/p&gt;
&lt;h4 id=&#34;32-位系统调用过程&#34;&gt;32 位系统调用过程&lt;/h4&gt;
&lt;p&gt;i386 目录下的&lt;code&gt;sysdep.h&lt;/code&gt; 文件&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/* Linux takes system call arguments in registers:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  syscall number  %eax       call-clobbered
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  arg 1    %ebx       call-saved
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  arg 2    %ecx       call-clobbered
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  arg 3    %edx       call-clobbered
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  arg 4    %esi       call-saved
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  arg 5    %edi       call-saved
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  arg 6    %ebp       call-saved
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;......
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;*/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;#define DO_CALL(syscall_name, args)                           
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    PUSHARGS_##args                             
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    DOARGS_##args                                
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    movl $SYS_ify (syscall_name), %eax;                          
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ENTER_KERNEL                                 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    POPARGS_##args
&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;code&gt;ENTER_KERNEL&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# define ENTER_KERNEL int $0x80
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ENTER_KERNEL&lt;/code&gt;就是一个软中断，通过它可以陷入 (trap) 内核。&lt;/p&gt;
&lt;p&gt;在内核启动的时候，还记得有一个 &lt;code&gt;trap_init()&lt;/code&gt;，这是一个软中断的陷入门。当接到一个系统调用时，&lt;code&gt;trap_init()&lt;/code&gt;就会调用&lt;code&gt;entry_INT80_32&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;push&lt;/code&gt; 和 &lt;code&gt;SAVE_ALL&lt;/code&gt; 将当前用户态的寄存器，保存在 &lt;code&gt;pt_regs&lt;/code&gt; 结构里面，然后调用 &lt;code&gt;do_syscall_32_irqs_on&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809163844.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809163844.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;h4 id=&#34;64-位系统调用过程&#34;&gt;64 位系统调用过程&lt;/h4&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809170711.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809170711.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809171554.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809171554.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="系统初始化">系统初始化</h2>
<h3 id="x86-架构概述">x86 架构概述</h3>
<p><strong>CPU（Central Processing Unit）</strong>：中央处理器，计算机所有设备都围绕它展开工作。</p>
<ul>
<li>运算单元：只管算，例如做加法、做位移等等。但是，它不知道应该算哪些数据，运算结果应该放在哪里。</li>
<li>数据单元：运算单元计算的数据如果每次都要经过总线，到内存里面现拿，这样就太慢了，所以就有了数据单元。数据单元包括 CPU 内部的缓存和寄存器组，空间很小，但是速度飞快，可以暂时存放数据和运算结果。</li>
<li>控制单元：有了放数据的地方，也有了算的地方，还需要有个指挥到底做什么运算的地方，这就是控制单元。控制单元是一个统一的指挥中心，它可以获得下一条指令，然后执行这条指令。这个指令会指导运算单元取出数据单元中的某几个数据，计算出个结果，然后放在数据单元的某个地方。</li>
</ul>
<p><strong>内存（Memory）</strong>：CPU 本身不能保存大量数据，许多复杂的计算需要将中间结果保存下来就必须用到内存。</p>
<p><strong>总线（Bus）</strong>：CPU 和其他设备连接，就靠总线，其实就是主板上密密麻麻的集成电路，这些东西组成了 CPU 和其他设备的高速通道。</p>
<ul>
<li>地址总线：传输地址数据（我想拿内存中哪个位置的数据）</li>
<li>数据总线：传输真正的数据</li>
</ul>
<p>总线就像 CPU 和内存之间的高速公路，总线多少位就类似高速公路多少个车道，但两种总线的位数意义不同。</p>
<p>地址总线的位数决定了访问地址范围有多广，数据总线位数决定了一次能拿多少数据进来。那么 CPU 中总线的位数有没有标准呢？如果没有标准，那操作系统作为软件就很难办了，因为软件层没办法实现通用的运算逻辑。早期每家公司的 CPU 架构都不同，后来历史将 x86 平台推到了<strong>开放，统一，兼容</strong>的位置。</p>
<h4 id="8086-架构图">8086 架构图</h4>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721092854.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721092854.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p><strong>数据单元：</strong> 8086 处理器内部共有 8 个 16 位的通用寄存器，分别是 数据寄存器（AX、BX、CX、DX）、指针寄存器（SP、BP）、变址寄存器（SI、DI）。其中 AX、BX、CX、DX 可以分成两个 8 位的寄存器来使用，分别是 AH、AL、BH、BL、CH、CL、DH、DL，其中 H 就是 High（高位），L 就是 Low（低位）的意思。</p>
<p><strong>控制单元：</strong> IP 寄存器（Instruction Pointer Register）就是指令指针寄存器，它指向代码段中下一条指令的位置。CPU 会根据它来不断地将指令从内存的代码段中，加载到 CPU 的指令队列中，然后交给运算单元去执行。</p>
<p>如果需要切换进程呢？每个进程都分代码段和数据段，为了指向不同进程的地址空间，有四个 16 位的段寄存器，分别是 CS、DS、SS、ES。</p>
<p>其中，<strong>CS</strong> 就是代码段寄存器（Code Segment Register），通过它可以找到代码在内存中的位置；<strong>DS</strong> 是数据段的寄存器（Data Segment Register），通过它可以找到数据在内存中的位置。<strong>SS</strong> 是栈寄存器（Stack Register）。栈是程序运行中一个特殊的数据结构，数据的存取只能从一端进行，秉承后进先出的原则。<strong>ES</strong>是扩展段寄存器（Extra Segment Register）顾名思义。</p>
<p>如果 CPU 运算中需要加载内存中的数据，需要通过 DS 找到内存中的数据，加载到通用寄存器中，应该如何加载呢？对于一个段，有一个起始的地址，而段内的具体位置，我们称为<strong>偏移量（Offset）</strong>。在 CS 和 DS 中都存放着一个段的起始地址。<strong>代码段的偏移量在 IP 寄存器中</strong>，<strong>数据段的偏移量会放在通用寄存器中</strong>。因为段寄存器都是 16 位的，而地址总线是 20 位的，所以通过 *<em>起始地址 <em>16+ 偏移量</em></em> 的方式，将寻址位数都变成 20 位，也就是将 CS 和 DS 的值左移 4 位。</p>
<p>对于只有 20 位地址总线的 8086 来说，寻址空间最大也就是$2^{20}=1\text{M}$，超过这个位置就访问不到了，一个段因为偏移量只有 16 位，所以一个段最大是$2^{16}=64\text{k}$。</p>
<h4 id="32-位处理器">32 位处理器</h4>
<p>随着计算机发展，内存越来越大，总线也越来越宽。在 32 位处理器中，有 32 根地址总线，可以访问 $2^{32}=4\text{G}$ 的内存。使用原来的模式肯定不行了，但是又不能完全抛弃原来的模式，因为这个架构是<strong>开放的</strong>。那么在开发架构的基础上如何保持兼容呢？</p>
<p>首先，通用寄存器有扩展，可以将 8 个 16 位的扩展到 8 个 32 位的，但是依然可以保留 16 位的和 8 位的使用方式。其中，指向下一条指令的指令指针寄存器 IP，就会扩展成 32 位的，同样也兼容 16 位的。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721103205.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721103205.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>段寄存器改动较大，新的段寄存器都改成了 32 位的，每个寄存器又分为<strong>段描述符缓存器（Segment Descriptor）</strong>，和<strong>段选择子寄存器（Selector）</strong> ,现在的段寄存器不在是段的起始地址，段的起始地址保存在表格一样的段描述符缓冲器中，段选择子寄存器保存地址在段描述符缓存器中的哪一项。这样，将一个从段寄存器直接拿到的段起始地址，就变成了<strong>先间接地从段寄存器找到表格中的一项，再从表格中的一项中拿到段起始地址。</strong></p>
<p>虽然现在的这种模式和之前的模式不兼容，但是后面这种模式灵活的非常高，可以保持一直兼容下去。在 32 位的系统架构下，将前一种模式称为<strong>实模式（Real Pattern）</strong>，后一种模式称为<strong>保护模式（Protected Pattern）</strong> 。当系统刚刚启动的时候，CPU 是处于实模式的，这个时候和原来的模式是兼容的。也就是说，哪怕你买了 32 位的 CPU，也支持在原来的模式下运行。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721104550.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210721104550.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<blockquote>
<p>汇编命令学习
mov,
call, jmp, int, ret, add, or, xor, shl, shr, push, pop, inc, dec, sub, cmp。</p>
</blockquote>
<h3 id="bios-与-bootloader">BIOS 与 BootLoader</h3>
<p>BIOS：基本输入输出系统</p>
<p>ROM：只读存储器</p>
<p>RAM：随机存取存储器</p>
<p>在我们按下电脑电源键的那一刻，主板就加电了，CPU 就要开始执行指令了，但是刚开始操作系统都没，CPU 执行什么指令呢？这就有了<code>BIOS</code>，它相当于一个指导手册，告诉 CPU 接下来要干啥。</p>
<p>刚开机时，系统初始化代码从 ROM 读取，将 <code>CS</code> 设置为 <code>0xFFFF</code>，将 <code>IP</code> 设置为 <code>0x0000</code>，所以第一条指令就会指向 <code>0xFFFF0</code>，初始化完成后确定访问指令位置。</p>
<p>接下来 BIOS 会检查各个硬件是否正常，检测内容显卡等关键部件的存在于工作状态，设备初始化，执行系统 BIOS 进行系统检测，更新 CMOS 中的扩展系统配置数据 ESCD。这期间也会建立中断向量表和中断服务程序，因为要使用键盘鼠标都需要中断进行。</p>
<p>下一步 BIOS 就得要找操作系统了，操作系统一般安装在硬盘上，但是 BIOS 得先找到启动盘，启动盘一般安装在第一个扇区，占 512 字节，会包含启动的相关代码。在 Linux 中，可以通过<code>Grub2</code>配置这些代码。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">grub2-mkconfig -o /boot/grub2/grub.cfg
</span></span></code></pre></div><p><code>grub2</code>第一个要安装的就是<code>boot.img</code>。它由 <code>boot.S</code>编译而成，一共 <code>512</code> 字节，正式安装到启动盘的第一个扇区。这个扇区通常称为<code>MBR</code>（Master Boot Record，主引导记录 / 扇区）。</p>
<p><code>BIOS</code> 完成任务后，会将 <code>boot.img</code> 从硬盘加载到内存中的 <code>0x7c00</code>来运行。</p>
<p>由于 <code>512</code> 个字节实在有限，<code>boot.img</code> 做不了太多的事情。它能做的最重要的一个事情就是加载<code>grub2</code> 的另一个镜像 <code>core.img</code>。</p>
<p><code>core.img</code> 由<code>lzma_decompress.img</code>、<code>diskboot.img</code>、<code>kernel.img</code> 和一系列的模块组成，功能比较丰富，能做很多事情。</p>
<p><code>boot.img</code> 先加载的是 <code>core.img</code> 的第一个扇区。如果从硬盘启动的话，这个扇区里面是<code>diskboot.img</code>，对应的代码是 <code>diskboot.S</code>。</p>
<p><code>boot.img</code> 将控制权交给 <code>diskboot.img</code> 后，<code>diskboot.img</code> 的任务就是将<code>core.img</code> 的其他部分加载进来，先是解压缩程序 <code>lzma_decompress.img</code>，再往下是 <code>kernel.img</code>，最后是各个模块<code>module</code>对应的映像。这里需要注意，它不是 Linux 的内核，而是<code>grub</code> 的内核。</p>
<p>在这之前，我们所有遇到过的程序都非常非常小，完全可以在实模式下运行，但是随着我们加载的东西越来越大，实模式这<code>1M</code> 的地址空间实在放不下了，所以在真正的解压缩之前，<code>lzma_decompress.img</code> 做了一个重要的决定，就是调用 <code>real_to_prot</code>，切换到<strong>保护模式</strong>，这样就能在更大的寻址空间里面，加载更多的东西。</p>
<p><code>BIOS</code>将加载程序从硬盘的引导扇区加载到指定位置，再跳转到指定位置，将控制权转交给加载程序。加载程序将操作系统代码读取到内存，并将控制权转到操作系统。</p>
<blockquote>
<p>Q：BIOS-操作系统，中间经过加载程序。为何不直接读取？
A：磁盘文件系统多种多样，硬盘出厂时不能限制只能用一种文件系统，而 BIOS 也不能加上识别所有文件系统的代码。所有为了灵活性只读取磁盘的一块，由加载程序来识别磁盘的文件系统。</p>
</blockquote>
<p>切换到保护模式后，将会做以下这些事，大多数都与内存访问方式有关。</p>
<p>首先<strong>启动分段</strong>，就是在内存里面<strong>建立段描述符表</strong>，将寄存器里面的段寄存器变成段选择子，指向某个段描述符，这样就能实现不同进程的切换了。</p>
<p>接着是<strong>启动分页</strong>。能够管理的内存变大了，就需要将内存分成相等大小的块。</p>
<p><strong>打开 Gate20</strong>，也就是第 21 根地址线的控制线。因为在实模式 8086 下，一共就 20 根地址线，最大访问<code>1M</code>的地址空间。切换保护模式的函数<code>DATA32 call real_to_prot</code>会打开<code>Gate A20</code>。</p>
<p>现在好了，有的是空间了。接下来我们要对压缩过的 <strong>kernel.img 进行解压缩</strong>，然后跳转到 <code>kernel.img</code> 开始运行。</p>
<h3 id="内核初始化">内核初始化</h3>
<ul>
<li>start_kernel()
<ul>
<li>INIT_TASK(init_task)</li>
<li>trap_init()</li>
<li>mm_init()</li>
<li>sched_init()</li>
<li>rest_init()
<ul>
<li>kernel_thread(kernel_init, NULL,CLONE_FS)</li>
<li>kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>内核的启动从入口函数<code>start_kernel()</code> 开始。在 <code>init/main.c</code> 文件中，<code>start_kernel</code> 相当于内核的 <code>main</code> 函数。打开这个函数，我们会发现，里面是各种各样初始化函数 <code>XXXX_init</code>。</p>
<p>在操作系统里面，先要有个<strong>创始进程</strong>，有一行指令 <code>set_task_stack_end_magic(&amp;init_task)</code>。这里面有一个参数 <code>init_task</code>，它的定义是 <code>struct task_struct init_task = INIT_TASK(init_task)</code>。它是系统创建的第一个进程，我们称为<code>0</code>号进程。这是唯一一个没有通过<code>fork</code> 或者<code>kernel_thread</code> 产生的进程，是进程列表的第一个。</p>
<p><code>trap_init()</code>里设置了很多**中断门 (Interrupt Gate)**处理各种中断。</p>
<p><code>mm_init()</code>初始化内存管理模块，<code>sched_init()</code>初始化调度模块。</p>
<p><code>vfs_caches_init()</code> 会用来初始化基于内存的文件系统 <code>rootfs</code>。在这个函数里面，会调用 <code>mnt_init()-&gt;init_rootfs()</code>。这里面有一行代码，<code>register_filesystem(&amp;rootfs_fs_type)</code>。在 VFS 虚拟文件系统里面注册了一种类型，我们定义为 <code>struct file_system_type rootfs_fs_type</code>。为了兼容各种各样的文件系统，我们需要将文件的相关数据结构和操作抽象出来，形成一个抽象层对上提供统一的接口，这个抽象层就是 <code>VFS（Virtual File System）</code>，虚拟文件系统。</p>
<p>最后<code>start_kernel()</code>调用<code>rest_init()</code>来做其他方面的初始化，如初始化 1 号进程，内核态与用户态转化等。</p>
<p><code>rest_init</code> 的第一大工作是，用 <code>kernel_thread(kernel_init, NULL, CLONE_FS)</code>创建第二个进程，这个是<strong>1 号进程</strong>。这对操作系统意义非凡，因为他将运行第一个用户进程，一旦有了用户进程，运行模式也将发生改变，之前所有资源都是给一个进程用，现在有了用户进程，就会出现抢夺资源的现象。资源也分核心和非核心资源，具有不同权限的进程可以获取不同的资源。<code>x86</code>提供了分层的权限机制，分成四个<code>Ring</code>，越往里权限越高。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210727192141.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210727192141.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>操作系统很好地利用了这个机制，将能够访问关键资源的代码放在 <code>Ring0</code>，我们称为<strong>内核态</strong>（Kernel Mode）；将普通的程序代码放在 <code>Ring3</code>，我们称为<strong>用户态</strong>（User Mode）。</p>
<p>继续探究<code>kernel_thread()</code>这个函数，它的一个参数有一个函数<code>kernel_init</code>，在这个函数里会调用<code>kernel_init_freeable()</code>，里面有这样一段代码</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ramdisk_execute_command</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">ramdisk_execute_command</span> <span class="o">=</span> <span class="s">&#34;/init&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>先不管<code>ramdisk</code> 是啥，我们回到 <code>kernel_init</code> 里面。这里面有这样的代码块：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">ramdisk_execute_command</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">ret</span> <span class="o">=</span> <span class="nf">run_init_process</span><span class="p">(</span><span class="n">ramdisk_execute_command</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">....</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">....</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">try_to_run_init_process</span><span class="p">(</span><span class="s">&#34;/sbin/init&#34;</span><span class="p">)</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">    <span class="o">!</span><span class="nf">try_to_run_init_process</span><span class="p">(</span><span class="s">&#34;/etc/init&#34;</span><span class="p">)</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">    <span class="o">!</span><span class="nf">try_to_run_init_process</span><span class="p">(</span><span class="s">&#34;/bin/init&#34;</span><span class="p">)</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">    <span class="o">!</span><span class="nf">try_to_run_init_process</span><span class="p">(</span><span class="s">&#34;/bin/sh&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span></code></pre></div><p>我们可以发现，1 号进程运行的是一个文件，如果我们打开<code>run_init_process</code>函数，会发现它调用的是<code>do_execve</code>。</p>
<p>前面讲系统调用的时候，<code>execve</code> 是一个系统调用，它的作用是运行一个执行文件。加一个 <code>do_</code> 的往往是内核系统调用的实现。没错，这就是一个系统调用，它会尝试运行 <code>ramdisk</code> 的“<code>/init”</code>，或者普通文件系统上的<code>“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”</code>。不同版本的 Linux 会选择不同的文件启动，但是只要有一个起来了就可以。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">run_init_process</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">init_filename</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">argv_init</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">init_filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nf">do_execve</span><span class="p">(</span><span class="nf">getname_kernel</span><span class="p">(</span><span class="n">init_filename</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="k">const</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">argv_init</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="n">__user</span> <span class="o">*</span><span class="k">const</span> <span class="n">__user</span> <span class="o">*</span><span class="p">)</span><span class="n">envp_init</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>如何利用执行 <code>init</code> 文件的机会，从内核态回到用户态呢？</p>
<p>我们从系统调用的过程可以得到启发，“用户态 - 系统调用 - 保存寄存器 - 内核态执行系统调用 - 恢复寄存器 - 返回用户态”，然后接着运行。而咱们刚才运行<code>init</code>，是调用 <code>do_execve</code>，正是上面的过程的后半部分，从内核态执行系统调用开始。</p>
<p><code>do_execve-&gt;do_execveat_common-&gt;exec_binprm-&gt;search_binary_handler</code>，这里面会调用这段内容：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">search_binary_handler</span><span class="p">(</span><span class="k">struct</span> <span class="n">linux_binprm</span> <span class="o">*</span><span class="n">bprm</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">......</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">linux_binfmt</span> <span class="o">*</span><span class="n">fmt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">......</span>
</span></span><span class="line"><span class="cl">  <span class="n">retval</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">-&gt;</span><span class="nf">load_binary</span><span class="p">(</span><span class="n">bprm</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">......</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>也就是说，我要运行一个程序，需要加载这个二进制文件，这就是我们常说的项目执行计划书。它是有一定格式的。Linux 下一个常用的格式是 ELF（Executable and Linkable Format，可执行与可链接格式）。于是我们就有了下面这个定义：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">linux_binfmt</span> <span class="n">elf_format</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">module</span>  <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">load_binary</span>  <span class="o">=</span> <span class="n">load_elf_binary</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">load_shlib</span>  <span class="o">=</span> <span class="n">load_elf_library</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">core_dump</span>  <span class="o">=</span> <span class="n">elf_core_dump</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">min_coredump</span>  <span class="o">=</span> <span class="n">ELF_EXEC_PAGESIZE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>这其实就是先调用 <code>load_elf_binary</code>，最后调用 <code>start_thread</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">void</span>
</span></span><span class="line"><span class="cl"><span class="nf">start_thread</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">new_ip</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">new_sp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="nf">set_user_gs</span><span class="p">(</span><span class="n">regs</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">fs</span>  <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">ds</span>  <span class="o">=</span> <span class="n">__USER_DS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">es</span>  <span class="o">=</span> <span class="n">__USER_DS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">ss</span>  <span class="o">=</span> <span class="n">__USER_DS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">cs</span>  <span class="o">=</span> <span class="n">__USER_CS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">ip</span>  <span class="o">=</span> <span class="n">new_ip</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">sp</span>  <span class="o">=</span> <span class="n">new_sp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">regs</span><span class="o">-&gt;</span><span class="n">flags</span>  <span class="o">=</span> <span class="n">X86_EFLAGS_IF</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nf">force_iret</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nf">EXPORT_SYMBOL_GPL</span><span class="p">(</span><span class="n">start_thread</span><span class="p">);</span>
</span></span></code></pre></div><p><code>struct pt_regs</code>，看名字里的 <code>register</code>，就是寄存器啊！这个结构就是在系统调用的时候，内核中保存用户态运行上下文的，里面将用户态的代码段 <code>CS</code>设置为 <code>__USER_CS</code>，将用户态的数据段 <code>DS</code> 设置为 <code>__USER_DS</code>，以及<code>指令指针寄存器 IP</code>、<code>栈指针寄存器 SP</code>。这里相当于补上了原来系统调用里，保存寄存器的一个步骤。</p>
<p>最后的 <code>iret</code> 是干什么的呢？它是用于从系统调用中返回。这个时候会恢复寄存器。从哪里恢复呢？按说是从进入系统调用的时候，保存的寄存器里面拿出。好在上面的函数补上了寄存器。<code>CS</code> 和指令指针寄存器 <code>IP</code> 恢复了，指向用户态下一个要执行的语句。<code>DS</code> 和函数栈指针 <code>SP</code> 也被恢复了，指向用户态函数栈的栈顶。所以，下一条指令，就从用户态开始运行了。</p>
<p>init 终于从内核到用户态了。一开始到用户态的是 ramdisk 的 init，后来会启动真正根文件系统上的 init，成为所有用户态进程的祖先。</p>
<p>为什么会有 ramdisk 这个东西呢？还记得上一节咱们内核启动的时候，配置过这个参数：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">initrd16 /boot/initramfs-3.10.0-862.el7.x86_64.img
</span></span></code></pre></div><p>就是这个东西，这是一个基于内存的文件系统。为啥会有这个呢？</p>
<p>是因为刚才那个 init 程序是在文件系统上的，文件系统一定是在一个存储设备上的，例如硬盘。Linux 访问存储设备，要有驱动才能访问。如果存储系统数目很有限，那驱动可以直接放到内核里面，反正前面我们加载过内核到内存里了，现在可以直接对存储系统进行访问。</p>
<p>但是存储系统越来越多了，如果所有市面上的存储系统的驱动都默认放进内核，内核就太大了。这该怎么办呢？</p>
<p>我们只好先弄一个基于内存的文件系统。内存访问是不需要驱动的，这个就是 <code>ramdisk</code>。这个时候，<code>ramdisk</code> 是根文件系统。</p>
<p>然后，我们开始运行 <code>ramdisk</code> 上的 <code>/init</code>。等它运行完了就已经在用户态了。<code>/init</code> 这个程序会先根据存储系统的类型加载驱动，有了驱动就可以设置真正的根文件系统了。有了真正的根文件系统，<code>ramdisk</code>上的 <code>/init</code> 会启动文件系统上的 <code>init</code>。</p>
<p>接下来就是各种系统的初始化。启动系统的服务，启动控制台，用户就可以登录进来了。</p>
<p>至此，用户态进程有了一个祖宗，那内核态的进程呢？这就是<code>rest_init</code>接下来要做的是，<strong>创建 2 号线程</strong>。</p>
<p><code>kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)</code>又一次使用 <code>kernel_thread</code> 函数创建进程。这里的函数 <code>kthreadd</code>，负责所有内核态的线程的调度和管理，是内核态所有线程运行的祖先。</p>
<h3 id="系统调用">系统调用</h3>
<p>Linux 提供了<code>glibc</code>这个库封装了系统调用，方便用户使用。那么在打开一个文件时，<code>glibc</code>是如何调用内核的<code>open</code>的呢？</p>
<p>在 <code>glibc</code> 的源代码中，有个文件<code>syscalls.list</code>，里面列着所有 <code>glibc</code> 的函数对应的系统调用，就像下面这个样子：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># File name Caller  Syscall name    Args    Strong name Weak names
</span></span><span class="line"><span class="cl">open    -  open    Ci:siv  __libc_open __open open
</span></span></code></pre></div><p>另外，<code>glibc</code> 还有一个脚本 <code>make-syscall.sh</code>，可以根据上面的配置文件，对于每一个封装好的系统调用，生成一个文件。这个文件里面定义了一些宏，例如 <code>#define SYSCALL_NAME open</code>。</p>
<p><code>glibc</code> 还有一个文件 <code>syscall-template.S</code>，使用上面这个宏，定义了这个系统调用的调用方式。</p>
<p>对于任何一个系统调用，会调用<code>DO_CALL</code>。这也是一个宏，这个宏 32 位和 64 位的定义是不一样的。</p>
<h4 id="32-位系统调用过程">32 位系统调用过程</h4>
<p>i386 目录下的<code>sysdep.h</code> 文件</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">/* Linux takes system call arguments in registers:
</span></span><span class="line"><span class="cl">  syscall number  %eax       call-clobbered
</span></span><span class="line"><span class="cl">  arg 1    %ebx       call-saved
</span></span><span class="line"><span class="cl">  arg 2    %ecx       call-clobbered
</span></span><span class="line"><span class="cl">  arg 3    %edx       call-clobbered
</span></span><span class="line"><span class="cl">  arg 4    %esi       call-saved
</span></span><span class="line"><span class="cl">  arg 5    %edi       call-saved
</span></span><span class="line"><span class="cl">  arg 6    %ebp       call-saved
</span></span><span class="line"><span class="cl">......
</span></span><span class="line"><span class="cl">*/
</span></span><span class="line"><span class="cl">#define DO_CALL(syscall_name, args)                           
</span></span><span class="line"><span class="cl">    PUSHARGS_##args                             
</span></span><span class="line"><span class="cl">    DOARGS_##args                                
</span></span><span class="line"><span class="cl">    movl $SYS_ify (syscall_name), %eax;                          
</span></span><span class="line"><span class="cl">    ENTER_KERNEL                                 
</span></span><span class="line"><span class="cl">    POPARGS_##args
</span></span></code></pre></div><p>这里，我们将请求参数放在寄存器里面，根据系统调用的名称，得到系统调用号，放在寄存器 <code>eax</code> 里面，然后执行 <code>ENTER_KERNEL</code>。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># define ENTER_KERNEL int $0x80
</span></span></code></pre></div><p><code>ENTER_KERNEL</code>就是一个软中断，通过它可以陷入 (trap) 内核。</p>
<p>在内核启动的时候，还记得有一个 <code>trap_init()</code>，这是一个软中断的陷入门。当接到一个系统调用时，<code>trap_init()</code>就会调用<code>entry_INT80_32</code>。</p>
<p>通过 <code>push</code> 和 <code>SAVE_ALL</code> 将当前用户态的寄存器，保存在 <code>pt_regs</code> 结构里面，然后调用 <code>do_syscall_32_irqs_on</code>。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809163844.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809163844.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<h4 id="64-位系统调用过程">64 位系统调用过程</h4>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809170711.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809170711.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809171554.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20210809171554.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux 操作系统-内存管理</title>
      <link>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/</link>
      <pubDate>Thu, 19 Aug 2021 09:37:04 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/</guid>
      <description>&lt;h2 id=&#34;内存管理概述&#34;&gt;内存管理概述&lt;/h2&gt;
&lt;p&gt;计算机所谓的“计算”指的是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;进程和线程对于 CPU 的使用&lt;/li&gt;
&lt;li&gt;对内存的管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;独享内存空间的原理&#34;&gt;独享内存空间的原理&lt;/h3&gt;
&lt;p&gt;每个进程都独享一段内存空间，并且真实物理内存地址对进程不可见，操作系统会给进程分配一个虚拟地址，每个进程看到的内存地址都是从 0 开始。操作系统会将不同进程的虚拟地址和不同内存的物理地址做映射。当程序访问虚拟地址时，由内核的数据结构进行转换，转换成不同的物理地址，这样不同的进程运行的时候，写入的是不同的物理地址。&lt;/p&gt;
&lt;h3 id=&#34;规划虚拟地址空间&#34;&gt;规划虚拟地址空间&lt;/h3&gt;
&lt;p&gt;通过以上的原理，我们可以看出，操作系统的内存管理，主要分为三个方面。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;物理内存的管理；&lt;/li&gt;
&lt;li&gt;虚拟地址的管理；&lt;/li&gt;
&lt;li&gt;虚拟地址和物理地址如何映射；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;进程获取了一段独立的虚拟内存空间后，可以不用管其他进程，“任意”使用这片内存，但是也有一点规则。这篇内存需要存放内核态和用户态的内容。高地址存放内核态的内容，低地址存放用户态的内容。具体分界线 64 位与 32 位不同，暂不深究。&lt;/p&gt;
&lt;p&gt;我们从最低位开始排起，先是&lt;strong&gt;Text Segment、Data Segment 和 BSS Segment&lt;/strong&gt;。Text Segment 是存放二进制可执行代码的位置，Data Segment 存放静态常量，BSS Segment 存放未初始化的静态变量。是不是觉得这几个名字很熟悉？没错，咱们前面讲 ELF 格式的时候提到过，在二进制执行文件里面，就有这三个部分。这里就是把二进制执行文件的三个部分加载到内存里面。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129170110.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129170110.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;接下来是&lt;strong&gt;堆&lt;/strong&gt;（Heap）&lt;strong&gt;段&lt;/strong&gt;。堆是往高地址增长的，是用来动态分配内存的区域，&lt;code&gt;malloc&lt;/code&gt; 就是在这里面分配的。
接下来的区域是&lt;strong&gt;Memory Mapping Segment&lt;/strong&gt;。这块地址可以用来把文件映射进内存用的，如果二进制的执行文件依赖于某个动态链接库，就是在这个区域里面将 so 文件映射到了内存中。
再下面就是&lt;strong&gt;栈&lt;/strong&gt;（Stack）&lt;strong&gt;地址段&lt;/strong&gt;。主线程的函数调用的函数栈就是用这里的。&lt;/p&gt;
&lt;p&gt;普通进程不能访问内核空间，如果需要进行更高权限的工作，就需要系统调用进入内核。每一段进程的内存空间存放的内容各不相同，但是进入内核后看到的都是同一个内核空间，同一个进程列表。&lt;/p&gt;
&lt;p&gt;内核的代码访问内核的数据结构，大部分的情况下都是使用虚拟地址的，虽然内核代码权限很大，但是能够使用的虚拟地址范围也只能在内核空间，也即内核代码访问内核数据结构。&lt;/p&gt;
&lt;p&gt;接下来，我们需要知道，如何将其映射成为物理地址呢？&lt;/p&gt;
&lt;p&gt;咱们前面讲 x86 CPU 的时候，讲过分段机制，咱们规划虚拟空间的时候，也是将空间分成多个段进行保存。我们来看看分段机制的原理。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129182908.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129182908.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;分段机制下的虚拟地址由两部分组成，&lt;strong&gt;段选择子&lt;/strong&gt;和&lt;strong&gt;段内偏移量&lt;/strong&gt;。段选择子就保存在咱们前面讲过的段寄存器里面。段选择子里面最重要的是&lt;strong&gt;段号&lt;/strong&gt;，用作段表的索引。段表里面保存的是这个段的&lt;strong&gt;基地址&lt;/strong&gt;、&lt;strong&gt;段的界限&lt;/strong&gt;和&lt;strong&gt;特权等级&lt;/strong&gt;等。虚拟地址中的段内偏移量应该位于 0 和段界限之间。如果段内偏移量是合法的，就将段基地址加上段内偏移量得到物理内存地址。&lt;/p&gt;
&lt;p&gt;例如，我们将上面的虚拟空间分成以下 4 个段，用 0～3 来编号。每个段在段表中有一个项，在物理空间中，段的排列如下图的右边所示。如果要访问段 2 中偏移量 600 的虚拟地址，我们可以计算出物理地址为，段 2 基地址 2000 + 偏移量 600 = 2600。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129183334.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129183334.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;在 Linux 里面，段表全称&lt;strong&gt;段描述符表&lt;/strong&gt;（segment descriptors），放在&lt;strong&gt;全局描述符表 GDT&lt;/strong&gt;（Global Descriptor Table）里面，会有下面的宏来初始化段描述符表里面的表项。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define GDT_ENTRY_INIT(flags, base, limit) { { { \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;    .a = ((limit) &amp;amp; 0xffff) | (((base) &amp;amp; 0xffff) &amp;lt;&amp;lt; 16), \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;    .b = (((base) &amp;amp; 0xff0000) &amp;gt;&amp;gt; 16) | (((flags) &amp;amp; 0xf0ff) &amp;lt;&amp;lt; 8) | \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;      ((limit) &amp;amp; 0xf0000) | ((base) &amp;amp; 0xff000000), \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;  } } }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一个段表项由段基地址 base、段界限 limit，还有一些标识符组成。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;DEFINE_PER_CPU_PAGE_ALIGNED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gdt_page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;gdt_page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;gdt&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#ifdef CONFIG_X86_64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_KERNEL32_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc09b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_KERNEL_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xa09b&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_KERNEL_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc093&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_DEFAULT_USER32_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc0fb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_DEFAULT_USER_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc0f3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_DEFAULT_USER_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xa0fb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_KERNEL_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc09a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_KERNEL_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;    &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc092&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_DEFAULT_USER_CS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc0fa&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GDT_ENTRY_DEFAULT_USER_DS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;  &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;GDT_ENTRY_INIT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mh&#34;&gt;0xc0f2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mh&#34;&gt;0xfffff&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;......&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#endif
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;EXPORT_PER_CPU_SYMBOL_GPL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;gdt_page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里面对于 64 位的和 32 位的，都定义了内核代码段、内核数据段、用户代码段和用户数据段。另外，还会定义下面四个段选择子，指向上面的段描述符表项。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define __KERNEL_CS      (GDT_ENTRY_KERNEL_CS*8)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define __KERNEL_DS      (GDT_ENTRY_KERNEL_DS*8)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define __USER_DS      (GDT_ENTRY_DEFAULT_USER_DS*8 + 3)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define __USER_CS      (GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过分析，我们发现，所有的段的起始地址都是一样的，都是 0。所以，在 Linux 操作系统中，并没有使用到全部的分段功能。那分段是不是完全没有用处呢？分段可以做权限审核，例如用户态 DPL 是 3，内核态 DPL 是 0。当用户态试图访问内核态的时候，会因为权限不足而报错。
其实 Linux 倾向于另外一种从虚拟地址到物理地址的转换方式，称为&lt;strong&gt;分页&lt;/strong&gt;（Paging）。对于物理内存，操作系统把它分成一块一块大小相同的页，这样更方便管理，例如有的内存页面长时间不用了，可以&lt;strong&gt;暂时写到硬盘上&lt;/strong&gt;，称为&lt;strong&gt;换出&lt;/strong&gt;。一旦需要的时候，再&lt;strong&gt;加载进来&lt;/strong&gt;，叫作&lt;strong&gt;换入&lt;/strong&gt;。这样可以扩大可用物理内存的大小，提高物理内存的利用率。&lt;/p&gt;
&lt;p&gt;这个换入和换出都是以页为单位的。页面的大小一般为 4KB。为了能够定位和访问每个页，需要有个页表，保存每个页的起始地址，再加上在页内的偏移量，组成线性地址，就能对于内存中的每个位置进行访问了。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129185728.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129185728.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;虚拟地址分为两部分，&lt;strong&gt;页号&lt;/strong&gt;和&lt;strong&gt;页内偏移&lt;/strong&gt;。页号作为页表的索引，页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址。&lt;/p&gt;
&lt;p&gt;32 位环境下，虚拟地址空间共 4GB。如果分成 4KB 一个页，那就是 1M 个页。每个页表项需要 4 个字节来存储，那么整个 4GB 空间的映射就需要 4MB 的内存来存储映射表。如果每个进程都有自己的映射表，100 个进程就需要 400MB 的内存。对于内核来讲，有点大了。&lt;/p&gt;
&lt;p&gt;页表中所有页表项必须提前建好，并且要求是连续的。如果不连续，就没有办法通过虚拟地址里面的页号找到对应的页表项了。&lt;/p&gt;
&lt;p&gt;那怎么办呢？我们可以试着将页表再分页，4G 的空间需要 4M 的页表来存储映射。我们把这 4M 分成 1K（1024）个 4K，每个 4K 又能放在一页里面，这样 1K 个 4K 就是 1K 个页，这 1K 个页也需要一个表进行管理，我们称为页目录表，这个页目录表里面有 1K 项，每项 4 个字节，页目录表大小也是 4K。&lt;/p&gt;
&lt;p&gt;页目录有 1K 项，用 10 位就可以表示访问页目录的哪一项。这一项其实对应的是一整页的页表项，也即 4K 的页表项。每个页表项也是 4 个字节，因而一整页的页表项是 1K 个。再用 10 位就可以表示访问页表项的哪一项，页表项中的一项对应的就是一个页，是存放数据的页，这个页的大小是 4K，用 12 位可以定位这个页内的任何一个位置。&lt;/p&gt;
&lt;p&gt;这样加起来正好 32 位，也就是用前 10 位定位到页目录表中的一项。将这一项对应的页表取出来共 1k 项，再用中间 10 位定位到页表中的一项，将这一项对应的存放数据的页取出来，再用最后 12 位定位到页中的具体位置访问数据。&lt;/p&gt;
&lt;p&gt;

&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
    &lt;meta charset=&#34;UTF-8&#34;&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
    &lt;title&gt;Responsive Image&lt;/title&gt;
    &lt;style&gt;
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    
    &lt;div class=&#34;post-img-view&#34;&gt;
        &lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129192245.png&#34;&gt;
            &lt;img class=&#34;responsive-image&#34; src=&#34;https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129192245.png&#34; alt=&#34;&#34;  style=&#34;margin: 0 auto;&#34;/&gt;
        &lt;/a&gt;
    &lt;/div&gt;
    

    &lt;script&gt;
        document.addEventListener(&#34;DOMContentLoaded&#34;, function() {
            var images = document.querySelectorAll(&#34;.responsive-image&#34;);
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + &#34;px&#34;;
            });
        });
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;/p&gt;
&lt;p&gt;你可能会问，如果这样的话，映射 4GB 地址空间就需要 4MB+4KB 的内存，这样不是更大了吗？当然如果页是满的，当时是更大了，但是，我们往往不会为一个进程分配那么多内存。&lt;/p&gt;
&lt;p&gt;比如说，上面图中，我们假设只给这个进程分配了一个数据页。如果只使用页表，也需要完整的 1M 个页表项共 4M 的内存，但是如果使用了页目录，页目录需要 1K 个全部分配，占用内存 4K，但是里面只有一项使用了。到了页表项，只需要分配能够管理那个数据页的页表项页就可以了，也就是说，最多 4K，这样内存就节省多了。&lt;/p&gt;
&lt;p&gt;当然对于 64 位的系统，两级肯定不够了，就变成了四级目录，分别是全局页目录项 PGD（Page Global Directory）、上层页目录项 PUD（Page Upper Directory）、中间页目录项 PMD（Page Middle Directory）和页表项 PTE（Page Table Entry）。&lt;/p&gt;
&lt;h2 id=&#34;进程空间管理&#34;&gt;进程空间管理&lt;/h2&gt;
&lt;h2 id=&#34;物理内存管理&#34;&gt;物理内存管理&lt;/h2&gt;
&lt;h2 id=&#34;用户态内存映射&#34;&gt;用户态内存映射&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#define GDT_ENTRY_INIT(flags, base, limit) { { { \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;  .a = ((limit) &amp;amp; 0xffff) | (((base) &amp;amp; 0xffff) &amp;lt;&amp;lt; 16), \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;  .b = (((base) &amp;amp; 0xff0000) &amp;gt;&amp;gt; 16) | (((flags) &amp;amp; 0xf0ff) &amp;lt;&amp;lt; 8) | \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;    ((limit) &amp;amp; 0xf0000) | ((base) &amp;amp; 0xff000000), \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;  } } }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;通过分析，我们发现，所有的段的起始地址都是一样的，都是 0。这算哪门子分段嘛！所以，在 Linux 操作系统中，并没有使用到全部的分段功能。那分段是不是完全没有用处呢？分段可以做权限审核，例如用户态 DPL 是 3，内核态 DPL 是 0。当用户态试图访问内核态的时候，会因为权限不足而报错。&lt;/p&gt;
&lt;p&gt;其实 Linux 倾向于另外一种从虚拟地址到物理地址的转换方式，称为分页（Paging）。&lt;/p&gt;
&lt;p&gt;对于物理内存，操作系统把它分成一块一块大小相同的页，这样更方便管理，例如有的内存页面长时间不用了，可以暂时写到硬盘上，称为换出。一旦需要的时候，再加载进来，叫作换入。这样可以扩大可用物理内存的大小，提高物理内存的利用率。&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<h2 id="内存管理概述">内存管理概述</h2>
<p>计算机所谓的“计算”指的是：</p>
<ul>
<li>进程和线程对于 CPU 的使用</li>
<li>对内存的管理</li>
</ul>
<h3 id="独享内存空间的原理">独享内存空间的原理</h3>
<p>每个进程都独享一段内存空间，并且真实物理内存地址对进程不可见，操作系统会给进程分配一个虚拟地址，每个进程看到的内存地址都是从 0 开始。操作系统会将不同进程的虚拟地址和不同内存的物理地址做映射。当程序访问虚拟地址时，由内核的数据结构进行转换，转换成不同的物理地址，这样不同的进程运行的时候，写入的是不同的物理地址。</p>
<h3 id="规划虚拟地址空间">规划虚拟地址空间</h3>
<p>通过以上的原理，我们可以看出，操作系统的内存管理，主要分为三个方面。</p>
<ol>
<li>物理内存的管理；</li>
<li>虚拟地址的管理；</li>
<li>虚拟地址和物理地址如何映射；</li>
</ol>
<p>进程获取了一段独立的虚拟内存空间后，可以不用管其他进程，“任意”使用这片内存，但是也有一点规则。这篇内存需要存放内核态和用户态的内容。高地址存放内核态的内容，低地址存放用户态的内容。具体分界线 64 位与 32 位不同，暂不深究。</p>
<p>我们从最低位开始排起，先是<strong>Text Segment、Data Segment 和 BSS Segment</strong>。Text Segment 是存放二进制可执行代码的位置，Data Segment 存放静态常量，BSS Segment 存放未初始化的静态变量。是不是觉得这几个名字很熟悉？没错，咱们前面讲 ELF 格式的时候提到过，在二进制执行文件里面，就有这三个部分。这里就是把二进制执行文件的三个部分加载到内存里面。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129170110.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129170110.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>接下来是<strong>堆</strong>（Heap）<strong>段</strong>。堆是往高地址增长的，是用来动态分配内存的区域，<code>malloc</code> 就是在这里面分配的。
接下来的区域是<strong>Memory Mapping Segment</strong>。这块地址可以用来把文件映射进内存用的，如果二进制的执行文件依赖于某个动态链接库，就是在这个区域里面将 so 文件映射到了内存中。
再下面就是<strong>栈</strong>（Stack）<strong>地址段</strong>。主线程的函数调用的函数栈就是用这里的。</p>
<p>普通进程不能访问内核空间，如果需要进行更高权限的工作，就需要系统调用进入内核。每一段进程的内存空间存放的内容各不相同，但是进入内核后看到的都是同一个内核空间，同一个进程列表。</p>
<p>内核的代码访问内核的数据结构，大部分的情况下都是使用虚拟地址的，虽然内核代码权限很大，但是能够使用的虚拟地址范围也只能在内核空间，也即内核代码访问内核数据结构。</p>
<p>接下来，我们需要知道，如何将其映射成为物理地址呢？</p>
<p>咱们前面讲 x86 CPU 的时候，讲过分段机制，咱们规划虚拟空间的时候，也是将空间分成多个段进行保存。我们来看看分段机制的原理。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129182908.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129182908.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>分段机制下的虚拟地址由两部分组成，<strong>段选择子</strong>和<strong>段内偏移量</strong>。段选择子就保存在咱们前面讲过的段寄存器里面。段选择子里面最重要的是<strong>段号</strong>，用作段表的索引。段表里面保存的是这个段的<strong>基地址</strong>、<strong>段的界限</strong>和<strong>特权等级</strong>等。虚拟地址中的段内偏移量应该位于 0 和段界限之间。如果段内偏移量是合法的，就将段基地址加上段内偏移量得到物理内存地址。</p>
<p>例如，我们将上面的虚拟空间分成以下 4 个段，用 0～3 来编号。每个段在段表中有一个项，在物理空间中，段的排列如下图的右边所示。如果要访问段 2 中偏移量 600 的虚拟地址，我们可以计算出物理地址为，段 2 基地址 2000 + 偏移量 600 = 2600。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129183334.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129183334.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>在 Linux 里面，段表全称<strong>段描述符表</strong>（segment descriptors），放在<strong>全局描述符表 GDT</strong>（Global Descriptor Table）里面，会有下面的宏来初始化段描述符表里面的表项。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define GDT_ENTRY_INIT(flags, base, limit) { { { \
</span></span></span><span class="line"><span class="cl"><span class="cp">    .a = ((limit) &amp; 0xffff) | (((base) &amp; 0xffff) &lt;&lt; 16), \
</span></span></span><span class="line"><span class="cl"><span class="cp">    .b = (((base) &amp; 0xff0000) &gt;&gt; 16) | (((flags) &amp; 0xf0ff) &lt;&lt; 8) | \
</span></span></span><span class="line"><span class="cl"><span class="cp">      ((limit) &amp; 0xf0000) | ((base) &amp; 0xff000000), \
</span></span></span><span class="line"><span class="cl"><span class="cp">  } } }
</span></span></span></code></pre></div><p>一个段表项由段基地址 base、段界限 limit，还有一些标识符组成。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="nf">DEFINE_PER_CPU_PAGE_ALIGNED</span><span class="p">(</span><span class="k">struct</span> <span class="n">gdt_page</span><span class="p">,</span> <span class="n">gdt_page</span><span class="p">)</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">gdt</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef CONFIG_X86_64
</span></span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_KERNEL32_CS</span><span class="p">]</span>    <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc09b</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_KERNEL_CS</span><span class="p">]</span>    <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xa09b</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_KERNEL_DS</span><span class="p">]</span>    <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc093</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_DEFAULT_USER32_CS</span><span class="p">]</span>  <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc0fb</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_DEFAULT_USER_DS</span><span class="p">]</span>  <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc0f3</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_DEFAULT_USER_CS</span><span class="p">]</span>  <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xa0fb</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="cp">#else
</span></span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_KERNEL_CS</span><span class="p">]</span>    <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc09a</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_KERNEL_DS</span><span class="p">]</span>    <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc092</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_DEFAULT_USER_CS</span><span class="p">]</span>  <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc0fa</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="n">GDT_ENTRY_DEFAULT_USER_DS</span><span class="p">]</span>  <span class="o">=</span> <span class="nf">GDT_ENTRY_INIT</span><span class="p">(</span><span class="mh">0xc0f2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0xfffff</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">......</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="nf">EXPORT_PER_CPU_SYMBOL_GPL</span><span class="p">(</span><span class="n">gdt_page</span><span class="p">);</span>
</span></span></code></pre></div><p>这里面对于 64 位的和 32 位的，都定义了内核代码段、内核数据段、用户代码段和用户数据段。另外，还会定义下面四个段选择子，指向上面的段描述符表项。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define __KERNEL_CS      (GDT_ENTRY_KERNEL_CS*8)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define __KERNEL_DS      (GDT_ENTRY_KERNEL_DS*8)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define __USER_DS      (GDT_ENTRY_DEFAULT_USER_DS*8 + 3)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define __USER_CS      (GDT_ENTRY_DEFAULT_USER_CS*8 + 3)
</span></span></span></code></pre></div><p>通过分析，我们发现，所有的段的起始地址都是一样的，都是 0。所以，在 Linux 操作系统中，并没有使用到全部的分段功能。那分段是不是完全没有用处呢？分段可以做权限审核，例如用户态 DPL 是 3，内核态 DPL 是 0。当用户态试图访问内核态的时候，会因为权限不足而报错。
其实 Linux 倾向于另外一种从虚拟地址到物理地址的转换方式，称为<strong>分页</strong>（Paging）。对于物理内存，操作系统把它分成一块一块大小相同的页，这样更方便管理，例如有的内存页面长时间不用了，可以<strong>暂时写到硬盘上</strong>，称为<strong>换出</strong>。一旦需要的时候，再<strong>加载进来</strong>，叫作<strong>换入</strong>。这样可以扩大可用物理内存的大小，提高物理内存的利用率。</p>
<p>这个换入和换出都是以页为单位的。页面的大小一般为 4KB。为了能够定位和访问每个页，需要有个页表，保存每个页的起始地址，再加上在页内的偏移量，组成线性地址，就能对于内存中的每个位置进行访问了。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129185728.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129185728.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>虚拟地址分为两部分，<strong>页号</strong>和<strong>页内偏移</strong>。页号作为页表的索引，页表包含物理页每页所在物理内存的基地址。这个基地址与页内偏移的组合就形成了物理内存地址。</p>
<p>32 位环境下，虚拟地址空间共 4GB。如果分成 4KB 一个页，那就是 1M 个页。每个页表项需要 4 个字节来存储，那么整个 4GB 空间的映射就需要 4MB 的内存来存储映射表。如果每个进程都有自己的映射表，100 个进程就需要 400MB 的内存。对于内核来讲，有点大了。</p>
<p>页表中所有页表项必须提前建好，并且要求是连续的。如果不连续，就没有办法通过虚拟地址里面的页号找到对应的页表项了。</p>
<p>那怎么办呢？我们可以试着将页表再分页，4G 的空间需要 4M 的页表来存储映射。我们把这 4M 分成 1K（1024）个 4K，每个 4K 又能放在一页里面，这样 1K 个 4K 就是 1K 个页，这 1K 个页也需要一个表进行管理，我们称为页目录表，这个页目录表里面有 1K 项，每项 4 个字节，页目录表大小也是 4K。</p>
<p>页目录有 1K 项，用 10 位就可以表示访问页目录的哪一项。这一项其实对应的是一整页的页表项，也即 4K 的页表项。每个页表项也是 4 个字节，因而一整页的页表项是 1K 个。再用 10 位就可以表示访问页表项的哪一项，页表项中的一项对应的就是一个页，是存放数据的页，这个页的大小是 4K，用 12 位可以定位这个页内的任何一个位置。</p>
<p>这样加起来正好 32 位，也就是用前 10 位定位到页目录表中的一项。将这一项对应的页表取出来共 1k 项，再用中间 10 位定位到页表中的一项，将这一项对应的存放数据的页取出来，再用最后 12 位定位到页中的具体位置访问数据。</p>
<p>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Image</title>
    <style>
        .post-img-view {
            text-align: center;
        }
        .responsive-image {
            display: block;
            margin: 0 auto;
        }
    </style>
</head>
<body>
    
    <div class="post-img-view">
        <a data-fancybox="gallery" href="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129192245.png">
            <img class="responsive-image" src="https://picbed-1311007548.cos.ap-shanghai.myqcloud.com/markdown_picbed/img/20211129192245.png" alt=""  style="margin: 0 auto;"/>
        </a>
    </div>
    

    <script>
        document.addEventListener("DOMContentLoaded", function() {
            var images = document.querySelectorAll(".responsive-image");
            var maxHeight = window.innerHeight / 3;
            images.forEach(function(image) {
                image.style.maxHeight = maxHeight + "px";
            });
        });
    </script>
</body>
</html></p>
<p>你可能会问，如果这样的话，映射 4GB 地址空间就需要 4MB+4KB 的内存，这样不是更大了吗？当然如果页是满的，当时是更大了，但是，我们往往不会为一个进程分配那么多内存。</p>
<p>比如说，上面图中，我们假设只给这个进程分配了一个数据页。如果只使用页表，也需要完整的 1M 个页表项共 4M 的内存，但是如果使用了页目录，页目录需要 1K 个全部分配，占用内存 4K，但是里面只有一项使用了。到了页表项，只需要分配能够管理那个数据页的页表项页就可以了，也就是说，最多 4K，这样内存就节省多了。</p>
<p>当然对于 64 位的系统，两级肯定不够了，就变成了四级目录，分别是全局页目录项 PGD（Page Global Directory）、上层页目录项 PUD（Page Upper Directory）、中间页目录项 PMD（Page Middle Directory）和页表项 PTE（Page Table Entry）。</p>
<h2 id="进程空间管理">进程空间管理</h2>
<h2 id="物理内存管理">物理内存管理</h2>
<h2 id="用户态内存映射">用户态内存映射</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define GDT_ENTRY_INIT(flags, base, limit) { { { \
</span></span></span><span class="line"><span class="cl"><span class="cp">  .a = ((limit) &amp; 0xffff) | (((base) &amp; 0xffff) &lt;&lt; 16), \
</span></span></span><span class="line"><span class="cl"><span class="cp">  .b = (((base) &amp; 0xff0000) &gt;&gt; 16) | (((flags) &amp; 0xf0ff) &lt;&lt; 8) | \
</span></span></span><span class="line"><span class="cl"><span class="cp">    ((limit) &amp; 0xf0000) | ((base) &amp; 0xff000000), \
</span></span></span><span class="line"><span class="cl"><span class="cp">  } } }
</span></span></span></code></pre></div><p>通过分析，我们发现，所有的段的起始地址都是一样的，都是 0。这算哪门子分段嘛！所以，在 Linux 操作系统中，并没有使用到全部的分段功能。那分段是不是完全没有用处呢？分段可以做权限审核，例如用户态 DPL 是 3，内核态 DPL 是 0。当用户态试图访问内核态的时候，会因为权限不足而报错。</p>
<p>其实 Linux 倾向于另外一种从虚拟地址到物理地址的转换方式，称为分页（Paging）。</p>
<p>对于物理内存，操作系统把它分成一块一块大小相同的页，这样更方便管理，例如有的内存页面长时间不用了，可以暂时写到硬盘上，称为换出。一旦需要的时候，再加载进来，叫作换入。这样可以扩大可用物理内存的大小，提高物理内存的利用率。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Linux 操作系统-进程间通信</title>
      <link>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1/</link>
      <pubDate>Sat, 14 Aug 2021 09:46:39 +0000</pubDate>
      <guid>https://lifeislife.cn/posts/linux%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F-%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1/</guid>
      <description>&lt;p&gt;Linux 环境下，进程地址空间相互独立，每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到，所以进程和进程之间不能相互访问，要交换数据必须通过内核，在内核中开辟一块缓冲区，进程 1 把数据从用户空间拷到内核缓冲区，进程 2 再从内核缓冲区把数据读走，内核提供的这种机制称为&lt;strong&gt;进程间通信（IPC，InterProcess Communication）&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;进程间通信概述&#34;&gt;进程间通信概述&lt;/h2&gt;
&lt;h3 id=&#34;管道&#34;&gt;管道&lt;/h3&gt;
&lt;p&gt;在学 Linux 命令时就有管道在这个概念，比如下面这个命令&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ps -ef  | -grep root | xargs kill -9
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;将上一个命令的输出作为下一个命令的输入，数据只能向一个方向流动；双方需要互相通信时，需要建立起两个管道。&lt;/p&gt;
&lt;p&gt;管道有两种类型：匿名管道和命名管道。上面提到的命令中&lt;code&gt;|&lt;/code&gt;表示的管道即&lt;strong&gt;匿名管道 pipe&lt;/strong&gt;。用完即销毁，自动创建，自动销毁。&lt;/p&gt;
&lt;p&gt;使用&lt;code&gt;mkfifo&lt;/code&gt;显示创建的是&lt;strong&gt;命名管道 fifo&lt;/strong&gt;，&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkfifo hello
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;hello&lt;/code&gt;即是管道名称，类型为&lt;code&gt;p&lt;/code&gt;，就是&lt;code&gt;pipe&lt;/code&gt;，接下来就可以在管道里写入东西，&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# echo &amp;#34;hello world&amp;#34; &amp;gt; hello
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;光写入还不行，只有有另一个进程读取了内容才完成一次信息交换，才完成一次通信，&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# cat &amp;lt; hello 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hello world
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种方式通信效率低，无法频繁通信。&lt;/p&gt;
&lt;h3 id=&#34;消息队列&#34;&gt;消息队列&lt;/h3&gt;
&lt;p&gt;类似于日常沟通使用的邮件，有一定格式，有个收件列表，列表上的用户都可以反复在原邮件基础上回复，达到频繁交流的目的。这种模型就是&lt;strong&gt;消息队列模型&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;共享内存&#34;&gt;共享内存&lt;/h3&gt;
&lt;p&gt;共享内存允许两个或多个进程共享一个给定的存储区，这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中，一个进程写入共享内存的信息，可以被其他使用这个共享内存的进程，通过一个简单的内存读取错做读出，从而实现了进程间的通信。&lt;/p&gt;
&lt;p&gt;每个进程都有自己独立的虚拟内存空间，不同进程的虚拟内存空间映射到不同的物理内存中去。这个进程访问 A 地址和另一个进程访问 A 地址，其实访问的是不同的物理内存地址，对于数据的增删查改互不影响。&lt;/p&gt;
&lt;p&gt;但是，咱们是不是可以变通一下，&lt;strong&gt;拿出一块虚拟地址空间来，映射到相同的物理内存中&lt;/strong&gt;。这样这个进程写入的东西，另外一个进程马上就能看到了，都不需要拷贝来拷贝去，传来传去。&lt;/p&gt;
&lt;p&gt;使用&lt;code&gt;shmget&lt;/code&gt;函数创建一个共享内存，&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//key_t key:  唯一定位一个共享内存对象
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//size_t size: 共享内存大小
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//int flag: 如果是 IPC_CREAT 表示创建新的共享内存空间
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;shmget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;key_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;创建完毕之后，我们可以通过 &lt;code&gt;ipcs&lt;/code&gt; 命令查看这个共享内存。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;#ipcs ­­--shmems
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;------ Shared Memory Segments ------ ­­­­­­­­
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;key        shmid    owner perms    bytes nattch status
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;0x00000000 19398656 marc  600    1048576 2      dest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;进程通过&lt;code&gt;shmat&lt;/code&gt;，就是&lt;code&gt;attach&lt;/code&gt;的意思，将内存加载到自己虚拟地址空间某个位置。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//int shm_id:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//const void *addr: 加载的地址，通常设为 NULL，让内核选一个合适地址
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//int flag:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;shmat&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;shm_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果共享内存使用完毕，可以通过 &lt;code&gt;shmdt&lt;/code&gt; 解除绑定，然后通过 &lt;code&gt;shmctl&lt;/code&gt;，将 &lt;code&gt;cmd&lt;/code&gt; 设置为 &lt;code&gt;IPC_RMID&lt;/code&gt;，从而删除这个共享内存对象。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;shmdt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;shmctl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;shm_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;shmid_ds&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;共享内存的最大不足之处在于，由于多个进程对同一块内存区具有访问的权限，各个进程之间的同步问题显得尤为突出。必须控制同一时刻只有一个进程对共享内存区域写入数据，否则将造成数据的混乱。&lt;/p&gt;
&lt;h3 id=&#34;信号量&#34;&gt;信号量&lt;/h3&gt;
&lt;p&gt;如果两个进程同时向一个共享内存读写数据，很可能就会导致冲突。所以需要有一种保护机制，使得同一个共享资源同时只能被一个进程访问。在进程间通信机制中，&lt;strong&gt;信号量&lt;/strong&gt;（Semaphore）就是用来实现进程间互斥与同步的。它其实是个&lt;strong&gt;计数器&lt;/strong&gt;，只不过不是用来记录进程间通信数据的。&lt;/p&gt;
&lt;p&gt;我们可以将信号量初始化为一个数值，来代表某种资源的总体数量。对于信号量来讲，会定义两种原子操作，一个是&lt;code&gt;P&lt;/code&gt; 操作，我们称为&lt;strong&gt;申请资源操作&lt;/strong&gt;。这个操作会申请将信号量的数值&lt;strong&gt;减去&lt;/strong&gt; N，表示这些数量被他申请使用了，其他人不能用了。另一个是&lt;code&gt;V&lt;/code&gt;操作，我们称为&lt;strong&gt;归还资源操作&lt;/strong&gt;，这个操作会申请将信号量&lt;strong&gt;加上&lt;/strong&gt; M，表示这些数量已经还给信号量了，其他人可以使用了。&lt;/p&gt;
&lt;p&gt;所谓&lt;strong&gt;原子操作&lt;/strong&gt;（Atom Operation）就是不可被中断的一个或一系列操作。&lt;/p&gt;
&lt;p&gt;使用&lt;code&gt;semget&lt;/code&gt;&lt;strong&gt;创建&lt;/strong&gt;信号量，第一个参数表示唯一标识，第二个参数表示可以创建多少个信号量。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;semget&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;key_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;num_sems&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sem_flags&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接下来，我们需要&lt;strong&gt;初始化&lt;/strong&gt;信号量的总的资源数量。通过&lt;code&gt;semctl&lt;/code&gt; 函数，第一个参数 &lt;code&gt;semid&lt;/code&gt;是这个信号量组的&lt;code&gt;id&lt;/code&gt;，第二个参数 &lt;code&gt;semnum&lt;/code&gt; 才是在这个信号量组中某个信号量的&lt;code&gt;id&lt;/code&gt;，第三个参数是命令，如果是初始化，则用 &lt;code&gt;SETVAL&lt;/code&gt;，第四个参数是一个 &lt;code&gt;union&lt;/code&gt;。如果初始化，应该用里面的&lt;code&gt;val&lt;/code&gt;设置资源总量。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;semctl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semnum&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;union&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semun&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;union&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semun&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semid_ds&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kt&#34;&gt;unsigned&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;short&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;seminfo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;__buf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;无论是 P 操作还是 V 操作，我们统一用 semop 函数。第一个参数还是信号量组的 id，一次可以操作多个信号量。第三个参数 numops 就是有多少个操作，第二个参数将这些操作放在一个数组中。&lt;/p&gt;
&lt;p&gt;数组的每一项是一个 &lt;code&gt;struct sembuf&lt;/code&gt;，里面的第一个成员是这个操作的对象是哪个信号量。第二个成员就是要对这个信号量做多少改变。如果 &lt;code&gt;sem_op &amp;lt; 0&lt;/code&gt;，就请求 &lt;code&gt;sem_op&lt;/code&gt; 的绝对值的资源。如果相应的资源数可以满足请求，则将该信号量的值减去 &lt;code&gt;sem_op&lt;/code&gt; 的绝对值，函数成功返回。&lt;/p&gt;
&lt;p&gt;当相应的资源数不能满足请求时，就要看&lt;code&gt;sem_flg&lt;/code&gt; 了。如果把 &lt;code&gt;sem_flg&lt;/code&gt; 设置为&lt;code&gt;IPC_NOWAIT&lt;/code&gt;，也就是没有资源也不等待，则 &lt;code&gt;semop&lt;/code&gt; 函数出错返回 &lt;code&gt;EAGAIN&lt;/code&gt;。如果 &lt;code&gt;sem_flg&lt;/code&gt; 没有指定&lt;code&gt;IPC_NOWAIT&lt;/code&gt;，则进程挂起，直到当相应的资源数可以满足请求。若 &lt;code&gt;sem_op &amp;gt; 0&lt;/code&gt;，表示进程归还相应的资源数，将 &lt;code&gt;sem_op&lt;/code&gt; 的值加到信号量的值上。如果有进程正在休眠等待此信号量，则唤醒它们。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;semop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sembuf&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;semoparray&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[],&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;size_t&lt;/span&gt;  &lt;span class=&#34;n&#34;&gt;numops&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sembuf&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kt&#34;&gt;short&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sem_num&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// 信号量组中对应的序号，0～sem_nums-1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kt&#34;&gt;short&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sem_op&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;// 信号量值在一次操作中的改变量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kt&#34;&gt;short&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sem_flg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// IPC_NOWAIT, SEM_UNDO
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;信号&#34;&gt;信号&lt;/h3&gt;
&lt;p&gt;以上提到的通信方式，都是常规状态下的工作模式，而信号一般是由&lt;strong&gt;错误&lt;/strong&gt;产生的。&lt;/p&gt;
&lt;p&gt;信号没有特别复杂的数据结构，就是用一个代号一样的数字。Linux 提供了几十种信号，分别代表不同的意义。信号之间依靠它们的值来区分。&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<p>Linux 环境下，进程地址空间相互独立，每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到，所以进程和进程之间不能相互访问，要交换数据必须通过内核，在内核中开辟一块缓冲区，进程 1 把数据从用户空间拷到内核缓冲区，进程 2 再从内核缓冲区把数据读走，内核提供的这种机制称为<strong>进程间通信（IPC，InterProcess Communication）</strong>。</p>
<h2 id="进程间通信概述">进程间通信概述</h2>
<h3 id="管道">管道</h3>
<p>在学 Linux 命令时就有管道在这个概念，比如下面这个命令</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">ps -ef  | -grep root | xargs kill -9
</span></span></code></pre></div><p>将上一个命令的输出作为下一个命令的输入，数据只能向一个方向流动；双方需要互相通信时，需要建立起两个管道。</p>
<p>管道有两种类型：匿名管道和命名管道。上面提到的命令中<code>|</code>表示的管道即<strong>匿名管道 pipe</strong>。用完即销毁，自动创建，自动销毁。</p>
<p>使用<code>mkfifo</code>显示创建的是<strong>命名管道 fifo</strong>，</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">mkfifo hello
</span></span></code></pre></div><p><code>hello</code>即是管道名称，类型为<code>p</code>，就是<code>pipe</code>，接下来就可以在管道里写入东西，</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># echo &#34;hello world&#34; &gt; hello
</span></span></code></pre></div><p>光写入还不行，只有有另一个进程读取了内容才完成一次信息交换，才完成一次通信，</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># cat &lt; hello 
</span></span><span class="line"><span class="cl">hello world
</span></span></code></pre></div><p>这种方式通信效率低，无法频繁通信。</p>
<h3 id="消息队列">消息队列</h3>
<p>类似于日常沟通使用的邮件，有一定格式，有个收件列表，列表上的用户都可以反复在原邮件基础上回复，达到频繁交流的目的。这种模型就是<strong>消息队列模型</strong>。</p>
<h3 id="共享内存">共享内存</h3>
<p>共享内存允许两个或多个进程共享一个给定的存储区，这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中，一个进程写入共享内存的信息，可以被其他使用这个共享内存的进程，通过一个简单的内存读取错做读出，从而实现了进程间的通信。</p>
<p>每个进程都有自己独立的虚拟内存空间，不同进程的虚拟内存空间映射到不同的物理内存中去。这个进程访问 A 地址和另一个进程访问 A 地址，其实访问的是不同的物理内存地址，对于数据的增删查改互不影响。</p>
<p>但是，咱们是不是可以变通一下，<strong>拿出一块虚拟地址空间来，映射到相同的物理内存中</strong>。这样这个进程写入的东西，另外一个进程马上就能看到了，都不需要拷贝来拷贝去，传来传去。</p>
<p>使用<code>shmget</code>函数创建一个共享内存，</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">//key_t key:  唯一定位一个共享内存对象
</span></span></span><span class="line"><span class="cl"><span class="c1">//size_t size: 共享内存大小
</span></span></span><span class="line"><span class="cl"><span class="c1">//int flag: 如果是 IPC_CREAT 表示创建新的共享内存空间
</span></span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">shmget</span><span class="p">(</span><span class="kt">key_t</span> <span class="n">key</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flag</span><span class="p">);</span>
</span></span></code></pre></div><p>创建完毕之后，我们可以通过 <code>ipcs</code> 命令查看这个共享内存。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">#ipcs ­­--shmems
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">------ Shared Memory Segments ------ ­­­­­­­­
</span></span><span class="line"><span class="cl">key        shmid    owner perms    bytes nattch status
</span></span><span class="line"><span class="cl">0x00000000 19398656 marc  600    1048576 2      dest
</span></span></code></pre></div><p>进程通过<code>shmat</code>，就是<code>attach</code>的意思，将内存加载到自己虚拟地址空间某个位置。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">//int shm_id:
</span></span></span><span class="line"><span class="cl"><span class="c1">//const void *addr: 加载的地址，通常设为 NULL，让内核选一个合适地址
</span></span></span><span class="line"><span class="cl"><span class="c1">//int flag:
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="o">*</span><span class="nf">shmat</span><span class="p">(</span><span class="kt">int</span> <span class="n">shm_id</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flag</span><span class="p">);</span>
</span></span></code></pre></div><p>如果共享内存使用完毕，可以通过 <code>shmdt</code> 解除绑定，然后通过 <code>shmctl</code>，将 <code>cmd</code> 设置为 <code>IPC_RMID</code>，从而删除这个共享内存对象。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">shmdt</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">addr</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">shmctl</span><span class="p">(</span><span class="kt">int</span> <span class="n">shm_id</span><span class="p">,</span> <span class="kt">int</span> <span class="n">cmd</span><span class="p">,</span> <span class="k">struct</span> <span class="n">shmid_ds</span> <span class="o">*</span><span class="n">buf</span><span class="p">);</span>
</span></span></code></pre></div><p>共享内存的最大不足之处在于，由于多个进程对同一块内存区具有访问的权限，各个进程之间的同步问题显得尤为突出。必须控制同一时刻只有一个进程对共享内存区域写入数据，否则将造成数据的混乱。</p>
<h3 id="信号量">信号量</h3>
<p>如果两个进程同时向一个共享内存读写数据，很可能就会导致冲突。所以需要有一种保护机制，使得同一个共享资源同时只能被一个进程访问。在进程间通信机制中，<strong>信号量</strong>（Semaphore）就是用来实现进程间互斥与同步的。它其实是个<strong>计数器</strong>，只不过不是用来记录进程间通信数据的。</p>
<p>我们可以将信号量初始化为一个数值，来代表某种资源的总体数量。对于信号量来讲，会定义两种原子操作，一个是<code>P</code> 操作，我们称为<strong>申请资源操作</strong>。这个操作会申请将信号量的数值<strong>减去</strong> N，表示这些数量被他申请使用了，其他人不能用了。另一个是<code>V</code>操作，我们称为<strong>归还资源操作</strong>，这个操作会申请将信号量<strong>加上</strong> M，表示这些数量已经还给信号量了，其他人可以使用了。</p>
<p>所谓<strong>原子操作</strong>（Atom Operation）就是不可被中断的一个或一系列操作。</p>
<p>使用<code>semget</code><strong>创建</strong>信号量，第一个参数表示唯一标识，第二个参数表示可以创建多少个信号量。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">semget</span><span class="p">(</span><span class="kt">key_t</span> <span class="n">key</span><span class="p">,</span> <span class="kt">int</span> <span class="n">num_sems</span><span class="p">,</span> <span class="kt">int</span> <span class="n">sem_flags</span><span class="p">);</span>
</span></span></code></pre></div><p>接下来，我们需要<strong>初始化</strong>信号量的总的资源数量。通过<code>semctl</code> 函数，第一个参数 <code>semid</code>是这个信号量组的<code>id</code>，第二个参数 <code>semnum</code> 才是在这个信号量组中某个信号量的<code>id</code>，第三个参数是命令，如果是初始化，则用 <code>SETVAL</code>，第四个参数是一个 <code>union</code>。如果初始化，应该用里面的<code>val</code>设置资源总量。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">semctl</span><span class="p">(</span><span class="kt">int</span> <span class="n">semid</span><span class="p">,</span> <span class="kt">int</span> <span class="n">semnum</span><span class="p">,</span> <span class="kt">int</span> <span class="n">cmd</span><span class="p">,</span> <span class="k">union</span> <span class="n">semun</span> <span class="n">args</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="k">union</span> <span class="n">semun</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">semid_ds</span> <span class="o">*</span><span class="n">buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">short</span> <span class="kt">int</span> <span class="o">*</span><span class="n">array</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="n">seminfo</span> <span class="o">*</span><span class="n">__buf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>无论是 P 操作还是 V 操作，我们统一用 semop 函数。第一个参数还是信号量组的 id，一次可以操作多个信号量。第三个参数 numops 就是有多少个操作，第二个参数将这些操作放在一个数组中。</p>
<p>数组的每一项是一个 <code>struct sembuf</code>，里面的第一个成员是这个操作的对象是哪个信号量。第二个成员就是要对这个信号量做多少改变。如果 <code>sem_op &lt; 0</code>，就请求 <code>sem_op</code> 的绝对值的资源。如果相应的资源数可以满足请求，则将该信号量的值减去 <code>sem_op</code> 的绝对值，函数成功返回。</p>
<p>当相应的资源数不能满足请求时，就要看<code>sem_flg</code> 了。如果把 <code>sem_flg</code> 设置为<code>IPC_NOWAIT</code>，也就是没有资源也不等待，则 <code>semop</code> 函数出错返回 <code>EAGAIN</code>。如果 <code>sem_flg</code> 没有指定<code>IPC_NOWAIT</code>，则进程挂起，直到当相应的资源数可以满足请求。若 <code>sem_op &gt; 0</code>，表示进程归还相应的资源数，将 <code>sem_op</code> 的值加到信号量的值上。如果有进程正在休眠等待此信号量，则唤醒它们。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">semop</span><span class="p">(</span><span class="kt">int</span> <span class="n">semid</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sembuf</span> <span class="n">semoparray</span><span class="p">[],</span> <span class="kt">size_t</span>  <span class="n">numops</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">sembuf</span> 
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">short</span> <span class="n">sem_num</span><span class="p">;</span> <span class="c1">// 信号量组中对应的序号，0～sem_nums-1
</span></span></span><span class="line"><span class="cl">  <span class="kt">short</span> <span class="n">sem_op</span><span class="p">;</span>  <span class="c1">// 信号量值在一次操作中的改变量
</span></span></span><span class="line"><span class="cl">  <span class="kt">short</span> <span class="n">sem_flg</span><span class="p">;</span> <span class="c1">// IPC_NOWAIT, SEM_UNDO
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="信号">信号</h3>
<p>以上提到的通信方式，都是常规状态下的工作模式，而信号一般是由<strong>错误</strong>产生的。</p>
<p>信号没有特别复杂的数据结构，就是用一个代号一样的数字。Linux 提供了几十种信号，分别代表不同的意义。信号之间依靠它们的值来区分。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
