<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>客舍青青</title>
  
  <subtitle>The finger falls into a poem, characters fly through my fingers.</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://www.caiyifan.cn/"/>
  <updated>2024-05-13T16:10:28.380Z</updated>
  <id>https://www.caiyifan.cn/</id>
  
  <author>
    <name>Ifan Tsai</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>k8s first commit 源码分析之 Cloudcfg</title>
    <link href="https://www.caiyifan.cn/p/a35ad04f.html"/>
    <id>https://www.caiyifan.cn/p/a35ad04f.html</id>
    <published>2022-10-01T09:51:30.000Z</published>
    <updated>2024-05-13T16:10:28.380Z</updated>
    
    <content type="html"><![CDATA[<p>上一次分析了<a href="https://www.caiyifan.cn/p/c0ce3a49.html">api server</a>的源码，这一次来分析 cloudcfg 的源码。</p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>cloudcfg 可以看做是 kubectl 的前身，负责与 API server 的交互，只存在于上古时代的 k8s 中，我们现在接触到的都是 kubectl 命令行工具了。该组件做的事情非常简单，就是将用户的命令行操作转化为对 API server 的 HTTP 请求。</p><h2 id="命令行主体逻辑"><a href="#命令行主体逻辑" class="headerlink" title="命令行主体逻辑"></a>命令行主体逻辑</h2><p>从 cloudcfg 的命令行入口开始分析，命令行代码位于 <code>cmd/apiserver/cloudcfg.go</code></p><a id="more"></a><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The flag package provides a default help printer via -h switch</span></span><br><span class="line"><span class="keyword">var</span> versionFlag *<span class="keyword">bool</span> = flag.Bool(<span class="string">"v"</span>, <span class="literal">false</span>, <span class="string">"Print the version number."</span>)</span><br><span class="line"><span class="keyword">var</span> httpServer *<span class="keyword">string</span> = flag.String(<span class="string">"h"</span>, <span class="string">""</span>, <span class="string">"The host to connect to."</span>)</span><br><span class="line"><span class="keyword">var</span> config *<span class="keyword">string</span> = flag.String(<span class="string">"c"</span>, <span class="string">""</span>, <span class="string">"Path to the config file."</span>)</span><br><span class="line"><span class="keyword">var</span> labelQuery *<span class="keyword">string</span> = flag.String(<span class="string">"l"</span>, <span class="string">""</span>, <span class="string">"Label query to use for listing"</span>)</span><br><span class="line"><span class="keyword">var</span> updatePeriod *time.Duration = flag.Duration(<span class="string">"u"</span>, <span class="number">60</span>*time.Second, <span class="string">"Update interarrival in seconds"</span>)</span><br><span class="line"><span class="keyword">var</span> portSpec *<span class="keyword">string</span> = flag.String(<span class="string">"p"</span>, <span class="string">""</span>, <span class="string">"The port spec, comma-separated list of &lt;external&gt;:&lt;internal&gt;,..."</span>)</span><br><span class="line"><span class="keyword">var</span> servicePort *<span class="keyword">int</span> = flag.Int(<span class="string">"s"</span>, <span class="number">-1</span>, <span class="string">"If positive, create and run a corresponding service on this port, only used with 'run'"</span>)</span><br><span class="line"><span class="keyword">var</span> authConfig *<span class="keyword">string</span> = flag.String(<span class="string">"auth"</span>, os.Getenv(<span class="string">"HOME"</span>)+<span class="string">"/.kubernetes_auth"</span>, <span class="string">"Path to the auth info file.  If missing, prompt the user"</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">usage</span><span class="params">()</span></span> &#123;</span><br><span class="line">log.Fatal(<span class="string">"Usage: cloudcfg -h &lt;host&gt; [-c config/file.json] [-p &lt;hostPort&gt;:&lt;containerPort&gt;,..., &lt;hostPort-n&gt;:&lt;containerPort-n&gt; &lt;method&gt; &lt;path&gt;"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最开始定义了 cloudcfg 命令行工具需要的所有参数。</p><p>main 函数中先从命令行中获取 method, api server 的 host，资源对象的类型以及鉴权参数。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">method := flag.Arg(<span class="number">0</span>)</span><br><span class="line">url := *httpServer + <span class="string">"/api/v1beta1"</span> + flag.Arg(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">auth, err := cloudcfg.LoadAuthInfo(*authConfig)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    log.Fatalf(<span class="string">"Error loading auth: %#v"</span>, err)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再根据 method 判断是对资源的何种操作方式。 </p><p>对资源的 CRUD 是通过拼接 method 和 labelQuery 作为 api server 的请求 URL 去发送 HTTP 请求，没有其他多余的逻辑。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> method == <span class="string">"get"</span> || method == <span class="string">"list"</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(*labelQuery) &gt; <span class="number">0</span> &amp;&amp; method == <span class="string">"list"</span> &#123;</span><br><span class="line">        url = url + <span class="string">"?labels="</span> + *labelQuery</span><br><span class="line">    &#125;</span><br><span class="line">    request, err = http.NewRequest(<span class="string">"GET"</span>, url, <span class="literal">nil</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> method == <span class="string">"delete"</span> &#123;</span><br><span class="line">    request, err = http.NewRequest(<span class="string">"DELETE"</span>, url, <span class="literal">nil</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> method == <span class="string">"create"</span> &#123;</span><br><span class="line">    request, err = cloudcfg.RequestWithBody(*config, url, <span class="string">"POST"</span>)</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> method == <span class="string">"update"</span> &#123;</span><br><span class="line">    request, err = cloudcfg.RequestWithBody(*config, url, <span class="string">"PUT"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> body <span class="keyword">string</span></span><br><span class="line">body, err = cloudcfg.DoRequest(request, auth.User, auth.Password)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    log.Fatalf(<span class="string">"Error: %#v"</span>, err)</span><br><span class="line">&#125;</span><br><span class="line">fmt.Println(body)</span><br></pre></td></tr></table></figure><p>还有对 rollingupdate 的操作和 controller 的操作，后面分别展开分析</p><h2 id="rollingupdate操作"><a href="#rollingupdate操作" class="headerlink" title="rollingupdate操作"></a>rollingupdate操作</h2><p>判断 method 为 rollingupdate 后通过 client 执行 update</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> method == <span class="string">"rollingupdate"</span> &#123;</span><br><span class="line">    client := &amp;kube_client.Client&#123;</span><br><span class="line">        Host: *httpServer,</span><br><span class="line">        Auth: &amp;auth,</span><br><span class="line">    &#125;</span><br><span class="line">    cloudcfg.Update(flag.Arg(<span class="number">1</span>), client, *updatePeriod)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>具体操作是通过<code>pkg/client/client.go</code>中实现的 client 进行操作， client 实现了以下接口</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ClientInterface holds the methods for clients of Kubenetes, an interface to allow mock testing</span></span><br><span class="line"><span class="keyword">type</span> ClientInterface <span class="keyword">interface</span> &#123;</span><br><span class="line">ListTasks(labelQuery <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>) (api.TaskList, error)</span><br><span class="line">GetTask(name <span class="keyword">string</span>) (api.Task, error)</span><br><span class="line">DeleteTask(name <span class="keyword">string</span>) error</span><br><span class="line">CreateTask(api.Task) (api.Task, error)</span><br><span class="line">UpdateTask(api.Task) (api.Task, error)</span><br><span class="line"></span><br><span class="line">GetReplicationController(name <span class="keyword">string</span>) (api.ReplicationController, error)</span><br><span class="line">CreateReplicationController(api.ReplicationController) (api.ReplicationController, error)</span><br><span class="line">UpdateReplicationController(api.ReplicationController) (api.ReplicationController, error)</span><br><span class="line">DeleteReplicationController(<span class="keyword">string</span>) error</span><br><span class="line"></span><br><span class="line">GetService(name <span class="keyword">string</span>) (api.Service, error)</span><br><span class="line">CreateService(api.Service) (api.Service, error)</span><br><span class="line">UpdateService(api.Service) (api.Service, error)</span><br><span class="line">DeleteService(<span class="keyword">string</span>) error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到就是对 tasks，RC 和 serveice 的操作。以 create tasks 为例</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// CreateTask takes the representation of a task.  Returns the server's representation of the task, and an error, if it occurs</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(client Client)</span> <span class="title">CreateTask</span><span class="params">(task api.Task)</span> <span class="params">(api.Task, error)</span></span> &#123;</span><br><span class="line"><span class="keyword">var</span> result api.Task</span><br><span class="line">body, err := json.Marshal(task)</span><br><span class="line"><span class="keyword">if</span> err == <span class="literal">nil</span> &#123;</span><br><span class="line">_, err = client.rawRequest(<span class="string">"POST"</span>, <span class="string">"tasks"</span>, bytes.NewBuffer(body), &amp;result)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> result, err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到同样是通过请求 api server 来完成操作的。</p><p>回到命令行的 main 函数，创建 client 后调用<code>cloudcfg.Update(flag.Arg(1), client, *updatePeriod)</code></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径：pkg/cloudcfg/cloudcfg.go</span></span><br><span class="line"><span class="comment">// Perform a rolling update of a collection of tasks.</span></span><br><span class="line"><span class="comment">// 'name' points to a replication controller.</span></span><br><span class="line"><span class="comment">// 'client' is used for updating tasks.</span></span><br><span class="line"><span class="comment">// 'updatePeriod' is the time between task updates.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Update</span><span class="params">(name <span class="keyword">string</span>, client client.ClientInterface, updatePeriod time.Duration)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">controller, err := client.GetReplicationController(name)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">labels := controller.DesiredState.ReplicasInSet</span><br><span class="line"></span><br><span class="line">taskList, err := client.ListTasks(labels)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">for</span> _, task := <span class="keyword">range</span> taskList.Items &#123;</span><br><span class="line">_, err = client.UpdateTask(task)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">time.Sleep(updatePeriod)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>大致逻辑是通过 name 获取 RC 对象，再通过 RC 对象的期望状态获取 tasks label，再通过 task label 来 list 所有 task 后更新 task。不过笔者没太看懂这里的更新逻辑，看上去把遍历 task 的时候由原封不动传回去了，或许第一个版本还不支持滚动更新？待笔者后续完整深入看完所有组件逻辑再来补充。</p><h2 id="controller-操作"><a href="#controller-操作" class="headerlink" title="controller 操作"></a>controller 操作</h2><p>判断 method 为 run 时，拿到 image，replicas， name 后执行 RunController</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> method == <span class="string">"run"</span> &#123;</span><br><span class="line">    args := flag.Args()</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(args) &lt; <span class="number">4</span> &#123;</span><br><span class="line">        log.Fatal(<span class="string">"usage: cloudcfg -h &lt;host&gt; run &lt;image&gt; &lt;replicas&gt; &lt;name&gt;"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    image := args[<span class="number">1</span>]</span><br><span class="line">    replicas, err := strconv.Atoi(args[<span class="number">2</span>])</span><br><span class="line">    name := args[<span class="number">3</span>]</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">"Error parsing replicas: %#v"</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">    err = cloudcfg.RunController(image, name, replicas, kube_client.Client&#123;Host: *httpServer, Auth: &amp;auth&#125;, *portSpec, *servicePort)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">"Error: %#v"</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>进入到 cloudcfg.RunController 函数内部分析</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">controller := api.ReplicationController&#123;</span><br><span class="line">    JSONBase: api.JSONBase&#123;</span><br><span class="line">        ID: name,</span><br><span class="line">    &#125;,</span><br><span class="line">    DesiredState: api.ReplicationControllerState&#123;</span><br><span class="line">        Replicas: replicas,</span><br><span class="line">        ReplicasInSet: <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>&#123;</span><br><span class="line">            <span class="string">"name"</span>: name,</span><br><span class="line">        &#125;,</span><br><span class="line">        TaskTemplate: api.TaskTemplate&#123;</span><br><span class="line">            DesiredState: api.TaskState&#123;</span><br><span class="line">                Manifest: api.ContainerManifest&#123;</span><br><span class="line">                    Containers: []api.Container&#123;</span><br><span class="line">                        api.Container&#123;</span><br><span class="line">                            Image: image,</span><br><span class="line">                            Ports: makePorts(portSpec),</span><br><span class="line">                        &#125;,</span><br><span class="line">                    &#125;,</span><br><span class="line">                &#125;,</span><br><span class="line">            &#125;,</span><br><span class="line">            Labels: <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>&#123;</span><br><span class="line">                <span class="string">"name"</span>: name,</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    Labels: <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>&#123;</span><br><span class="line">        <span class="string">"name"</span>: name,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">controllerOut, err := client.CreateReplicationController(controller)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>根据外部传入的参数构造 RC 对象，然后调用 client 的 CreateReplicationController 函数，本质还是向 API Server 发起请求</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">data, err := yaml.Marshal(controllerOut)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">fmt.Print(<span class="keyword">string</span>(data))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> servicePort &gt; <span class="number">0</span> &#123;</span><br><span class="line">    svc, err := createService(name, servicePort, client)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    data, err = yaml.Marshal(svc)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="keyword">string</span>(data))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再根据掺入的 servicePort 创建 service</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">createService</span><span class="params">(name <span class="keyword">string</span>, port <span class="keyword">int</span>, client client.ClientInterface)</span> <span class="params">(api.Service, error)</span></span> &#123;</span><br><span class="line">svc := api.Service&#123;</span><br><span class="line">JSONBase: api.JSONBase&#123;ID: name&#125;,</span><br><span class="line">Port:     port,</span><br><span class="line">Labels: <span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>&#123;</span><br><span class="line"><span class="string">"name"</span>: name,</span><br><span class="line">&#125;,</span><br><span class="line">&#125;</span><br><span class="line">svc, err := client.CreateService(svc)</span><br><span class="line"><span class="keyword">return</span> svc, err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>同样是向 API server 发起请求。</p><p>回到 cloudcfg 命令行中，判断 method 为 stop 时执行 StopController</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">else</span> <span class="keyword">if</span> method == <span class="string">"stop"</span> &#123;</span><br><span class="line">    err = cloudcfg.StopController(flag.Arg(<span class="number">1</span>), kube_client.Client&#123;Host: *httpServer, Auth: &amp;auth&#125;)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">"Error: %#v"</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>分析 cloudcfg.StopController</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// StopController stops a controller named 'name' by setting replicas to zero</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">StopController</span><span class="params">(name <span class="keyword">string</span>, client client.ClientInterface)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">controller, err := client.GetReplicationController(name)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">controller.DesiredState.Replicas = <span class="number">0</span></span><br><span class="line">controllerOut, err := client.UpdateReplicationController(controller)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">data, err := yaml.Marshal(controllerOut)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line">fmt.Print(<span class="keyword">string</span>(data))</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到将 RC 的 Replicas 置为 0 后发送给了 API server。</p><p>Controller 的操作还有一个 rm</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">lse <span class="keyword">if</span> method == <span class="string">"rm"</span> &#123;</span><br><span class="line">    err = cloudcfg.DeleteController(flag.Arg(<span class="number">1</span>), kube_client.Client&#123;Host: *httpServer, Auth: &amp;auth&#125;)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">        log.Fatalf(<span class="string">"Error: %#v"</span>, err)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>cloudcfg.DeleteController 也很简单，获取当前 RC 的副本数，如果副本数不为 0 则报错退出，否则请求 API server 删除</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DeleteController deletes a replication controller named 'name', requires that the controller</span></span><br><span class="line"><span class="comment">// already be stopped</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">DeleteController</span><span class="params">(name <span class="keyword">string</span>, client client.ClientInterface)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">controller, err := client.GetReplicationController(name)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> controller.DesiredState.Replicas != <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">"controller has non-zero replicas (%d)"</span>, controller.DesiredState.Replicas)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> client.DeleteReplicationController(name)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我的博客即将同步至腾讯云开发者社区，邀请大家一同入驻：<a href="https://cloud.tencent.com/developer/support-plan?invite_code=2td5ld5xvow0o" target="_blank" rel="external nofollow noopener noreferrer">https://cloud.tencent.com/developer/support-plan?invite_code=2td5ld5xvow0o</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;上一次分析了&lt;a href=&quot;https://www.caiyifan.cn/p/c0ce3a49.html&quot;&gt;api server&lt;/a&gt;的源码，这一次来分析 cloudcfg 的源码。&lt;/p&gt;
&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;cloudcfg 可以看做是 kubectl 的前身，负责与 API server 的交互，只存在于上古时代的 k8s 中，我们现在接触到的都是 kubectl 命令行工具了。该组件做的事情非常简单，就是将用户的命令行操作转化为对 API server 的 HTTP 请求。&lt;/p&gt;
&lt;h2 id=&quot;命令行主体逻辑&quot;&gt;&lt;a href=&quot;#命令行主体逻辑&quot; class=&quot;headerlink&quot; title=&quot;命令行主体逻辑&quot;&gt;&lt;/a&gt;命令行主体逻辑&lt;/h2&gt;&lt;p&gt;从 cloudcfg 的命令行入口开始分析，命令行代码位于 &lt;code&gt;cmd/apiserver/cloudcfg.go&lt;/code&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="k8s" scheme="https://www.caiyifan.cn/tags/k8s/"/>
    
  </entry>
  
  <entry>
    <title>k8s first commit 源码分析之 API Server</title>
    <link href="https://www.caiyifan.cn/p/c0ce3a49.html"/>
    <id>https://www.caiyifan.cn/p/c0ce3a49.html</id>
    <published>2022-09-14T16:44:10.000Z</published>
    <updated>2024-05-01T10:34:38.736Z</updated>
    
    <content type="html"><![CDATA[<p>穿越回 2014 年，分析下 k8s 第一个提交的源码。</p><h2 id="获取-first-commit-源码"><a href="#获取-first-commit-源码" class="headerlink" title="获取 first commit 源码"></a>获取 first commit 源码</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git clone https:&#x2F;&#x2F;github.com&#x2F;kubernetes&#x2F;kubernetes.git</span><br><span class="line">cd kubernetes</span><br><span class="line">git checkout &#96;git rev-list --max-parents&#x3D;0 HEAD&#96;</span><br></pre></td></tr></table></figure><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>api-server 是 k8s 的核心组件之一，用于接收 kubelet 的请求，并将请求信息保存到后端存储 etcd 中。核心功能是提供 k8s 各类资源对象的 CURD 等操作。</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>从 api-server 的命令行入口开始分析，命令行代码位于 <code>cmd/apiserver/apiserver.go</code></p><a id="more"></a><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> (</span><br><span class="line">port                        = flag.Uint(<span class="string">"port"</span>, <span class="number">8080</span>, <span class="string">"The port to listen on.  Default 8080."</span>)</span><br><span class="line">address                     = flag.String(<span class="string">"address"</span>, <span class="string">"127.0.0.1"</span>, <span class="string">"The address on the local server to listen to. Default 127.0.0.1"</span>)</span><br><span class="line">apiPrefix                   = flag.String(<span class="string">"api_prefix"</span>, <span class="string">"/api/v1beta1"</span>, <span class="string">"The prefix for API requests on the server. Default '/api/v1beta1'"</span>)</span><br><span class="line">etcdServerList, machineList util.StringList</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> &#123;</span><br><span class="line">flag.Var(&amp;etcdServerList, <span class="string">"etcd_servers"</span>, <span class="string">"Servers for the etcd (http://ip:port), comma separated"</span>)</span><br><span class="line">flag.Var(&amp;machineList, <span class="string">"machines"</span>, <span class="string">"List of machines to schedule onto, comma separated."</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最开始定义了 api-server 启动所需要的相关参数，上古版本的 k8s 使用了标准库自带的 flag 库，其中 <code>util.StringList</code>实现了<code>flag.Value</code>接口。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> StringList []<span class="keyword">string</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sl *StringList)</span> <span class="title">String</span><span class="params">()</span> <span class="title">string</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Sprint(*sl)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(sl *StringList)</span> <span class="title">Set</span><span class="params">(value <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line"><span class="keyword">for</span> _, s := <span class="keyword">range</span> strings.Split(value, <span class="string">","</span>) &#123;</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(s) == <span class="number">0</span> &#123;</span><br><span class="line"><span class="keyword">return</span> fmt.Errorf(<span class="string">"value should not be an empty string"</span>)</span><br><span class="line">&#125;</span><br><span class="line">*sl = <span class="built_in">append</span>(*sl, s)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到<code>util.StringList</code>用于将以逗号分割的字符串转为<code>[]string</code>类型。各个命令行参数含义如下:</p><ul><li>port: api-server 监听的 port</li><li>address: api-server 监听的 ip</li><li>apiPrefix: 访问 api-server 的 URL 前缀</li><li>etcdServerList: 后端存储的 etcd 节点列表</li><li>machineList: 工作节点的列表</li></ul><p>从 main 函数开始分析 api-server 的具体实现</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> (</span><br><span class="line">    taskRegistry       registry.TaskRegistry</span><br><span class="line">    controllerRegistry registry.ControllerRegistry</span><br><span class="line">    serviceRegistry    registry.ServiceRegistry</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(etcdServerList) &gt; <span class="number">0</span> &#123;</span><br><span class="line">    log.Printf(<span class="string">"Creating etcd client pointing to %v"</span>, etcdServerList)</span><br><span class="line">    etcdClient := etcd.NewClient(etcdServerList)</span><br><span class="line">    taskRegistry = registry.MakeEtcdRegistry(etcdClient, machineList)</span><br><span class="line">    controllerRegistry = registry.MakeEtcdRegistry(etcdClient, machineList)</span><br><span class="line">    serviceRegistry = registry.MakeEtcdRegistry(etcdClient, machineList)</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    taskRegistry = registry.MakeMemoryRegistry()</span><br><span class="line">    controllerRegistry = registry.MakeMemoryRegistry()</span><br><span class="line">    serviceRegistry = registry.MakeMemoryRegistry()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>registry 是对具体资源对象的后端存储的抽象，这里定义了三个 registry，并根据命令行参数判断是使用 etcd 还是内存作为存储后端。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径：pkg/registry/interfaces.go</span></span><br><span class="line"><span class="comment">// TaskRegistry is an interface implemented by things that know how to store Task objects</span></span><br><span class="line"><span class="keyword">type</span> TaskRegistry <span class="keyword">interface</span> &#123;</span><br><span class="line"><span class="comment">// ListTasks obtains a list of tasks that match query.</span></span><br><span class="line"><span class="comment">// Query may be nil in which case all tasks are returned.</span></span><br><span class="line">ListTasks(query *<span class="keyword">map</span>[<span class="keyword">string</span>]<span class="keyword">string</span>) ([]api.Task, error)</span><br><span class="line"><span class="comment">// Get a specific task</span></span><br><span class="line">GetTask(taskId <span class="keyword">string</span>) (*api.Task, error)</span><br><span class="line"><span class="comment">// Create a task based on a specification, schedule it onto a specific machine.</span></span><br><span class="line">CreateTask(machine <span class="keyword">string</span>, task api.Task) error</span><br><span class="line"><span class="comment">// Update an existing task</span></span><br><span class="line">UpdateTask(task api.Task) error</span><br><span class="line"><span class="comment">// Delete an existing task</span></span><br><span class="line">DeleteTask(taskId <span class="keyword">string</span>) error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中<code>taskRegistry</code>是对 task 的存储抽象，task 可以当作 pod 的前身看待，实现了对 task 的 list，get，create, update, delete 的操作。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径：pkg/registry/interfaces.go</span></span><br><span class="line"><span class="comment">// ControllerRegistry is an interface for things that know how to store Controllers</span></span><br><span class="line"><span class="keyword">type</span> ControllerRegistry <span class="keyword">interface</span> &#123;</span><br><span class="line">ListControllers() ([]api.ReplicationController, error)</span><br><span class="line">GetController(controllerId <span class="keyword">string</span>) (*api.ReplicationController, error)</span><br><span class="line">CreateController(controller api.ReplicationController) error</span><br><span class="line">UpdateController(controller api.ReplicationController) error</span><br><span class="line">DeleteController(controllerId <span class="keyword">string</span>) error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而<code>ControllerRegistry</code>是对 RC(Replication Controller) 的存储抽象，而我们现在使用的较多的是 RS(RepicateSet)， RS 正是 RC 的升级，同样是实现了对 RC 的 list，get，create，update，delete 操作。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径：pkg/registry/service_registry.go</span></span><br><span class="line"><span class="keyword">type</span> ServiceRegistry <span class="keyword">interface</span> &#123;</span><br><span class="line">ListServices() (ServiceList, error)</span><br><span class="line">CreateService(svc Service) error</span><br><span class="line">GetService(name <span class="keyword">string</span>) (*Service, error)</span><br><span class="line">DeleteService(name <span class="keyword">string</span>) error</span><br><span class="line">UpdateService(svc Service) error</span><br><span class="line">UpdateEndpoints(e Endpoints) error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ServiceRegistry</code>是对 service 的存储抽象</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">containerInfo := &amp;kube_client.HTTPContainerInfo&#123;</span><br><span class="line">    Client: http.DefaultClient,</span><br><span class="line">    Port:   <span class="number">10250</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">storage := <span class="keyword">map</span>[<span class="keyword">string</span>]apiserver.RESTStorage&#123;</span><br><span class="line">    <span class="string">"tasks"</span>:                  registry.MakeTaskRegistryStorage(taskRegistry, containerInfo, registry.MakeFirstFitScheduler(machineList, taskRegistry)),</span><br><span class="line">    <span class="string">"replicationControllers"</span>: registry.MakeControllerRegistryStorage(controllerRegistry),</span><br><span class="line">    <span class="string">"services"</span>:               registry.MakeServiceRegistryStorage(serviceRegistry),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>storge 是对所有资源的 registry 的统一抽象，被定义为 REST 风格的资源操作接口。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径: pkg/apiserver/api_server.go</span></span><br><span class="line"><span class="comment">// RESTStorage is a generic interface for RESTful storage services</span></span><br><span class="line"><span class="keyword">type</span> RESTStorage <span class="keyword">interface</span> &#123;</span><br><span class="line">List(*url.URL) (<span class="keyword">interface</span>&#123;&#125;, error)</span><br><span class="line">Get(id <span class="keyword">string</span>) (<span class="keyword">interface</span>&#123;&#125;, error)</span><br><span class="line">Delete(id <span class="keyword">string</span>) error</span><br><span class="line">Extract(body <span class="keyword">string</span>) (<span class="keyword">interface</span>&#123;&#125;, error)</span><br><span class="line">Create(<span class="keyword">interface</span>&#123;&#125;) error</span><br><span class="line">Update(<span class="keyword">interface</span>&#123;&#125;) error</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>实例化所有资源的 storage 后放在 map 中维护，用于后面 handler 的处理。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">s := &amp;http.Server&#123;</span><br><span class="line">    Addr:           fmt.Sprintf(<span class="string">"%s:%d"</span>, *address, *port),</span><br><span class="line">    Handler:        apiserver.New(storage, *apiPrefix),  <span class="comment">// 使用 REST storage 创建请求的 handler</span></span><br><span class="line">    ReadTimeout:    <span class="number">10</span> * time.Second,</span><br><span class="line">    WriteTimeout:   <span class="number">10</span> * time.Second,</span><br><span class="line">    MaxHeaderBytes: <span class="number">1</span> &lt;&lt; <span class="number">20</span>,</span><br><span class="line">&#125;</span><br><span class="line">log.Fatal(s.ListenAndServe())</span><br></pre></td></tr></table></figure><p>使用前面的 REST Storage map 和 api prefix 创建 handler，启动 HTTP 服务器等待接收请求。接下来转到 handler 分析源码</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径：pkg/apiserver/api_server.go</span></span><br><span class="line"><span class="comment">// New creates a new ApiServer object.</span></span><br><span class="line"><span class="comment">// 'storage' contains a map of handlers.</span></span><br><span class="line"><span class="comment">// 'prefix' is the hosting path prefix.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">New</span><span class="params">(storage <span class="keyword">map</span>[<span class="keyword">string</span>]RESTStorage, prefix <span class="keyword">string</span>)</span> *<span class="title">ApiServer</span></span> &#123;</span><br><span class="line"><span class="keyword">return</span> &amp;ApiServer&#123;</span><br><span class="line">storage: storage,</span><br><span class="line">prefix:  prefix,</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// HTTP Handler interface</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(server *ApiServer)</span> <span class="title">ServeHTTP</span><span class="params">(w http.ResponseWriter, req *http.Request)</span></span> &#123;</span><br><span class="line">log.Printf(<span class="string">"%s %s"</span>, req.Method, req.RequestURI)</span><br><span class="line">url, err := url.ParseRequestURI(req.RequestURI)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> url.Path == <span class="string">"/index.html"</span> || url.Path == <span class="string">"/"</span> || url.Path == <span class="string">""</span> &#123;</span><br><span class="line">server.handleIndex(w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> !strings.HasPrefix(url.Path, server.prefix) &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">requestParts := strings.Split(url.Path[<span class="built_in">len</span>(server.prefix):], <span class="string">"/"</span>)[<span class="number">1</span>:]</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(requestParts) &lt; <span class="number">1</span> &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">storage := server.storage[requestParts[<span class="number">0</span>]]</span><br><span class="line"><span class="keyword">if</span> storage == <span class="literal">nil</span> &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">server.handleREST(requestParts, url, req, w, storage)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Golang HTTP 的标准库是通过实现 Handler 接口的 ServeHTTP 函数来实现处理请求，通过代码可以看出先对请求的 URL 进行解析获取具体的资源对象，再通过 REST storage map 拿到对应资源对象的 REST storage，最后调用 <code>server.handleREST</code>来处理具体的请求。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径：pkg/apiserver/api_server.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(server *ApiServer)</span> <span class="title">handleREST</span><span class="params">(parts []<span class="keyword">string</span>, url *url.URL, req *http.Request, w http.ResponseWriter, storage RESTStorage)</span></span> &#123;</span><br><span class="line"><span class="keyword">switch</span> req.Method &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="string">"GET"</span>:</span><br><span class="line"><span class="keyword">switch</span> <span class="built_in">len</span>(parts) &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">controllers, err := storage.List(url)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">server.write(<span class="number">200</span>, controllers, w)</span><br><span class="line"><span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">task, err := storage.Get(parts[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> task == <span class="literal">nil</span> &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">server.write(<span class="number">200</span>, task, w)</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">server.notFound(req, w)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">"POST"</span>:</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(parts) != <span class="number">1</span> &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">body, err := server.readBody(req)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">obj, err := storage.Extract(body)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">storage.Create(obj)</span><br><span class="line">server.write(<span class="number">200</span>, obj, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">"DELETE"</span>:</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(parts) != <span class="number">2</span> &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">err := storage.Delete(parts[<span class="number">1</span>])</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">server.write(<span class="number">200</span>, Status&#123;success: <span class="literal">true</span>&#125;, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">case</span> <span class="string">"PUT"</span>:</span><br><span class="line"><span class="keyword">if</span> <span class="built_in">len</span>(parts) != <span class="number">2</span> &#123;</span><br><span class="line">server.notFound(req, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">body, err := server.readBody(req)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line">&#125;</span><br><span class="line">obj, err := storage.Extract(body)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">err = storage.Update(obj)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">server.error(err, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line">server.write(<span class="number">200</span>, obj, w)</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">server.notFound(req, w)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以很清晰的看出，这段逻辑是根据请求方法和请求参数对实际的资源对象进行特定的 REST 的操作。</p><p>回到 main 函数，在启动 HTTP server 之前还启动了一个 goroutine 做定时任务</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">endpoints := registry.MakeEndpointController(serviceRegistry, taskRegistry)</span><br><span class="line"><span class="keyword">go</span> util.Forever(<span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123; endpoints.SyncServiceEndpoints() &#125;, time.Second*<span class="number">10</span>)</span><br></pre></td></tr></table></figure><p>其中 <code>util.Forever</code>就是周期性任务的封装</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径: pkg/util/util.go</span></span><br><span class="line"><span class="comment">// Loops forever running f every d.  Catches any panics, and keeps going.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Forever</span><span class="params">(f <span class="keyword">func</span>()</span>, <span class="title">period</span> <span class="title">time</span>.<span class="title">Duration</span>)</span> &#123;</span><br><span class="line"><span class="keyword">for</span> &#123;</span><br><span class="line"><span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line"><span class="keyword">defer</span> HandleCrash()</span><br><span class="line">f()</span><br><span class="line">&#125;()</span><br><span class="line">time.Sleep(period)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>任务实体<code>endpoints.SyncServiceEndpoints</code>逻辑如下</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 代码路径: pkg/registry/endpoint.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *EndpointController)</span> <span class="title">SyncServiceEndpoints</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line">services, err := e.serviceRegistry.ListServices()</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line"><span class="keyword">return</span> err</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> resultErr error</span><br><span class="line"><span class="keyword">for</span> _, service := <span class="keyword">range</span> services.Items &#123;</span><br><span class="line">tasks, err := e.taskRegistry.ListTasks(&amp;service.Labels)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Printf(<span class="string">"Error syncing service: %#v, skipping."</span>, service)</span><br><span class="line">resultErr = err</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">endpoints := <span class="built_in">make</span>([]<span class="keyword">string</span>, <span class="built_in">len</span>(tasks))</span><br><span class="line"><span class="keyword">for</span> ix, task := <span class="keyword">range</span> tasks &#123;</span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> Use port names in the service object, don't just use port #0</span></span><br><span class="line">endpoints[ix] = fmt.Sprintf(<span class="string">"%s:%d"</span>, task.CurrentState.Host, task.DesiredState.Manifest.Containers[<span class="number">0</span>].Ports[<span class="number">0</span>].HostPort)</span><br><span class="line">&#125;</span><br><span class="line">err = e.serviceRegistry.UpdateEndpoints(Endpoints&#123;</span><br><span class="line">Name:      service.ID,</span><br><span class="line">Endpoints: endpoints,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">log.Printf(<span class="string">"Error updating endpoints: %#v"</span>, err)</span><br><span class="line"><span class="keyword">continue</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> resultErr</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到主要逻辑就是定时获取所有 service 列表，再遍历 service 列表查询 service 下所有 task，最后根据 task 的 endpoint 来更新 service 的 endpoints。这一段逻辑其实就是为 kubeproxy 做负载均衡用的，让 kubeproxy 知道需要代理的 endpoint 有哪些。这一块逻辑在现在的 k8s 架构中已经从 api-server 中移除了。</p><p>笔者只分析了 api-server 主体的逻辑，后续会分析具体 registry 的逻辑。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;穿越回 2014 年，分析下 k8s 第一个提交的源码。&lt;/p&gt;
&lt;h2 id=&quot;获取-first-commit-源码&quot;&gt;&lt;a href=&quot;#获取-first-commit-源码&quot; class=&quot;headerlink&quot; title=&quot;获取 first commit 源码&quot;&gt;&lt;/a&gt;获取 first commit 源码&lt;/h2&gt;&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;kubernetes&amp;#x2F;kubernetes.git&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;cd kubernetes&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;git checkout &amp;#96;git rev-list --max-parents&amp;#x3D;0 HEAD&amp;#96;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h2 id=&quot;简介&quot;&gt;&lt;a href=&quot;#简介&quot; class=&quot;headerlink&quot; title=&quot;简介&quot;&gt;&lt;/a&gt;简介&lt;/h2&gt;&lt;p&gt;api-server 是 k8s 的核心组件之一，用于接收 kubelet 的请求，并将请求信息保存到后端存储 etcd 中。核心功能是提供 k8s 各类资源对象的 CURD 等操作。&lt;/p&gt;
&lt;h2 id=&quot;源码分析&quot;&gt;&lt;a href=&quot;#源码分析&quot; class=&quot;headerlink&quot; title=&quot;源码分析&quot;&gt;&lt;/a&gt;源码分析&lt;/h2&gt;&lt;p&gt;从 api-server 的命令行入口开始分析，命令行代码位于 &lt;code&gt;cmd/apiserver/apiserver.go&lt;/code&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="k8s" scheme="https://www.caiyifan.cn/tags/k8s/"/>
    
      <category term="golang" scheme="https://www.caiyifan.cn/tags/golang/"/>
    
  </entry>
  
  <entry>
    <title>Linux驱动之网卡驱动剖析</title>
    <link href="https://www.caiyifan.cn/p/46fde57.html"/>
    <id>https://www.caiyifan.cn/p/46fde57.html</id>
    <published>2021-10-11T14:52:05.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<p>网络设备不同于字符设备和块设备，并不对应于<code>/dev</code>目录下的文件，应用程序通过 socket 完成与网络设备的交互，在网络设备上并不体现”一切皆文件”的设计思想。</p><h2 id="Linux-网络设备驱动架构"><a href="#Linux-网络设备驱动架构" class="headerlink" title="Linux 网络设备驱动架构"></a>Linux 网络设备驱动架构</h2><p>驱动架构自上而下分为4层：</p><ul><li>协议接口层</li><li>设备接口层</li><li>设备驱动功能层</li><li>网络设备与媒介层</li></ul><a id="more"></a><p><img src="https://img.caiyifan.cn/DM9000/driver_structure.png"></p><h3 id="协议接口层"><a href="#协议接口层" class="headerlink" title="协议接口层"></a>协议接口层</h3><p>协议接口层主要功能是给上层协议提供接收和发送的接口。当内核协议栈需要发送数据时，会通过调用 <code>dev_queue_xmit</code> 函数来发送数据。同样内核协议栈接收数据也是通过协议接口层的 <code>netif_rx</code> 函数来进行的。传递的数据被描述为套接字缓冲区，用<code>struct sk_buff</code>结构描述，该结构体定义位于<code>include/linux/skbuff.h</code>中，用于在Linux网络子系统中的各层之间传输数据，该结构在整个网络收发过程中贯穿始终。</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211011175619718.png" alt="image-20211011175619718"></p><p>sk buffer 结构可以分为两部分，一部分是存储真正的数据包，在图中为 Packetdata，另一部分是一组指针组成。</p><ul><li>head 指向内核缓冲区(Packetdata)的头部(headroom)</li><li>data 指向的是实际数据包的头部</li><li>tail 指向的是实际数据包的尾部</li><li>end 指向内核缓冲区的尾部</li></ul><h3 id="设备接口层"><a href="#设备接口层" class="headerlink" title="设备接口层"></a>设备接口层</h3><p>网络设备接口层用于抽象各种不同的网络设备，用 <code>struct net_device</code>来表示网络设备，该结构地位等同于字符设备的抽象描述<code>struct cdev</code>。</p><h3 id="设备驱动功能层"><a href="#设备驱动功能层" class="headerlink" title="设备驱动功能层"></a>设备驱动功能层</h3><p>类似于字符设备，<code>struct net_device</code>结构体也提供了一个操作函数集<code>struct net_device_ops</code>来描述对网卡的各种操作。</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>笔者基于的是 S5PV210 的 DM9000 驱动，会大体上对 DM9000 的驱动源码进行分析, 分析源码位于<a href="https://github.com/IfanTsai/x210_kernel/blob/master/drivers/net/dm9000.c" target="_blank" rel="external nofollow noopener noreferrer">DM9000 源码</a></p><h3 id="platform-框架分析"><a href="#platform-框架分析" class="headerlink" title="platform 框架分析"></a>platform 框架分析</h3><p>DM9000 的驱动是基于 platform 架构实现，首先从 platform 框架入手。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">platform_driver</span> <span class="title">dm9000_driver</span> = &#123;</span></span><br><span class="line">    .driver    = &#123;</span><br><span class="line">        .name    = <span class="string">"dm9000"</span>,</span><br><span class="line">        .owner     = THIS_MODULE,</span><br><span class="line">        .pm     = &amp;dm9000_drv_pm_ops,</span><br><span class="line">    &#125;,</span><br><span class="line">    .probe   = dm9000_probe,</span><br><span class="line">    .<span class="built_in">remove</span>  = __devexit_p(dm9000_drv_remove),</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">dm9000_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">/* disable buzzer */</span></span><br><span class="line">    s3c_gpio_setpull(S5PV210_GPD0(<span class="number">2</span>), S3C_GPIO_PULL_UP);</span><br><span class="line">    s3c_gpio_cfgpin(S5PV210_GPD0(<span class="number">2</span>), S3C_GPIO_SFN(<span class="number">1</span>));</span><br><span class="line">    gpio_set_value(S5PV210_GPD0(<span class="number">2</span>), <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    dm9000_power_int();</span><br><span class="line">    printk(KERN_INFO <span class="string">"%s Ethernet Driver, V%s\n"</span>, CARDNAME, DRV_VERSION);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> platform_driver_register(&amp;dm9000_driver);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该函数调用了 <code>platform_driver_register</code> 函数注册了一个平台总线驱动，对应的平台设备的注册定义位于 <code>xxx_machine_init</code>中，在笔者基于的<code>s5pv210</code> kernel 上位于<code>arch/arm/mach-s5pv210/mach-x210.c</code>中的<code>smdkc110_machine_init</code>中，具体的分析过程省略，笔者直接列出对应的平台总线设备。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* DM9000 registrations */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">resource</span> <span class="title">s5p_dm9000_resources</span>[] = &#123;</span></span><br><span class="line">    [<span class="number">0</span>] = &#123;</span><br><span class="line">        .start = S5P_PA_DM9000,</span><br><span class="line">        .<span class="built_in">end</span>   = S5P_PA_DM9000 + <span class="number">3</span>,</span><br><span class="line">        .flags = IORESOURCE_MEM,    <span class="comment">// 内存资源 (DM900 地址端口)</span></span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="number">1</span>] = &#123;</span><br><span class="line">        .start = S5P_PA_DM9000 + <span class="number">4</span>,</span><br><span class="line">        .<span class="built_in">end</span>   = S5P_PA_DM9000 + <span class="number">7</span>,</span><br><span class="line">        .flags = IORESOURCE_MEM,      <span class="comment">// 内存资源  (DM900 数据端口)</span></span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="number">2</span>] = &#123;</span><br><span class="line">        .start = IRQ_EINT10,</span><br><span class="line">        .<span class="built_in">end</span>   = IRQ_EINT10,</span><br><span class="line">        .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL, <span class="comment">// 中断资源 （高电平触发）</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">dm9000_plat_data</span> <span class="title">s5p_dm9000_platdata</span> = &#123;</span></span><br><span class="line">    .flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,</span><br><span class="line">    .dev_addr = &#123;<span class="number">0x00</span>,<span class="number">0x09</span>,<span class="number">0xc0</span>,<span class="number">0xff</span>,<span class="number">0xec</span>,<span class="number">0x48</span>&#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">platform_device</span> <span class="title">s5p_device_dm9000</span> = &#123;</span></span><br><span class="line">    .name      = <span class="string">"dm9000"</span>,</span><br><span class="line">    .id        =  <span class="number">0</span>,</span><br><span class="line">    .num_resources    = ARRAY_SIZE(s5p_dm9000_resources),</span><br><span class="line">    .resource   = s5p_dm9000_resources,</span><br><span class="line">    .dev        = &#123;</span><br><span class="line">        .platform_data = &amp;s5p_dm9000_platdata,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>根据平台总线的原理，驱动和设备匹配上后，会调用驱动的 probe 函数 <code>dm9000_probe</code>，分段进行分析</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">dm9000_plat_data</span> *<span class="title">pdata</span> = <span class="title">pdev</span>-&gt;<span class="title">dev</span>.<span class="title">platform_data</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">board_info</span> *<span class="title">db</span>;</span>    <span class="comment">/* Point a board information structure */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">net_device</span> *<span class="title">ndev</span>;</span>   <span class="comment">/* struct net_device 为网络设备的抽象 */</span></span><br><span class="line"><span class="keyword">const</span> <span class="keyword">unsigned</span> <span class="keyword">char</span> *mac_src;</span><br><span class="line"><span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">int</span> iosize;</span><br><span class="line"><span class="keyword">int</span> i;</span><br><span class="line">u32 id_val;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Init network device */</span></span><br><span class="line">ndev = alloc_etherdev(<span class="keyword">sizeof</span>(struct board_info)); <span class="comment">/* 同时为 ndev 和 db 申请内存, db 内存位于 ndev 后面 */</span></span><br><span class="line"><span class="keyword">if</span> (!ndev) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"could not allocate device.\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> -ENOMEM;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">SET_NETDEV_DEV(ndev, &amp;pdev-&gt;dev);</span><br><span class="line"></span><br><span class="line">dev_dbg(&amp;pdev-&gt;dev, <span class="string">"dm9000_probe()\n"</span>);dm9000_opendm9000_open</span><br><span class="line"></span><br><span class="line"><span class="comment">/* setup board info structure */</span></span><br><span class="line">db = netdev_priv(ndev);</span><br><span class="line"></span><br><span class="line">db-&gt;dev = &amp;pdev-&gt;dev;</span><br><span class="line">db-&gt;ndev = ndev;</span><br><span class="line"></span><br><span class="line">spin_lock_init(&amp;db-&gt;lock);</span><br><span class="line">mutex_init(&amp;db-&gt;addr_lock);</span><br><span class="line"></span><br><span class="line">INIT_DELAYED_WORK(&amp;db-&gt;phy_poll, dm9000_poll_work);</span><br></pre></td></tr></table></figure><p>该部分为 <code>struct net_device</code> 和 <code>struct board_info</code> 结构体申请内存，<code>struct board_info</code>定义在 DM9000 的驱动文件中，表示设备的私有数据，随后对各个指针做了挂接，并初始化了一部分 <code>struct board_info</code> 中的成员。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">   db-&gt;addr_res = platform_get_resource(pdev, IORESOURCE_MEM, <span class="number">0</span>); <span class="comment">/* dm9000 地址端口 */</span></span><br><span class="line">   db-&gt;data_res = platform_get_resource(pdev, IORESOURCE_MEM, <span class="number">1</span>); <span class="comment">/* dm9000 数据端口 */</span></span><br><span class="line">   db-&gt;irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, <span class="number">0</span>); <span class="comment">/* dm9000 irq 号 */</span></span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (db-&gt;addr_res == <span class="literal">NULL</span> || db-&gt;data_res == <span class="literal">NULL</span> ||</span><br><span class="line">       db-&gt;irq_res == <span class="literal">NULL</span>) &#123;</span><br><span class="line">       dev_err(db-&gt;dev, <span class="string">"insufficient resources\n"</span>);</span><br><span class="line">       ret = -ENOENT;</span><br><span class="line">       <span class="keyword">goto</span> out;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 第二个参数为 1 表示获取的是第二个中断资源。</span></span><br><span class="line"><span class="comment"> * 由于只定义了一个中断, 所以返回 -ENXIO</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">   db-&gt;irq_wake = platform_get_irq(pdev, <span class="number">1</span>);</span><br><span class="line">   <span class="keyword">if</span> (db-&gt;irq_wake &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">   <span class="comment">/* 这一段代码并不会执行, 省略 */</span></span><br><span class="line">       <span class="comment">// ...</span></span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   iosize = resource_size(db-&gt;addr_res); <span class="comment">// res-&gt;end - res-&gt;start + 1 = 4</span></span><br><span class="line">   <span class="comment">/* 申请地址端口内存 */</span></span><br><span class="line">   db-&gt;addr_req = request_mem_region(db-&gt;addr_res-&gt;start, iosize,</span><br><span class="line">                     pdev-&gt;name);</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (db-&gt;addr_req == <span class="literal">NULL</span>) &#123;</span><br><span class="line">       dev_err(db-&gt;dev, <span class="string">"cannot claim address reg area\n"</span>);</span><br><span class="line">       ret = -EIO;</span><br><span class="line">       <span class="keyword">goto</span> out;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 映射地址端口虚拟地址 */</span></span><br><span class="line">   db-&gt;io_addr = ioremap(db-&gt;addr_res-&gt;start, iosize);</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (db-&gt;io_addr == <span class="literal">NULL</span>) &#123;</span><br><span class="line">       dev_err(db-&gt;dev, <span class="string">"failed to ioremap address reg\n"</span>);</span><br><span class="line">       ret = -EINVAL;</span><br><span class="line">       <span class="keyword">goto</span> out;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   iosize = resource_size(db-&gt;data_res);</span><br><span class="line">   <span class="comment">/* 申请数据端口内存 */</span></span><br><span class="line">   db-&gt;data_req = request_mem_region(db-&gt;data_res-&gt;start, iosize,</span><br><span class="line">                     pdev-&gt;name);</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (db-&gt;data_req == <span class="literal">NULL</span>) &#123;</span><br><span class="line">       dev_err(db-&gt;dev, <span class="string">"cannot claim data reg area\n"</span>);</span><br><span class="line">       ret = -EIO;</span><br><span class="line">       <span class="keyword">goto</span> out;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* 映射数据端口虚拟地址 */</span></span><br><span class="line">   db-&gt;io_data = ioremap(db-&gt;data_res-&gt;start, iosize);</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (db-&gt;io_data == <span class="literal">NULL</span>) &#123;</span><br><span class="line">       dev_err(db-&gt;dev, <span class="string">"failed to ioremap data reg\n"</span>);</span><br><span class="line">       ret = -EINVAL;</span><br><span class="line">       <span class="keyword">goto</span> out;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">/* fill in parameters for net-dev structure */</span></span><br><span class="line">   ndev-&gt;base_addr = (<span class="keyword">unsigned</span> <span class="keyword">long</span>)db-&gt;io_addr;</span><br><span class="line">   ndev-&gt;irq       = db-&gt;irq_res-&gt;start;</span><br></pre></td></tr></table></figure><p>以上代码从<code>platform_device</code>中获取 DM9000 资源: 地址端口、数据端口地址和中断号, 并为端口地址 <code>ioremap</code>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">    <span class="comment">/* ensure at least we have a default set of IO routines */</span></span><br><span class="line">    dm9000_set_io(db, iosize); <span class="comment">/* 在下面 if 判断中还会设置一次, 所以这里设置无效 */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* check to see if anything is being over-ridden */</span></span><br><span class="line">    <span class="keyword">if</span> (pdata != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="comment">/* check to see if the driver wants to over-ride the</span></span><br><span class="line"><span class="comment">         * default IO width */</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pdata-&gt;flags &amp; DM9000_PLATF_8BITONLY)</span><br><span class="line">            dm9000_set_io(db, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pdata-&gt;flags &amp; DM9000_PLATF_16BITONLY)  <span class="comment">/* 只有这个 if 成立 */</span></span><br><span class="line">            dm9000_set_io(db, <span class="number">2</span>);  <span class="comment">/* 设置 board_info 的读写函数 */</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pdata-&gt;flags &amp; DM9000_PLATF_32BITONLY)</span><br><span class="line">            dm9000_set_io(db, <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* check to see if there are any IO routine</span></span><br><span class="line"><span class="comment">         * over-rides */</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pdata-&gt;inblk != <span class="literal">NULL</span>)</span><br><span class="line">            db-&gt;inblk = pdata-&gt;inblk;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pdata-&gt;outblk != <span class="literal">NULL</span>)</span><br><span class="line">            db-&gt;outblk = pdata-&gt;outblk;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pdata-&gt;dumpblk != <span class="literal">NULL</span>)</span><br><span class="line">            db-&gt;dumpblk = pdata-&gt;dumpblk;</span><br><span class="line"></span><br><span class="line">        db-&gt;flags = pdata-&gt;flags;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL</span></span><br><span class="line">    db-&gt;flags |= DM9000_PLATF_SIMPLE_PHY;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">    dm9000_reset(db);    <span class="comment">/* 重启 dm9000 */</span></span><br></pre></td></tr></table></figure><p>根据平台设备的平台数据，DM9000 配置在了 16bit 的模式下，所以这一部分设置只有<code>dm9000_set_io(db, 2);</code>是成功的。 <code>dm9000_set_io</code> 函数用于设置 DM9000 的读写函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">dm9000_set_io</span><span class="params">(struct board_info *db, <span class="keyword">int</span> byte_width)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="comment">/* use the size of the data resource to work out what IO</span></span><br><span class="line"><span class="comment"> * routines we want to use</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> (byte_width) &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">db-&gt;dumpblk = dm9000_dumpblk_8bit;</span><br><span class="line">db-&gt;outblk  = dm9000_outblk_8bit;</span><br><span class="line">db-&gt;inblk   = dm9000_inblk_8bit;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">": 3 byte IO, falling back to 16bit\n"</span>);</span><br><span class="line"><span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">db-&gt;dumpblk = dm9000_dumpblk_16bit;</span><br><span class="line">db-&gt;outblk  = dm9000_outblk_16bit;</span><br><span class="line">db-&gt;inblk   = dm9000_inblk_16bit;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">db-&gt;dumpblk = dm9000_dumpblk_32bit;</span><br><span class="line">db-&gt;outblk  = dm9000_outblk_32bit;</span><br><span class="line">db-&gt;inblk   = dm9000_inblk_32bit;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>设置完读写函数后，软件重启 DM9000。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">dm9000_reset</span><span class="params">(<span class="keyword">board_info_t</span> * db)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"resetting device\n"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* RESET device */</span></span><br><span class="line">writeb(DM9000_NCR, db-&gt;io_addr); <span class="comment">//  DM9000_NCR: 0x00</span></span><br><span class="line">udelay(<span class="number">200</span>);</span><br><span class="line">writeb(NCR_RST, db-&gt;io_data);    <span class="comment">// NCR_RST: 1 &lt;&lt; 0</span></span><br><span class="line">udelay(<span class="number">200</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>DM9000 通过端口来操作寄存器, 先将寄存器的偏移值或命令码写入地址端口, 再将值写入数据端口。重启 DM900 只需往地址为 0 的端口写入 1。</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010210447443.png" alt="image-20211010210447443"></p><p>重启完 DM9000 后，开始读取 DM9000 的寄存器</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* try multiple times, DM9000 sometimes gets the read wrong */</span></span><br><span class="line"><span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">8</span>; i++) &#123;</span><br><span class="line">    id_val  = ior(db, DM9000_VIDL);             <span class="comment">/* DM9000_VIDL：0x28, 读取 vendor id */</span></span><br><span class="line">    id_val |= (u32)ior(db, DM9000_VIDH) &lt;&lt; <span class="number">8</span>;   <span class="comment">/* DM9000_VIDH: 0x29 */</span></span><br><span class="line">    id_val |= (u32)ior(db, DM9000_PIDL) &lt;&lt; <span class="number">16</span>;  <span class="comment">/* DM9000_PIDL: 0x2A, 读取 product id */</span></span><br><span class="line">    id_val |= (u32)ior(db, DM9000_PIDH) &lt;&lt; <span class="number">24</span>;  <span class="comment">/* DM9000_PIDH: 0x2B */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (id_val == DM9000_ID)   <span class="comment">/* 验证是否是 DM900 */</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    dev_err(db-&gt;dev, <span class="string">"read wrong id 0x%08x\n"</span>, id_val);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (id_val != DM9000_ID) &#123;</span><br><span class="line">    dev_err(db-&gt;dev, <span class="string">"wrong id: 0x%08x\n"</span>, id_val);</span><br><span class="line">    ret = -ENODEV;</span><br><span class="line">    <span class="keyword">goto</span> out;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Identify what type of DM9000 we are working on */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* I/O mode */</span></span><br><span class="line">db-&gt;io_mode = ior(db, DM9000_ISR) &gt;&gt; <span class="number">6</span>;    <span class="comment">/* ISR bit7:6 keeps I/O mode */</span> <span class="comment">// 读取 I/O mode</span></span><br><span class="line">id_val = ior(db, DM9000_CHIPR);  <span class="comment">/* DM9000_CHIPR: 0x2C, 读取 chip revision */</span></span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"dm9000 revision 0x%02x  , io_mode %02x \n"</span>, id_val, db-&gt;io_mode);</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> (id_val) &#123;</span><br><span class="line"><span class="keyword">case</span> CHIPR_DM9000A:</span><br><span class="line">    db-&gt;type = TYPE_DM9000A;</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> <span class="number">0x1a</span>:</span><br><span class="line">    db-&gt;type = TYPE_DM9000C;</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">    dev_dbg(db-&gt;dev, <span class="string">"ID %02x =&gt; defaulting to DM9000E\n"</span>, id_val);</span><br><span class="line">    db-&gt;type = TYPE_DM9000E;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010211748560.png" alt="image-20211010211748560"></p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010214238644.png" alt="image-20211010214238644"></p><p>读取 vendor id 和 product id 验证是否是 DM9000。再读取 I&#x2F;O mode 和 chip revision, 并根据不同 revision 对<code>db-&gt;type</code>进行赋值。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* driver system function */</span></span><br><span class="line">ether_setup(ndev);</span><br><span class="line"></span><br><span class="line">ndev-&gt;netdev_ops    = &amp;dm9000_netdev_ops;     <span class="comment">// net device 的 ops</span></span><br><span class="line">ndev-&gt;watchdog_timeo    = msecs_to_jiffies(watchdog);</span><br><span class="line">ndev-&gt;ethtool_ops    = &amp;dm9000_ethtool_ops;   <span class="comment">// ethtool 的 ops, 用于支持应用层的 ethtool 命令</span></span><br><span class="line"></span><br><span class="line">db-&gt;msg_enable       = NETIF_MSG_LINK;</span><br><span class="line">db-&gt;mii.phy_id_mask  = <span class="number">0x1f</span>;</span><br><span class="line">db-&gt;mii.reg_num_mask = <span class="number">0x1f</span>;</span><br><span class="line">db-&gt;mii.force_media  = <span class="number">0</span>;</span><br><span class="line">db-&gt;mii.full_duplex  = <span class="number">0</span>;</span><br><span class="line">db-&gt;mii.dev         = ndev;</span><br><span class="line">db-&gt;mii.mdio_read    = dm9000_phy_read;</span><br><span class="line">db-&gt;mii.mdio_write   = dm9000_phy_write;</span><br><span class="line"></span><br><span class="line">mac_src = <span class="string">"eeprom"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* try reading the node address from the attached EEPROM */</span></span><br><span class="line"><span class="comment">/* platdata 设置了 DM9000_PLATF_NO_EEPROM flag, 所以这个读取无效 */</span></span><br><span class="line"><span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">6</span>; i += <span class="number">2</span>)</span><br><span class="line">    dm9000_read_eeprom(db, i / <span class="number">2</span>, ndev-&gt;dev_addr+i);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!is_valid_ether_addr(ndev-&gt;dev_addr) &amp;&amp; pdata != <span class="literal">NULL</span>) &#123;</span><br><span class="line">    mac_src = <span class="string">"platform data"</span>;</span><br><span class="line">    <span class="comment">//memcpy(ndev-&gt;dev_addr, pdata-&gt;dev_addr, 6);</span></span><br><span class="line">    <span class="comment">/* mac from bootloader */</span></span><br><span class="line">    <span class="built_in">memcpy</span>(ndev-&gt;dev_addr, mac, <span class="number">6</span>);  <span class="comment">/* 这是真正的设置 mac 地址, 其他设置均无效 */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!is_valid_ether_addr(ndev-&gt;dev_addr)) &#123;</span><br><span class="line">    <span class="comment">/* try reading from mac */</span></span><br><span class="line"></span><br><span class="line">    mac_src = <span class="string">"chip"</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">6</span>; i++)</span><br><span class="line">        ndev-&gt;dev_addr[i] = ior(db, i+DM9000_PAR);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!is_valid_ether_addr(ndev-&gt;dev_addr))</span><br><span class="line">    dev_warn(db-&gt;dev, <span class="string">"%s: Invalid ethernet MAC address. Please "</span></span><br><span class="line">         <span class="string">"set using ifconfig\n"</span>, ndev-&gt;name);</span><br><span class="line"></span><br><span class="line">platform_set_drvdata(pdev, ndev);</span><br><span class="line">ret = register_netdev(ndev);   <span class="comment">// 注册网络设备</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (ret == <span class="number">0</span>)</span><br><span class="line">    printk(KERN_INFO <span class="string">"%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n"</span>,</span><br><span class="line">           ndev-&gt;name, dm9000_type_to_char(db-&gt;type),</span><br><span class="line">           db-&gt;io_addr, db-&gt;io_data, ndev-&gt;irq,</span><br><span class="line">           ndev-&gt;dev_addr, mac_src);</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p>调用<code>ether_setup</code>函数对<code>ndev</code>成员进行初始化。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">ether_setup</span><span class="params">(struct net_device *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    dev-&gt;header_ops   = &amp;eth_header_ops; <span class="comment">/* 硬件头部操作函数集，主要完成创建硬件头和从 sk_buf 分析硬件头等操作 */</span></span><br><span class="line">    dev-&gt;type         = ARPHRD_ETHER;    <span class="comment">// 设置以太网协议</span></span><br><span class="line">    dev-&gt;hard_header_len     = ETH_HLEN; <span class="comment">// 以太网头部大小   14B</span></span><br><span class="line">    dev-&gt;mtu          = ETH_DATA_LEN;    <span class="comment">// 设置以太网 MTU  1500B</span></span><br><span class="line">    dev-&gt;addr_len     = ETH_ALEN;        <span class="comment">// mac 地址长度    6B</span></span><br><span class="line">    dev-&gt;tx_queue_len = <span class="number">1000</span>;    <span class="comment">/* Ethernet wants good queues */</span></span><br><span class="line">    dev-&gt;flags        = IFF_BROADCAST|IFF_MULTICAST;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">memset</span>(dev-&gt;broadcast, <span class="number">0xFF</span>, ETH_ALEN);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>初始化完<code>ndev</code>后，设置了<code>netdev_ops</code> 和 mac 地址，最后调用<code>register_netdev</code>函数注册了网络设备。至此，probe 函数分析完毕，紧接着把关注点放在<code>netdev_ops</code>上。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">net_device_ops</span> <span class="title">dm9000_netdev_ops</span> = &#123;</span></span><br><span class="line">.ndo_open= dm9000_open,              <span class="comment">/* ifconfig eth0 up */</span></span><br><span class="line">.ndo_stop= dm9000_stop,              <span class="comment">/* ifconfig eth0 down */</span></span><br><span class="line">.ndo_start_xmit= dm9000_start_xmit,    <span class="comment">/* 数据包发送时由网络协议栈调用 */</span></span><br><span class="line">.ndo_tx_timeout= dm9000_timeout,       <span class="comment">/* 数据包发送超时后会被调用　*/</span></span><br><span class="line">.ndo_set_multicast_list= dm9000_hash_table,</span><br><span class="line">.ndo_do_ioctl= dm9000_ioctl,</span><br><span class="line">.ndo_change_mtu= eth_change_mtu,</span><br><span class="line">.ndo_validate_addr= eth_validate_addr,</span><br><span class="line">.ndo_set_mac_address= eth_mac_addr,</span><br><span class="line">#ifdef CONFIG_NET_POLL_CONTROLLER</span><br><span class="line">.ndo_poll_controller= dm9000_poll_controller,</span><br><span class="line">#endif</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="dm9000-open-过程分析"><a href="#dm9000-open-过程分析" class="headerlink" title="dm9000 open 过程分析"></a>dm9000 open 过程分析</h3><p>当用户执行命令<code>ifconfig eth0 up</code>后会调用网卡驱动的 open 函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> *  Open the interface.</span></span><br><span class="line"><span class="comment"> *  The interface is opened whenever "ifconfig" actives it.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">dm9000_open</span><span class="params">(struct net_device *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">board_info_t</span> *db = netdev_priv(dev);</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> irqflags = db-&gt;irq_res-&gt;flags &amp; IRQF_TRIGGER_MASK;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (netif_msg_ifup(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"enabling %s\n"</span>, dev-&gt;name);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* If there is no IRQ type specified, default to something that</span></span><br><span class="line"><span class="comment"> * may work, and tell the user that this is a problem */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (irqflags == IRQF_TRIGGER_NONE)</span><br><span class="line">dev_warn(db-&gt;dev, <span class="string">"WARNING: no IRQ resource flags set.\n"</span>);</span><br><span class="line"></span><br><span class="line">irqflags |= IRQF_SHARED;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 申请收发中断 */</span></span><br><span class="line"><span class="keyword">if</span> (request_irq(dev-&gt;irq, dm9000_interrupt, irqflags, dev-&gt;name, dev))</span><br><span class="line"><span class="keyword">return</span> -EAGAIN;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Initialize DM9000 board */</span></span><br><span class="line"><span class="comment">//dm9000_reset(db);</span></span><br><span class="line">dm9000_init_dm9000(dev);   <span class="comment">/* 初始化 DM9000 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Init driver variable */</span></span><br><span class="line">db-&gt;dbug_cnt = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">mii_check_media(&amp;db-&gt;mii, netif_msg_link(db), <span class="number">1</span>);</span><br><span class="line">netif_start_queue(dev);   <span class="comment">/* 激活设备发送队列，允许上层调用 xxx_xmit 函数 */</span></span><br><span class="line"></span><br><span class="line">dm9000_schedule_poll(db);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>open 函数主要做了申请收发中断、初始化 DM9000、激活设备发送队列。其中 DM900 的初始化全是对硬件寄存器的操作，在此省略。</p><h3 id="DM9000-发送过程分析"><a href="#DM9000-发送过程分析" class="headerlink" title="DM9000 发送过程分析"></a>DM9000 发送过程分析</h3><p>应用程序调用<code>send</code>函数去发送数据，内核协议栈会将数据构造成<code>struct sk_buff</code>后放入等待队列，调用<code>start_xmit</code>通知网卡发送数据。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">dm9000_start_xmit</span><span class="params">(struct sk_buff *skb, struct net_device *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line"><span class="keyword">board_info_t</span> *db = netdev_priv(dev);</span><br><span class="line"></span><br><span class="line">dm9000_dbg(db, <span class="number">3</span>, <span class="string">"%s:\n"</span>, __func__);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> ((db-&gt;tx_pkt_cnt &gt; <span class="number">0</span>) &amp;&amp; !netif_carrier_ok(dev))</span><br><span class="line"><span class="keyword">return</span> NETDEV_TX_BUSY;</span><br><span class="line"></span><br><span class="line">spin_lock_irqsave(&amp;db-&gt;lock, flags);</span><br><span class="line"></span><br><span class="line">netif_stop_queue(dev);  <span class="comment">/* 关闭发送队列，通知协议接口层停止向下递交数据包 */</span></span><br><span class="line"></span><br><span class="line">db-&gt;tx_pkt_cnt++;</span><br><span class="line">dev-&gt;stats.tx_packets++;</span><br><span class="line">dev-&gt;stats.tx_bytes += skb-&gt;len;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Set TX length to DM9000 */</span>       <span class="comment">// 设置数据包总长度</span></span><br><span class="line">iow(db, DM9000_TXPLL, skb-&gt;len);    <span class="comment">// DM9000_TXPLL: 0xFC</span></span><br><span class="line">iow(db, DM9000_TXPLH, skb-&gt;len &gt;&gt; <span class="number">8</span>); <span class="comment">// DM9000_TXPLH: 0xFD</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Move data to DM9000 TX RAM */</span>   <span class="comment">/* 将数据包放入 TX SRAM 中 */</span></span><br><span class="line">writeb(DM9000_MWCMD, db-&gt;io_addr);   <span class="comment">// DM9000_MWCMD: 0xF8</span></span><br><span class="line">(db-&gt;outblk)(db-&gt;io_data, skb-&gt;data, skb-&gt;len);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Issue TX polling command */</span>  <span class="comment">/* 开始将 TX SRAM 中的数据发送出去, 发送完毕会通过中断告知 */</span></span><br><span class="line">iow(db, DM9000_TCR, TCR_TXREQ);<span class="comment">/* Cleared after TX complete */</span> <span class="comment">// DM9000_TCR: 0x02, TCR_TXREQ: 1 &lt;&lt; 0</span></span><br><span class="line">dev-&gt;trans_start = jiffies;</span><br><span class="line"></span><br><span class="line">spin_unlock_irqrestore(&amp;db-&gt;lock, flags);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* free this SKB */</span></span><br><span class="line">dev_kfree_skb(skb);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> NETDEV_TX_OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010223228911.png" alt="image-20211010223228911"></p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010222652092.png" alt="image-20211010222652092"></p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010222923468.png" alt="image-20211010222923468"></p><p>由以上代码可知，先关闭发送队列，通知协议接口层停止向下递交数据包, 然后设置数据包的总长度后将数据包拷贝进 DM9000 的 TX SRAM 中，再然后置位 TCR 寄存器后网卡开始发送数据，该标志位会在发送完毕后硬件自动清 0, 最后由中断通知 CPU 数据发送完毕</p><p>在 open 函数中申请过 DM9000 的硬件中断，该中断在发送和接收完毕都会触发，在这先只关注中断处理函数的发送完毕过程</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">irqreturn_t</span> <span class="title">dm9000_interrupt</span><span class="params">(<span class="keyword">int</span> irq, <span class="keyword">void</span> *dev_id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">net_device</span> *<span class="title">dev</span> = <span class="title">dev_id</span>;</span></span><br><span class="line"><span class="keyword">board_info_t</span> *db = netdev_priv(dev);</span><br><span class="line"><span class="keyword">int</span> int_status;</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line">u8 reg_save;</span><br><span class="line"></span><br><span class="line">dm9000_dbg(db, <span class="number">3</span>, <span class="string">"entering %s\n"</span>, __func__);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* A real interrupt coming */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* holders of db-&gt;lock must always block IRQs */</span></span><br><span class="line">spin_lock_irqsave(&amp;db-&gt;lock, flags);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Save previous register address */</span></span><br><span class="line">reg_save = readb(db-&gt;io_addr);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Disable all interrupts */</span></span><br><span class="line">iow(db, DM9000_IMR, IMR_PAR);   <span class="comment">// 先 disable 掉所有中断</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Got DM9000 interrupt status */</span></span><br><span class="line">int_status = ior(db, DM9000_ISR);<span class="comment">/* Got ISR */</span>  <span class="comment">/* 获取中断状态, 是接收中断还是发送中断 */</span></span><br><span class="line">iow(db, DM9000_ISR, int_status);<span class="comment">/* Clear ISR status */</span>  <span class="comment">/* 清中断 */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (netif_msg_intr(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"interrupt status %02x\n"</span>, int_status);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Received the coming packet */</span></span><br><span class="line"><span class="keyword">if</span> (int_status &amp; ISR_PRS)   <span class="comment">/* ISR_PRS: 1 &lt;&lt; 0, 接收中断 */</span></span><br><span class="line">dm9000_rx(dev);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Got DM9000 interrupt status */</span></span><br><span class="line">int_status |= ior(db, DM9000_ISR);<span class="comment">/* Got ISR */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* Trnasmit Interrupt check */</span></span><br><span class="line"><span class="keyword">if</span> (int_status &amp; ISR_PTS)   <span class="comment">/* ISR_PTS: 1 &lt;&lt; 1, 发送中断 */</span></span><br><span class="line">&#123;</span><br><span class="line">iow(db, DM9000_ISR, ISR_PTS);<span class="comment">/* Clear ISR status */</span></span><br><span class="line">dm9000_tx_done(dev, db);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (db-&gt;type != TYPE_DM9000E) &#123;</span><br><span class="line"><span class="keyword">if</span> (int_status &amp; ISR_LNKCHNG) &#123;</span><br><span class="line"><span class="comment">/* fire a link-change request */</span></span><br><span class="line">schedule_delayed_work(&amp;db-&gt;phy_poll, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Re-enable interrupt mask */</span></span><br><span class="line">iow(db, DM9000_IMR, db-&gt;imr_all);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Restore previous register address */</span></span><br><span class="line">writeb(reg_save, db-&gt;io_addr);</span><br><span class="line"></span><br><span class="line">spin_unlock_irqrestore(&amp;db-&gt;lock, flags);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> IRQ_HANDLED;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先禁用所有中断，然后通过读取 ISR 寄存器获取中断状态</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010224312491.png" alt="image-20211010224312491"></p><p>由 bit 0 和 1 可判断是接收中断还是发送中断， 如果是发送中断，则清中断后调用<code>dm9000_tx_done</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">dm9000_tx_done</span><span class="params">(struct net_device *dev, <span class="keyword">board_info_t</span> *db)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">int</span> tx_status = ior(db, DM9000_TCR);<span class="comment">/* Got TX status */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (tx_status &amp; TCR_TXREQ) &#123;</span><br><span class="line">dev-&gt;stats.tx_fifo_errors++;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (db-&gt;tx_pkt_cnt &amp;&amp; !db-&gt;wait_reset) &#123;</span><br><span class="line"><span class="comment">/* One packet sent complete */</span></span><br><span class="line">db-&gt;tx_pkt_cnt = <span class="number">0</span>;</span><br><span class="line">dev-&gt;trans_start = <span class="number">0</span>;</span><br><span class="line">netif_wake_queue(dev);  <span class="comment">/* 唤醒发送队列，协议接口层可以继续向下递交数据了 */</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再次读取寄存器状态，如果发送中断未置位，则唤醒发送队列，表示协议接口层可以继续向下递交数据了。由于在<code>dm9000_start_xmit</code>函数中将发送队列关闭了并且调用<code>dm9000_tx_done</code>前清了中断，此时如果中断仍置位，表示出错了，所以<code>dev-&gt;stats.tx_fifo_errors++;</code></p><p>以 UDP 为例，下图说明 DM9000 发送数据包的流程</p><img src="https://img.caiyifan.cn/typora_picgo/2016xxxx.jpg" alt="2016xxxx" style="zoom:80%;"><h3 id="DM9000-接收过程分析"><a href="#DM9000-接收过程分析" class="headerlink" title="DM9000 接收过程分析"></a>DM9000 接收过程分析</h3><p>由发送过程分析可知，接收也是由中断通知的。而且与发送过程共用同一个中断处理函数，当中断是接收中断时会调用<code>dm9000_rx</code>函数来处理接收过程。</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20211010231338825.png" alt="image-20211010231338825"></p><p>RX SRAM 中一个完整数据包包含 4  字节的头部，其中第一个字节固定为 0x01, 第二个字节为数据包状态，最后两个字节表示有效数据的长度。驱动代码中用这样一个结构体来表示头部，头部之后的数据才为真正有效数据</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">dm9000_rxhdr</span> &#123;</span></span><br><span class="line">u8RxPktReady;    <span class="comment">// 固定为 0x01</span></span><br><span class="line">u8RxStatus;</span><br><span class="line">__le16RxLen;</span><br><span class="line">&#125; __attribute__((__packed__));</span><br></pre></td></tr></table></figure><p><code>dm9000_rx</code>函数比较长，关键部分都在代码中注释说明</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">dm9000_rx</span><span class="params">(struct net_device *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">board_info_t</span> *db = netdev_priv(dev);</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">dm9000_rxhdr</span> <span class="title">rxhdr</span>;</span>   <span class="comment">/* RX SRAM 存储的数据的四字节头部, 去除头部后才是数据包 */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sk_buff</span> *<span class="title">skb</span>;</span></span><br><span class="line">u8 rxbyte, *rdptr;</span><br><span class="line"><span class="keyword">bool</span> GoodPacket;</span><br><span class="line"><span class="keyword">int</span> RxLen;</span><br><span class="line"><span class="keyword">int</span> save_mrr, calc_mrr, check_mrr;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Check packet ready or not */</span></span><br><span class="line"><span class="keyword">do</span> &#123;</span><br><span class="line">ior(db, DM9000_MRCMDX);<span class="comment">/* Dummy read */</span></span><br><span class="line">save_mrr = (ior(db, <span class="number">0xf5</span>) &lt;&lt; <span class="number">8</span>) | ior(db, <span class="number">0xf4</span>);</span><br><span class="line"><span class="comment">/* Get most updated data */</span></span><br><span class="line">rxbyte = ior(db, DM9000_MRCMDX); <span class="comment">/* 读取 RX SRAM 的数据, 地址不会自增 */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(rxbyte != DM9000_PKT_RDY)  <span class="comment">/* DM9000_PKT_RDY: 0x01, RX sram存储的数据的四字节头部第一字节固定为 0x01 */</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="comment">/* Status check: this byte must be 0 or 1 */</span></span><br><span class="line"><span class="keyword">if</span> (rxbyte &gt; DM9000_PKT_RDY) &#123;</span><br><span class="line">dev_warn(db-&gt;dev, <span class="string">"status check fail: %d\n"</span>, rxbyte);</span><br><span class="line">iow(db, DM9000_RCR, <span class="number">0x00</span>);<span class="comment">/* Stop Device */</span></span><br><span class="line">iow(db, DM9000_IMR, IMR_PAR);<span class="comment">/* Stop INT request */</span></span><br><span class="line"></span><br><span class="line">db-&gt;wait_reset = <span class="number">1</span>;</span><br><span class="line">dev-&gt;trans_start = <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* A packet ready now  &amp; Get status/length */</span></span><br><span class="line">GoodPacket = <span class="literal">true</span>;</span><br><span class="line">writeb(DM9000_MRCMD, db-&gt;io_addr);  <span class="comment">/* 读取 RX SRAM 的数据, 并且地址自增 */</span></span><br><span class="line">(db-&gt;inblk)(db-&gt;io_data, &amp;rxhdr, <span class="keyword">sizeof</span>(rxhdr));</span><br><span class="line"></span><br><span class="line">RxLen = le16_to_cpu(rxhdr.RxLen);  <span class="comment">// 数据包的总长度</span></span><br><span class="line"></span><br><span class="line">calc_mrr = save_mrr + <span class="number">4</span> + RxLen;</span><br><span class="line"><span class="keyword">if</span>(<span class="number">0x00</span> == db-&gt;io_mode)  <span class="comment">//16 bit only</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">if</span>(RxLen &amp; <span class="number">0x01</span>) calc_mrr++;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(calc_mrr &gt; <span class="number">0x3fff</span>) calc_mrr -= <span class="number">0x3400</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (netif_msg_rx_status(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"RX: status %02x, length %04x\n"</span>,</span><br><span class="line">rxhdr.RxStatus, RxLen);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Packet Status check */</span></span><br><span class="line"><span class="comment">/* 64 &lt; 以太网帧长度 &lt;= 1536 */</span></span><br><span class="line"><span class="keyword">if</span> (RxLen &lt; <span class="number">0x40</span>) &#123;</span><br><span class="line">GoodPacket = <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">if</span> (netif_msg_rx_err(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"RX: Bad Packet (runt)\n"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (RxLen &gt; DM9000_PKT_MAX) &#123;</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"RST: RX Len:%x\n"</span>, RxLen);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 校验头部的状态值，判断是否是一个正常的数据包</span></span><br><span class="line"><span class="comment">/* rxhdr.RxStatus is identical to RSR register. */</span></span><br><span class="line"><span class="keyword">if</span> (rxhdr.RxStatus &amp; (RSR_FOE | RSR_CE | RSR_AE |</span><br><span class="line">      RSR_PLE | RSR_RWTO |</span><br><span class="line">      RSR_LCS | RSR_RF)) &#123;</span><br><span class="line"><span class="keyword">if</span> (rxhdr.RxStatus &amp; RSR_FOE) &#123;</span><br><span class="line"><span class="keyword">if</span> (netif_msg_rx_err(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"fifo error\n"</span>);</span><br><span class="line">dev-&gt;stats.rx_fifo_errors++;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (rxhdr.RxStatus &amp; RSR_CE) &#123;</span><br><span class="line"><span class="keyword">if</span> (netif_msg_rx_err(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"crc error\n"</span>);</span><br><span class="line">dev-&gt;stats.rx_crc_errors++;</span><br><span class="line">GoodPacket = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (rxhdr.RxStatus &amp; RSR_RF) &#123;</span><br><span class="line"><span class="keyword">if</span> (netif_msg_rx_err(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"length error\n"</span>);</span><br><span class="line">dev-&gt;stats.rx_length_errors++;</span><br><span class="line">GoodPacket = <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Move data from DM9000 */</span></span><br><span class="line"><span class="keyword">if</span> (GoodPacket &amp;&amp;</span><br><span class="line">    ((skb = dev_alloc_skb(RxLen + <span class="number">4</span>)) != <span class="literal">NULL</span>)) &#123;   <span class="comment">// 如果是正常数据包，就申请 sk buffer</span></span><br><span class="line">skb_reserve(skb, <span class="number">2</span>);</span><br><span class="line">rdptr = (u8 *) skb_put(skb, RxLen - <span class="number">4</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Read received packet from RX SRAM */</span></span><br><span class="line"></span><br><span class="line">(db-&gt;inblk)(db-&gt;io_data, rdptr, RxLen);  <span class="comment">// 将 RX SRAM 中的有效数据拷贝到 sk buffer 中</span></span><br><span class="line">dev-&gt;stats.rx_bytes += RxLen;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Pass to upper layer */</span></span><br><span class="line">skb-&gt;protocol = eth_type_trans(skb, dev);</span><br><span class="line"></span><br><span class="line">netif_rx(skb);           <span class="comment">/* 将 skb uffer 向上递交给协议接口层 */</span></span><br><span class="line">dev-&gt;stats.rx_packets++;</span><br><span class="line"></span><br><span class="line">check_mrr = (ior(db, <span class="number">0xf5</span>) &lt;&lt; <span class="number">8</span>) | ior(db, <span class="number">0xf4</span>);</span><br><span class="line"><span class="keyword">if</span>(calc_mrr != check_mrr)</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (netif_msg_rx_err(db))</span><br><span class="line">dev_dbg(db-&gt;dev, <span class="string">"rx point error %04x %04x %04x %04x\n"</span>,</span><br><span class="line">save_mrr, RxLen, calc_mrr, check_mrr);</span><br><span class="line"></span><br><span class="line">iow(db, <span class="number">0xf5</span>, (calc_mrr &gt;&gt; <span class="number">8</span>) &amp; <span class="number">0xff</span>);</span><br><span class="line">iow(db, <span class="number">0xf4</span>, calc_mrr &amp; <span class="number">0xff</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">/* need to dump the packet's data */</span></span><br><span class="line">iow(db, <span class="number">0xf5</span>, (calc_mrr &gt;&gt; <span class="number">8</span>) &amp; <span class="number">0xff</span>);</span><br><span class="line">iow(db, <span class="number">0xf4</span>, calc_mrr &amp; <span class="number">0xff</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">while</span> (rxbyte &amp; DM9000_PKT_RDY);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>大体逻辑可以归为以下流程：</p><p>1.先读取 RX SRAM 中 4 字节头部到<code>struct dm9000_rxhdr rxhdr</code>中</p><p>2.判断第一字节是否为 0x01, 判断数据包总长度是否符合以太网规范，最后根据头部中的状态值是否是一个正常的封包</p><p>3.经过 2 判断是正常封包后，读取有效数据</p><p>4.创建分配 sk buffer，并将有效数据拷贝到 sk buffer 中</p><p>5.调用<code>netif_rx</code>, 将 sk buffer 向上递交给协议接口层</p><p>以 UDP 为例，下图说明 DM9000 接收数据包的流程</p><img src="https://img.caiyifan.cn/typora_picgo/image-20211011180352577.png" alt="image-20211011180352577" style="zoom:80%;"><h3 id="NAPI-方式接收介绍"><a href="#NAPI-方式接收介绍" class="headerlink" title="NAPI 方式接收介绍"></a>NAPI 方式接收介绍</h3><p>通常情况下，网络驱动以中断方式接收数据，但是当数据量大的时候会频繁产生中断，CPU 要频繁去处理中断导致效率低下而不如纯轮询模式。在 kernel 2.5 之后引入了新的处理方式，叫 NAPI，综合了中断方式和轮询方式。NAPI 这个名字取得不知所云，据说由于当时未找到合适的名字，就叫 NAPI (New API)，目前已经公认为专有名词了。</p><p> NAPI 接收数据的流程：接收中断来临 -&gt; 关闭接收中断 -&gt; 轮询方式接收所有数据包直到为空 -&gt; 开启接收中断 -&gt; 接收中断来临 -&gt; …</p><p>笔者在 DM9000 中加入了 NAPI 的支持 <a href="https://github.com/IfanTsai/x210_kernel/commit/d019705ac0361b96ce250a24eef72798de7ae322" target="_blank" rel="external nofollow noopener noreferrer">git commit</a>。</p><p>主要修改如下：</p><p>1.在<code>driver/net/Kconfig</code>中加入配置</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">config</span> DM9000_NAPI</span><br><span class="line">    <span class="keyword">bool</span> <span class="string">"DM9000 NAPI"</span></span><br><span class="line">    depends on DM9000</span><br><span class="line">    <span class="keyword">default</span> n</span><br><span class="line">    help</span><br><span class="line">        Support DM9000 driver <span class="built_in">run</span> NAPI mode</span><br></pre></td></tr></table></figure><p>２.在<code>struct board_info</code>添加成员</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000_NAPI</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">napi_struct</span> <span class="title">napi</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>３.在 probe 函数中调用<code>netif_napi_add</code>注册 NAPI 要调度执行的轮询函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> DM9000_NAPI_WEIGHT 64</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000_NAPI</span></span><br><span class="line">    netif_napi_add(ndev, &amp;db-&gt;napi, dm9000_napi_poll, DM9000_NAPI_WEIGHT);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p><code>dm9000_napi_poll</code>函数如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000_NAPI</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">dm9000_napi_poll</span><span class="params">(struct napi_struct *napi, <span class="keyword">int</span> budget)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">board_info_t</span> *db = container_of(napi, <span class="keyword">board_info_t</span>, napi);</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line">    u8 reg_save;</span><br><span class="line"></span><br><span class="line">    spin_lock_irqsave(&amp;db-&gt;lock, flags);</span><br><span class="line"></span><br><span class="line">    reg_save = readb(db-&gt;io_addr);</span><br><span class="line"></span><br><span class="line">    dm9000_rx(db-&gt;ndev, budget);    <span class="comment">// 轮询处理收包</span></span><br><span class="line"></span><br><span class="line">    napi_complete(napi);</span><br><span class="line"></span><br><span class="line">    iow(db, DM9000_IMR, db-&gt;imr_all);</span><br><span class="line"></span><br><span class="line">    writeb(reg_save, db-&gt;io_addr);</span><br><span class="line"></span><br><span class="line">    spin_unlock_irqrestore(&amp;db-&gt;lock, flags);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p><code>dm9000_rx</code>轮询处理完收包后，需要调用<code>napi_complete</code>表示轮询完毕。</p><p>４.在 open 函数中调用<code>napi_enable</code>使能 NAPI 调度</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000_NAPI</span></span><br><span class="line">    napi_enable(&amp;db-&gt;napi);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>同样在 stop 函数中禁止 NAPI 调度</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_DM9000_NAPI</span></span><br><span class="line">    napi_disable(&amp;db-&gt;napi);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;网络设备不同于字符设备和块设备，并不对应于&lt;code&gt;/dev&lt;/code&gt;目录下的文件，应用程序通过 socket 完成与网络设备的交互，在网络设备上并不体现”一切皆文件”的设计思想。&lt;/p&gt;
&lt;h2 id=&quot;Linux-网络设备驱动架构&quot;&gt;&lt;a href=&quot;#Linux-网络设备驱动架构&quot; class=&quot;headerlink&quot; title=&quot;Linux 网络设备驱动架构&quot;&gt;&lt;/a&gt;Linux 网络设备驱动架构&lt;/h2&gt;&lt;p&gt;驱动架构自上而下分为4层：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;协议接口层&lt;/li&gt;
&lt;li&gt;设备接口层&lt;/li&gt;
&lt;li&gt;设备驱动功能层&lt;/li&gt;
&lt;li&gt;网络设备与媒介层&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="嵌入式" scheme="https://www.caiyifan.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
      <category term="linux驱动" scheme="https://www.caiyifan.cn/tags/linux%E9%A9%B1%E5%8A%A8/"/>
    
  </entry>
  
  <entry>
    <title>公网环境搭建 k8s 集群</title>
    <link href="https://www.caiyifan.cn/p/d6990d10.html"/>
    <id>https://www.caiyifan.cn/p/d6990d10.html</id>
    <published>2021-08-09T15:29:03.000Z</published>
    <updated>2024-05-01T10:34:38.736Z</updated>
    
    <content type="html"><![CDATA[<p>笔者利用手头几台云服务器搭建 k8s 集群，由于这几台云服务属于不同的云服务厂商，无法搭建局域网环境的 k8s 集群，故笔者搭建的是公网环境的 k8s 集群，在此做个记录, 以下均在 ubuntu 20.04 环境下进行</p><h2 id="创建虚拟网卡"><a href="#创建虚拟网卡" class="headerlink" title="创建虚拟网卡"></a>创建虚拟网卡</h2><p>由于主机内看到的只有内网 IP, 而且几台云服务器位于不同的内网, 直接搭建会将内网 IP 注册进集群导致搭建不成功。解决方案：使用虚拟网卡绑定公网 IP, 使用该公网 IP 来注册集群</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 所有主机都要创建虚拟网卡，并绑定对应的公网 ip</span></span><br><span class="line">sudo ifconfig eth0:1 139.198.108.103</span><br></pre></td></tr></table></figure><blockquote><p>该设置方式在重启服务器后失效，持久化需要将配置写入<code>/etc/network/interfaces</code>或<code>/etc/netplan/50-cloud-init.yaml</code></p></blockquote><a id="more"></a><h2 id="更新-x2F-etc-x2F-hosts"><a href="#更新-x2F-etc-x2F-hosts" class="headerlink" title="更新 &#x2F;etc&#x2F;hosts"></a>更新 &#x2F;etc&#x2F;hosts</h2><p>将集群所有节点的公网 ip 和 hostname 对应关系写入<code>/etc/hosts</code>中</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vi /etc/hosts</span><br></pre></td></tr></table></figure><h2 id="关闭-swap-分区"><a href="#关闭-swap-分区" class="headerlink" title="关闭 swap 分区"></a>关闭 swap 分区</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo swapoff -a</span><br><span class="line"><span class="comment"># 注释掉 fstab 中 swap 分区的挂载信息</span></span><br><span class="line">sudo sed -ri <span class="string">'s/.*swap.*/#&amp;/'</span> /etc/fstab</span><br></pre></td></tr></table></figure><h2 id="配置-systemd-来管理-docker-的-cgroup"><a href="#配置-systemd-来管理-docker-的-cgroup" class="headerlink" title="配置 systemd 来管理 docker 的 cgroup"></a>配置 systemd 来管理 docker 的 cgroup</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir /etc/docker</span><br><span class="line">cat &lt;&lt;EOF | sudo tee /etc/docker/daemon.json</span><br><span class="line">&#123;</span><br><span class="line">  <span class="string">"exec-opts"</span>: [<span class="string">"native.cgroupdriver=systemd"</span>],</span><br><span class="line">  <span class="string">"log-driver"</span>: <span class="string">"json-file"</span>,</span><br><span class="line">  <span class="string">"log-opts"</span>: &#123;</span><br><span class="line">    <span class="string">"max-size"</span>: <span class="string">"100m"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">"storage-driver"</span>: <span class="string">"overlay2"</span></span><br><span class="line">&#125;</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">sudo systemctl <span class="built_in">enable</span> docker</span><br><span class="line">sudo systemctl daemon-reload</span><br><span class="line">sudo systemctl restart docker</span><br></pre></td></tr></table></figure><h2 id="允许-iptables-检查桥接流量"><a href="#允许-iptables-检查桥接流量" class="headerlink" title="允许 iptables 检查桥接流量"></a>允许 iptables 检查桥接流量</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">cat &lt;&lt;EOF | sudo tee /etc/modules-load.d/k8s.conf</span><br><span class="line">br_netfilter</span><br><span class="line">EOF</span><br><span class="line"></span><br><span class="line">cat &lt;&lt;EOF | sudo tee /etc/sysctl.d/k8s.conf</span><br><span class="line">net.bridge.bridge-nf-call-ip6tables = 1</span><br><span class="line">net.bridge.bridge-nf-call-iptables = 1</span><br><span class="line">EOF</span><br><span class="line">sudo sysctl --system</span><br></pre></td></tr></table></figure><h2 id="开启相关端口"><a href="#开启相关端口" class="headerlink" title="开启相关端口"></a>开启相关端口</h2><h3 id="master-节点"><a href="#master-节点" class="headerlink" title="master 节点"></a>master 节点</h3><table><thead><tr><th align="center">协议</th><th align="center">方向</th><th align="center">端口范围</th><th align="center">作用</th><th align="center">使用者</th></tr></thead><tbody><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">6443</td><td align="center">Kubernetes API 服务器</td><td align="center">所有组件</td></tr><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">2379-2380</td><td align="center">etcd 服务器客户端 API</td><td align="center">kube-apiserver、etcd</td></tr><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">10250</td><td align="center">Kubelet API</td><td align="center">kubelet 自身、控制平面组件</td></tr><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">10251</td><td align="center">kube-scheduler</td><td align="center">kube-scheduler 自身</td></tr><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">10252</td><td align="center">kube-controller-manager</td><td align="center">kube-controller-manager 自身</td></tr></tbody></table><h3 id="worker-节点"><a href="#worker-节点" class="headerlink" title="worker 节点"></a>worker 节点</h3><table><thead><tr><th align="center">协议</th><th align="center">方向</th><th align="center">端口范围</th><th align="center">作用</th><th align="center">使用者</th></tr></thead><tbody><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">10250</td><td align="center">Kubelet API</td><td align="center">kubelet 自身、控制平面组件</td></tr><tr><td align="center">TCP</td><td align="center">入站</td><td align="center">30000-32767</td><td align="center">NodePort 服务</td><td align="center">所有组件</td></tr></tbody></table><h3 id="所有节点"><a href="#所有节点" class="headerlink" title="所有节点"></a>所有节点</h3><table><thead><tr><th align="center">协议</th><th align="center">方向</th><th align="center">端口范围</th><th align="center">作用</th><th align="center">使用者</th></tr></thead><tbody><tr><td align="center">UDP</td><td align="center">入站</td><td align="center">8472</td><td align="center">vxlan Overlay 网络通信</td><td align="center">Overlay 网络</td></tr></tbody></table><h2 id="安装-kubeadm、kubelet-和-kubectl"><a href="#安装-kubeadm、kubelet-和-kubectl" class="headerlink" title="安装 kubeadm、kubelet 和 kubectl"></a>安装 kubeadm、kubelet 和 kubectl</h2><ul><li><p>kubeadm：用来初始化集群的指令</p></li><li><p>kubelet：在集群中的每个节点上用来启动 Pod 和容器等</p></li><li><p>kubectl：用来与集群通信的命令行工具</p></li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install -y apt-transport-https ca-certificates curl</span><br><span class="line"></span><br><span class="line">curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add</span><br><span class="line"><span class="built_in">echo</span> <span class="string">"deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main"</span> | sudo tee /etc/apt/sources.list.d/kubernetes.list</span><br><span class="line"></span><br><span class="line">sudo apt update</span><br><span class="line">sudo apt install -y kubelet kubeadm kubectl</span><br><span class="line">sudo apt-mark hold kubelet kubeadm kubectl</span><br></pre></td></tr></table></figure><h2 id="修改-kubelet-启动参数"><a href="#修改-kubelet-启动参数" class="headerlink" title="修改 kubelet 启动参数"></a>修改 kubelet 启动参数</h2><p>添加 kubelet  的启动参数<code>--node-ip=公网IP</code>， 每个主机都要添加并指定对应的公网 ip, 添加了这一步才能使用公网 ip 来注册进集群</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210809220253057.png" alt="image-20210809220253057"></p><h2 id="初始化-master-节点"><a href="#初始化-master-节点" class="headerlink" title="初始化 master 节点"></a>初始化 master 节点</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sudo kubeadm init \</span><br><span class="line">    --kubernetes-version=v1.22.0 \</span><br><span class="line">    --apiserver-advertise-address=139.198.108.103 \</span><br><span class="line">    --control-plane-endpoint=139.198.108.103 \</span><br><span class="line">    --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \</span><br><span class="line">    --service-cidr=10.10.0.0/16 \</span><br><span class="line">    --pod-network-cidr=10.122.0.0/16</span><br></pre></td></tr></table></figure><p>报错及解决：</p><p>ERROR ImagePull: failed to pull image registry.cn-hangzhou.aliyuncs.com&#x2F;google_containers&#x2F;coredns:v1.8.4: output: Error response from daemon: manifest for registry.cn-hangzhou.aliyuncs.com&#x2F;google_containers&#x2F;coredns:v1.8.4 not found: manifest unknown: manifest unknown</p><p>解决：</p><p>1.从官方镜像拉取 coredns</p><pre><code><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull coredns/coredns</span><br></pre></td></tr></table></figure></code></pre><p>2.打 tag，修改镜像名</p><pre><code><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker tag coredns&#x2F;coredns:latest registry.cn-hangzhou.aliyuncs.com&#x2F;google_containers&#x2F;coredns:v1.8.4</span><br></pre></td></tr></table></figure></code></pre><p>3.删除多余镜像</p><pre><code> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker rmi coredns/coredns:latest</span><br></pre></td></tr></table></figure></code></pre><p>初始化成功后输出如下</p><img src="https://img.caiyifan.cn/typora_picgo/image-20210808145510236.png" alt="image-20210808145510236" style="zoom:67%;"><p>根据输出提示执行以下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p <span class="variable">$HOME</span>/.kube</span><br><span class="line">sudo cp -i /etc/kubernetes/admin.conf <span class="variable">$HOME</span>/.kube/config</span><br><span class="line">sudo chown $(id -u):$(id -g) <span class="variable">$HOME</span>/.kube/config</span><br></pre></td></tr></table></figure><p>记录下该命令，用于之后将 worker 节点加入集群</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kubeadm join 139.198.108.103:6443 --token hnop0o.t16okler9962rroq \</span><br><span class="line">        --discovery-token-ca-cert-hash sha256:64c85683ac63f820e64787ed47674c7d470574feebcfe0f2142f45699cfe8ec6</span><br></pre></td></tr></table></figure><h3 id="修改kube-apiserver参数"><a href="#修改kube-apiserver参数" class="headerlink" title="修改kube-apiserver参数"></a>修改<code>kube-apiserver</code>参数</h3><p>在 master 节点，kube-apiserver 添加<code>--bind-address</code>和修改<code>--advertise-addres</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml</span><br></pre></td></tr></table></figure><img src="https://img.caiyifan.cn/typora_picgo/image-20210809221224565.png" alt="image-20210809221224565" style="zoom:67%;"><h2 id="安装-flannel-网络"><a href="#安装-flannel-网络" class="headerlink" title="安装 flannel 网络"></a>安装 flannel 网络</h2><p>在 master 节点执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载 flannel 的 yaml 配置文件</span></span><br><span class="line">wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml</span><br></pre></td></tr></table></figure><p>修改 yaml 配置文件，添加两处地方和修改一处地方</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi kube-flannel.yml</span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">args:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">--public-ip=$(PUBLIC_IP)</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">--iface=eth0</span></span><br><span class="line"></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">PUBLIC_IP</span></span><br><span class="line">   <span class="attr">valueFrom:</span></span><br><span class="line">     <span class="attr">fieldRef:</span></span><br><span class="line">       <span class="attr">fieldPath:</span> <span class="string">status.podIP</span></span><br><span class="line"></span><br><span class="line"><span class="attr">net-conf.json:</span> <span class="string">|</span></span><br><span class="line">  <span class="string">&#123;</span></span><br><span class="line">    <span class="attr">"Network":</span> <span class="string">"10.122.0.0/16"</span><span class="string">,</span></span><br><span class="line">      <span class="attr">"Backend":</span> <span class="string">&#123;</span></span><br><span class="line">        <span class="attr">"Type":</span> <span class="string">"vxlan"</span></span><br><span class="line">      <span class="string">&#125;</span></span><br><span class="line">  <span class="string">&#125;</span></span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210809222538582.png" alt="image-20210809222538582"></p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210809222717398.png" alt="image-20210809222717398"></p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20220126230053561.png" alt="image-20220126230053561"></p><p>修改完后，开始安装网络插件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply -f kube-flannel.yml</span><br></pre></td></tr></table></figure><p>执行如下命令，等待一会儿，直到所有的容器组处于 Running 状态</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">watch -n 1 kubectl get pod -n kube-system -o wide</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20220126233108588.png" alt="image-20220126233108588"></p><h2 id="worker-节点加入集群"><a href="#worker-节点加入集群" class="headerlink" title="worker 节点加入集群"></a>worker 节点加入集群</h2><p>使用初始化 master 节点成功后输出的命令来加入集群，或者在 master 节点重新打印 token 和加入命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubeadm token create --<span class="built_in">print</span>-join-command</span><br></pre></td></tr></table></figure><p>在 worker 节点执行命令加入集群</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo kubeadm join 139.198.108.103:6443 --token wm2039.cf8qnsrgyip6qvsz --discovery-token-ca-cert-hash sha256:64c85683ac63f820e64787ed47674c7d470574feebcfe0f2142f45699cfe8ec6</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210808152039514.png" alt="image-20210808152039514"></p><p>等待所有需要加入的节点加入成功后，在 master 节点执行下面命令，并等待所有节点状态变为 Ready （笔者搭建的一主两从的集群，均使用的公网 ip)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get nodes</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210808152200461.png" alt="image-20210808152200461"></p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p>master 节点执行下面命令来部署 nginx</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kubectl create deploy my-nginx --image=nginx</span><br><span class="line">kubectl expose deploy my-nginx --port=80 --<span class="built_in">type</span>=NodePort</span><br></pre></td></tr></table></figure><p>查看 nginx 部署的 pod 信息，可以看到 Pod ip，以及部署在哪一个节点上</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get pods -o wide</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210809230648621.png" alt="image-20210809230648621"></p><p>尝试 ping Pod 的 ip，如果无法 ping 通，执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo iptables -P FORWARD ACCEPT</span><br></pre></td></tr></table></figure><blockquote><p>docker 从 1.13 版本开始，可能将 <strong>iptables FORWARD chain</strong> 的默认策略设置为了 <strong>DROP</strong>，该设置会导致 ping 其他 node 上的 Pod ip 失败</p></blockquote><p>查看 nginx 对外暴露的端口</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl get all</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210809230346570.png" alt="image-20210809230346570"></p><p>可以看到对外暴露的端口是 30950, 如果分别通过集群内所有节点的公网 ip 访问这个端口，能请求到 nginx 主页，则证明部署成功</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210809232520672.png" alt="image-20210809232520672"></p><h2 id="安装-Dashboard"><a href="#安装-Dashboard" class="headerlink" title="安装 Dashboard"></a>安装 Dashboard</h2><p>下载 dashboard 的 yaml 描述文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.6.1/aio/deploy/recommended.yaml</span><br></pre></td></tr></table></figure><p>修改下载下来的 recommend.yaml</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">    <span class="attr">k8s-app:</span> <span class="string">kubernetes-dashboard</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">kubernetes-dashboard</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kubernetes-dashboard</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">port:</span> <span class="number">443</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">8443</span></span><br><span class="line">      <span class="attr">nodePort:</span> <span class="number">30001</span>    <span class="comment"># 指定对外暴露的 port</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">NodePort</span>         <span class="comment"># 指定服务类型为 NodePort</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">k8s-app:</span> <span class="string">kubernetes-dashboard</span></span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_pico/image-20220903235042690.png" alt="image-20220903235042690"> </p><p>应用修改后的 yaml 文件，创建 dashboard 服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply -f recommended.yaml</span><br></pre></td></tr></table></figure><p>现在可以通过 30001 端口访问 dashboard 的登录页面了</p><p><img src="https://img.caiyifan.cn/typora_pico/image-20220904000035011.png" alt="image-20220904000035011"></p><p>如果使用的是 chrome 浏览器并出现了以上页面，可以鼠标点击页面任意地方，然后键盘输入 thisisunsafe。正常访问会进入 Login 页面，提示需要授权</p><p><img src="https://img.caiyifan.cn/typora_pico/image-20220904000346775.png" alt="image-20220904000346775"></p><p>接下来创建 admin 用户来获取 token</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vi dashboard-admin.yaml</span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">rbac.authorization.k8s.io/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ClusterRoleBinding</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-user</span></span><br><span class="line"><span class="attr">roleRef:</span></span><br><span class="line">  <span class="attr">apiGroup:</span> <span class="string">rbac.authorization.k8s.io</span></span><br><span class="line">  <span class="attr">kind:</span> <span class="string">ClusterRole</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">cluster-admin</span></span><br><span class="line"><span class="attr">subjects:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-user</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kubernetes-dashboard</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">ServiceAccount</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">admin-user</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">kubernetes-dashboard</span></span><br></pre></td></tr></table></figure><p>创建 admin 用户并获取 token</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">kubectl apply -f dashboard-admin.yaml</span><br><span class="line">kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk <span class="string">'&#123;print $1&#125;'</span>)</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_pico/image-20220904000836247.png" alt="image-20220904000836247"> </p><p>在 Login 页面输入 token 后就可以成功访问 dashboard 页面了<img src="https://img.caiyifan.cn/typora_pico/image-20220904001051285.png" alt="image-20220904001051285"> </p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;笔者利用手头几台云服务器搭建 k8s 集群，由于这几台云服务属于不同的云服务厂商，无法搭建局域网环境的 k8s 集群，故笔者搭建的是公网环境的 k8s 集群，在此做个记录, 以下均在 ubuntu 20.04 环境下进行&lt;/p&gt;
&lt;h2 id=&quot;创建虚拟网卡&quot;&gt;&lt;a href=&quot;#创建虚拟网卡&quot; class=&quot;headerlink&quot; title=&quot;创建虚拟网卡&quot;&gt;&lt;/a&gt;创建虚拟网卡&lt;/h2&gt;&lt;p&gt;由于主机内看到的只有内网 IP, 而且几台云服务器位于不同的内网, 直接搭建会将内网 IP 注册进集群导致搭建不成功。解决方案：使用虚拟网卡绑定公网 IP, 使用该公网 IP 来注册集群&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 所有主机都要创建虚拟网卡，并绑定对应的公网 ip&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sudo ifconfig eth0:1 139.198.108.103&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;该设置方式在重启服务器后失效，持久化需要将配置写入&lt;code&gt;/etc/network/interfaces&lt;/code&gt;或&lt;code&gt;/etc/netplan/50-cloud-init.yaml&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="k8s" scheme="https://www.caiyifan.cn/tags/k8s/"/>
    
  </entry>
  
  <entry>
    <title>Docker 入门之网络</title>
    <link href="https://www.caiyifan.cn/p/cd5dbabb.html"/>
    <id>https://www.caiyifan.cn/p/cd5dbabb.html</id>
    <published>2021-07-25T17:44:04.000Z</published>
    <updated>2024-05-01T10:34:38.734Z</updated>
    
    <content type="html"><![CDATA[<p>Docker 网络使用了 Linux 的 network namespace 机制，隔离了网络设备， ip 协议栈，路由表以及防火墙规则。</p><h2 id="默认网络模式"><a href="#默认网络模式" class="headerlink" title="默认网络模式"></a>默认网络模式</h2><p>每一个安装了 Docker 的 Linux 主机都会创建一个名为 docker0 的虚拟网桥，该虚拟网桥作为所有容器的默认网关。<br>在默认的网络模式下，虚拟网桥的工作方式和物理交换机类似，主机上的所有容器通过 docker0 连接在了一个二层网络中。</p><img src="https://img.caiyifan.cn/typora_picgo/image-20210725184030580.png" alt="image-20210725184030580" style="zoom: 67%;"><br><a id="more"></a><p>每启动一个容器，Docker 都会创建一个虚拟网卡， 并根据 Docker 网桥所在的网段来分配给容器一个未使用的 ip 地址，称之为容器 ip。在宿主机和容器内分别创建了一个虚拟接口，它们彼此连通，这对接口称之为 veth pair。</p><img src="https://img.caiyifan.cn/typora_picgo/image-20210725184800623.png" alt="image-20210725184800623" style="zoom: 67%;"><p>默认情况下的网络模式称之为 <strong>bridge</strong> 模式，该模式为 docker 的默认模式。在启动 docker 时可以使用<code>--net</code>指定容器的网络模式</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker run -it --net=bridge -p 9001:6379 redis:alpine</span><br></pre></td></tr></table></figure><p><strong>bridge</strong> 模式的网络转发如下图所示</p><img src="https://img.caiyifan.cn/typora_picgo/image-20210726014311855.png" alt="image-20210726014311855" style="zoom:80%;"><h2 id="四种网络模式"><a href="#四种网络模式" class="headerlink" title="四种网络模式"></a>四种网络模式</h2><p>除了 <strong>brideg</strong> 模式，docker 还支持 <strong>container</strong>, <strong>host</strong>, <strong>none</strong> 模式</p><table><thead><tr><th>网络模式</th><th>配置</th><th>说明</th></tr></thead><tbody><tr><td>bridge</td><td><code>--net=bridge</code></td><td>默认模式</td></tr><tr><td>container</td><td><code>--net=container:name或id</code></td><td>容器和另外一个容器共享 network namespace <br> k8s 中的 pod 就是多个容器共享一个 network namespace</td></tr><tr><td>host</td><td><code>--net=host</code></td><td>容器和宿主机共享 network namespace</td></tr><tr><td>none</td><td><code>--net=none</code></td><td>不配置网络，用户可以稍后进入容器，自行配置</td></tr></tbody></table><h2 id="自定义网络"><a href="#自定义网络" class="headerlink" title="自定义网络"></a>自定义网络</h2><p>默认主机中创建了三个网络， 可通过<code>network ls</code>命令查看</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker network ls</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210725235557249.png" alt="image-20210725235557249"></p><p>用户可通过<code>network create</code>命令创建自定义网络</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建自定义网络 指定 bridge 模式，网段为 192.168.0.0/16， 网关为 192.168.0.0/16, 命名为 mynet</span></span><br><span class="line">sudo docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet</span><br></pre></td></tr></table></figure><img src="https://img.caiyifan.cn/typora_picgo/image-20210726001838833.png" alt="image-20210726001838833" style="zoom:80%;"><p>启动容器可以指定连接到自定义网络</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 启动两个 redis 容器，分别命名为 redis1, redis2</span></span><br><span class="line">sudo docker run -it -d --name redis1 -P --net mynet redis:alpine</span><br><span class="line">sudo docker run -it -d --name redis2 -P --net mynet redis:alpine</span><br></pre></td></tr></table></figure><p>通过<code>network inspect</code>命令可以查看一个网络的详情</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看 mynet 网络详细信息，参数为网络 id 或网络名，参数可通过 network ls 命令查到</span></span><br><span class="line">sudo docker inspect e37aeda5814a</span><br></pre></td></tr></table></figure><p>可以看到该网络下连接了两个容器</p><img src="https://img.caiyifan.cn/typora_picgo/image-20210726004006124.png" alt="image-20210726004006124" style="zoom: 67%;"><p>在以前可使用 <code>--link</code> 参数来使容器互联，但在自定义网络下默认就可以使用容器名进行容器的互联，内部已经维护好了容器名和 ip 的对应关系</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 在 redis1 容器中访问 redis2 容器</span></span><br><span class="line">sudo docker <span class="built_in">exec</span> -it redis1 ping redis2</span><br></pre></td></tr></table></figure><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210726004536405.png" alt="image-20210726004536405"></p><blockquote><p>注：默认的 bridge 网络不支持通过容器名进行互联</p></blockquote><p>默认情况下两个网络是隔离的，如果需要让两个网络下的容器能够互相访问，可以使用<code>network connect</code>命令将容器连接到另一个网络</p><p>在默认的 bridge 网络下创建一个 redis3 容器用于演示</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker run -it -d -P --name redis3 --net bridge redis:alpine</span><br></pre></td></tr></table></figure><img src="https://img.caiyifan.cn/typora_picgo/image-20210726010653424.png" alt="image-20210726010653424" style="zoom:80%;"><p>默认情况下，两个不同网络的容器无法访问</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210726010925385.png" alt="image-20210726010925385"></p><p>将 redis3 容器加入到 mynet 网络中</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># docker network connect 网络名或id 容器名或id</span></span><br><span class="line">sudo docker network connect mynet redis3</span><br></pre></td></tr></table></figure><p>加入后就可以成功访问了</p><p><img src="https://img.caiyifan.cn/typora_picgo/image-20210726011103789.png" alt="image-20210726011103789"></p><p>查看 mynet 网络信息和 ip 信息后发现，redis3 容器被分配了一个 mynet 的网络接口和 ip 地址</p><img src="https://img.caiyifan.cn/typora_picgo/image-20210726011254819.png" alt="image-20210726011254819" style="zoom:80%;"><br><br><img src="https://img.caiyifan.cn/typora_picgo/image-20210726011411737.png" alt="image-20210726011411737" style="zoom:80%;">]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Docker 网络使用了 Linux 的 network namespace 机制，隔离了网络设备， ip 协议栈，路由表以及防火墙规则。&lt;/p&gt;
&lt;h2 id=&quot;默认网络模式&quot;&gt;&lt;a href=&quot;#默认网络模式&quot; class=&quot;headerlink&quot; title=&quot;默认网络模式&quot;&gt;&lt;/a&gt;默认网络模式&lt;/h2&gt;&lt;p&gt;每一个安装了 Docker 的 Linux 主机都会创建一个名为 docker0 的虚拟网桥，该虚拟网桥作为所有容器的默认网关。&lt;br&gt;在默认的网络模式下，虚拟网桥的工作方式和物理交换机类似，主机上的所有容器通过 docker0 连接在了一个二层网络中。&lt;/p&gt;
&lt;img src=&quot;https://img.caiyifan.cn/typora_picgo/image-20210725184030580.png&quot; alt=&quot;image-20210725184030580&quot; style=&quot;zoom: 67%;&quot;&gt;
&lt;br&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="docker" scheme="https://www.caiyifan.cn/tags/docker/"/>
    
  </entry>
  
  <entry>
    <title>Linux 同步机制之原子操作</title>
    <link href="https://www.caiyifan.cn/p/956947ba.html"/>
    <id>https://www.caiyifan.cn/p/956947ba.html</id>
    <published>2021-05-17T15:30:58.000Z</published>
    <updated>2024-05-01T10:34:38.734Z</updated>
    
    <content type="html"><![CDATA[<p>使用原子操作典型例子众所周知就是多个线程操作同一个全局变量 <code>i++</code>, 由于对应的汇编指令并不只是一条，在并发访问下可能出现多个线程中的多条指令交错导致部分加操作丢失。全局变量<code>i</code>属于临界资源，当然可以使用加锁的方式保护临界资源，但是加锁开销比较大，用在这里有些杀鸡焉用牛刀。最好的方式是使用内核提供的<code>atomic_t</code>类型的原子变量来进行原子操作。</p><p>笔者本次通过源码来窥探原子操作的底层实现, 本次仍以 arm 架构下的 kernel 2.6.35 版本为源码来源。</p><p>首先来看下<code>atomic_t</code>的定义, 仅仅只是一个<code>int</code>类型变量</p><blockquote><p>include&#x2F;linux&#x2F;types.h</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="keyword">int</span> counter;</span><br><span class="line">&#125; <span class="keyword">atomic_t</span>;</span><br></pre></td></tr></table></figure><a id="more"></a><p>以原子加操作为例, 来看下<code>atomic_add</code>的实现</p><blockquote><p>arch&#x2F;arm&#x2F;include&#x2F;asm&#x2F;atomic.h</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">atomic_add</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">atomic_t</span> *v)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> tmp;</span><br><span class="line">    <span class="keyword">int</span> result;</span><br><span class="line"></span><br><span class="line">    __asm__ __volatile__(<span class="string">"@ atomic_add\n"</span></span><br><span class="line"><span class="string">"1: ldrex   %0, [%3]\n"</span></span><br><span class="line"><span class="string">"   add %0, %0, %4\n"</span></span><br><span class="line"><span class="string">"   strex   %1, %0, [%3]\n"</span></span><br><span class="line"><span class="string">"   teq %1, #0\n"</span> </span><br><span class="line"><span class="string">"   bne 1b"</span></span><br><span class="line">    : <span class="string">"=&amp;r"</span> (result), <span class="string">"=&amp;r"</span> (tmp), <span class="string">"+Qo"</span> (v-&gt;counter)</span><br><span class="line">    : <span class="string">"r"</span> (&amp;v-&gt;counter), <span class="string">"Ir"</span> (i)</span><br><span class="line">    : <span class="string">"cc"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先对以上需要用到的内嵌汇编知识做一个简单介绍。<br>内嵌汇编的格式如下:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">__asm__ <span class="title">volatile</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    instruction</span></span></span><br><span class="line"><span class="function"><span class="params">    : output</span></span></span><br><span class="line"><span class="function"><span class="params">    : intput</span></span></span><br><span class="line"><span class="function"><span class="params">    : changed)</span></span>;</span><br></pre></td></tr></table></figure><ul><li>instruction 部分便是要执行的汇编指令</li><li>input 部分为汇编指令需要执行的输入, 表示将 c语言定义的值传入汇编</li><li>output 部分为汇编指令执行的输出，表示将汇编执行后的值传给 c语言</li><li>change 部分用于告诉 gcc 该内嵌汇编改变了一些值，强迫 gcc 在编译这段内嵌汇编之前保存会被修改的值，在执行完后恢复</li></ul><p>内嵌汇编中引用 input 部分和 output 部分的值使用 %0, %1, %2 … 占位符, 也就是上述代码中的 result 为 %0, tmp 为 %1, v-&gt;counter 为 %2, &amp;v-&gt;counte 为 %3, i 为 %4。<br>除此每个变量都以 “xx”(yy) 形式出现, 其中”xx”部分为修饰, 以下列出理解<code>atomic_add</code>需要用到的修饰，其他可忽略</p><ul><li>“&#x3D;&amp;r”: &#x3D; 表示只写, &amp; 表示仅用作输出, r 使用任何可用的寄存器</li><li>“+Qo”: + 表示可读可写</li></ul><p><code>atomic_add</code>的核心是两条关键的汇编指令, <code>ldrex</code>和<code>strex</code>需要配套使用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 将寄存器 ry 指向的内存值 load 到寄存器 rx 中， 并记录 ry 指向的内存状态为 exclusive(独占的)</span></span><br><span class="line">ldrex rx, [ry]</span><br><span class="line"></span><br><span class="line"><span class="comment">// strex 更新内存时，会检查内存 exclusive 状态</span></span><br><span class="line"><span class="comment">// 将寄存器 ry 的值 store 到 rz 指向的内存，如果指向的内存为 exclusive, 则执行成功，否则失败。</span></span><br><span class="line"><span class="comment">// 成功则寄存器 rx 被设置为 0, 否则设置为 1。执行成功后清除 exclusive 标记 (清除后可以认为标记为 open)</span></span><br><span class="line">strex rx, ry, [rz]</span><br></pre></td></tr></table></figure><p>铺垫完上述前提知识后, 以下给出对汇编代码的逐行注释</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">atomic_add</span><span class="params">(<span class="keyword">int</span> i, <span class="keyword">atomic_t</span> *v)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> tmp;</span><br><span class="line">    <span class="keyword">int</span> result;</span><br><span class="line"></span><br><span class="line">    __asm__ __volatile__(<span class="string">"@ atomic_add\n"</span>  <span class="comment">// @ 为注释</span></span><br><span class="line"><span class="string">"1: ldrex   %0, [%3]\n"</span>         <span class="comment">// 把 v-&gt;counter 内存值 load 到 result 中   (v-&gt;counter 内存会被记录为 exclusive)</span></span><br><span class="line"><span class="string">"   add %0, %0, %4\n"</span>           <span class="comment">// result += i</span></span><br><span class="line"><span class="string">"   strex   %1, %0, [%3]\n"</span>     <span class="comment">// 把 result 的值 store 到 v-&gt;counter 的内存，并把 store 成功与否存入 tmp</span></span><br><span class="line"><span class="string">"   teq %1, #0\n"</span>               <span class="comment">// tmp 为 0 表示成功</span></span><br><span class="line"><span class="string">"   bne 1b"</span>                     <span class="comment">// 如果不为 0， 则重新执行一遍</span></span><br><span class="line">    : <span class="string">"=&amp;r"</span> (result), <span class="string">"=&amp;r"</span> (tmp), <span class="string">"+Qo"</span> (v-&gt;counter)</span><br><span class="line">    : <span class="string">"r"</span> (&amp;v-&gt;counter), <span class="string">"Ir"</span> (i)</span><br><span class="line">    : <span class="string">"cc"</span>);   <span class="comment">// condition register, 状态寄存器标志位</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>考虑这样的一种 case 来帮助理解, 假设有两个 cpu 发起对同一段内存的访问<br>1.CPU1 发起 ldrex 读操作, 记录当前状态为 exclusive<br>2.CPU2 发起 ldrex 读操作, 记录当前状态为 exclusive, 状态保持不变<br>3.CPU2 发起 strex 写操作, 状态从 exclusive 变为 open, 同时数据写回内存<br>4.CPU1 发起 strex 写操作, 由于当前状态为 open, 则写失败<br>5.CPU1 由于 strex 写失败, 根据<code>atomic_add</code>的<code>&quot;teq %1, #0\n&quot; &quot;bne 1b&quot;</code>逻辑会再进行 ldrex 后 strex 直到成功(这就是所谓的自旋), 所以保证了每一个加操作都不会丢失</p><p>arm 的 exclusive 标记是通过 exclusive monitor 模块实现的，在老的 x86 架构下实现类似 ldrex&#x2F;strex 功能会通过锁总线实现导致效率低下</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;使用原子操作典型例子众所周知就是多个线程操作同一个全局变量 &lt;code&gt;i++&lt;/code&gt;, 由于对应的汇编指令并不只是一条，在并发访问下可能出现多个线程中的多条指令交错导致部分加操作丢失。全局变量&lt;code&gt;i&lt;/code&gt;属于临界资源，当然可以使用加锁的方式保护临界资源，但是加锁开销比较大，用在这里有些杀鸡焉用牛刀。最好的方式是使用内核提供的&lt;code&gt;atomic_t&lt;/code&gt;类型的原子变量来进行原子操作。&lt;/p&gt;
&lt;p&gt;笔者本次通过源码来窥探原子操作的底层实现, 本次仍以 arm 架构下的 kernel 2.6.35 版本为源码来源。&lt;/p&gt;
&lt;p&gt;首先来看下&lt;code&gt;atomic_t&lt;/code&gt;的定义, 仅仅只是一个&lt;code&gt;int&lt;/code&gt;类型变量&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;include&amp;#x2F;linux&amp;#x2F;types.h&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;class&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;struct&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;keyword&quot;&gt;int&lt;/span&gt; counter;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125; &lt;span class=&quot;keyword&quot;&gt;atomic_t&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
  </entry>
  
  <entry>
    <title>AF_XDP socket 介绍</title>
    <link href="https://www.caiyifan.cn/p/5cef6b86.html"/>
    <id>https://www.caiyifan.cn/p/5cef6b86.html</id>
    <published>2021-05-07T17:29:06.000Z</published>
    <updated>2024-05-01T10:34:38.732Z</updated>
    
    <content type="html"><![CDATA[<h2 id="eBPF"><a href="#eBPF" class="headerlink" title="eBPF"></a>eBPF</h2><p>eBPF 相当于在内核中有一个运行特定字节码的虚拟机，可以动态将 eBPF 字节码注入进内核。eBPF 程序会 attach 到指定的内核代码路径中，当执行到该代码路径时，会执行对应的 eBPF 程序</p><h2 id="XDP"><a href="#XDP" class="headerlink" title="XDP"></a>XDP</h2><p>XDP 是专门针对于网络数据，是基于 eBPF 的高性能数据链路。可以在以下三种模式运行：</p><ul><li><p>Native：工作在网络驱动早期接收队列上</p></li><li><p>Offload： 直接运行在网卡中，需要特定的智能网卡支持</p></li><li><p>Generic：对于不能支持 Native 和 Offload 模式下， 内核提供一种通用模式。该模式运行在网络协议栈处理早期，不需要特定网卡支持，但性能会远低于以上两种模式</p><a id="more"></a><p>XDP 对每个报文的处理称之为 action，支持以下action：</p></li><li><p>DROP： 在驱动层直接丢弃数据包，通常用于丢弃 DDos 攻击报文</p></li><li><p>PASS：  允许数据包进入协议栈处理，之后数据包的处理就跟传统的处理方式一样</p></li><li><p>TX：可将报文从接收到该报文的 NIC 发送出去</p></li><li><p>REDIRECT： 与 TX 模式一样，但是重定向到另一个 NIC 发送出去，或可以<strong>将数据包重定向到 AF_XDP socket 的用户空间程序 mmap 映射的内存中</strong></p></li><li><p>ABORTED：表示程序发生了异常，效果与 DROP 一样，但可以在用户空间来监控这种异常发生</p></li></ul><h2 id="AF-XDP"><a href="#AF-XDP" class="headerlink" title="AF_XDP"></a>AF_XDP</h2><p>区别于传统 socket 数据流经内核协议栈的方式，XDP 程序在网卡驱动中直接取得网卡收到的数据包，然后直接送到用户态应用程序</p><p>应用程序利用 AF_XDP 协议族的 socket 接收数据。 XDP 程序会把数据帧送到一个在用户态可以读写的内存中，用户态应可在该内存中直接完成数据包的读取和写入，整个过程是完全 zero copy</p><p><img src="https://img.caiyifan.cn/typora_picgo/xdp_socket.jpg" alt="xdp_socket"> </p><h2 id="UMEM"><a href="#UMEM" class="headerlink" title="UMEM"></a>UMEM</h2><p>使用 XDP socket 之前，需要在用户态通过 mmap 创建一段用户空间的内存，称之为 <strong>UMEM</strong>。这是一段连续的内存，被分割为若干个相同大小的 frame，每个 frame 可容纳一个数据包。</p><p>通过 socket 系统调用创建 AF_XDP socket，创建之后每个 socket 都各自分配了一个 RX ring 和 TX ring。这两个 ring 需要通过 socket 选项 XDP_RX_RING 和 XDP_TX_RING 进行注册。每个 socket 必须至少具有其中一个ring。RX 或 TX ring 存储着描述符集合，每个描述符指向 UMEM 中的一个 frame，描述符通过引用 frame 在 UMEM 中的 偏移量来引用 frame。RX 和 TX 可以共享相同的UMEM，所以一个报文无需在 RX 和 TX 之间进行拷贝。</p><p>UMEM 也包含两个 ring：Filling ring 和 Completion ring。应用程序会使用 Fill ring 下发描述符，让内核填写 RX 包数据后发送，一旦接收到报文，就绪的描述符也会被填入 RX ring，可以在用户态使用 <code>poll</code>来等待就绪描述符的到来。通过写入 Completion ring，通知内核有一个或多个数据包已经就绪，请求内核进行数据发送。</p><p><img src="https://img.caiyifan.cn/typora_picgo/xdp_umem.png" alt="xdp_umem"> </p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;eBPF&quot;&gt;&lt;a href=&quot;#eBPF&quot; class=&quot;headerlink&quot; title=&quot;eBPF&quot;&gt;&lt;/a&gt;eBPF&lt;/h2&gt;&lt;p&gt;eBPF 相当于在内核中有一个运行特定字节码的虚拟机，可以动态将 eBPF 字节码注入进内核。eBPF 程序会 attach 到指定的内核代码路径中，当执行到该代码路径时，会执行对应的 eBPF 程序&lt;/p&gt;
&lt;h2 id=&quot;XDP&quot;&gt;&lt;a href=&quot;#XDP&quot; class=&quot;headerlink&quot; title=&quot;XDP&quot;&gt;&lt;/a&gt;XDP&lt;/h2&gt;&lt;p&gt;XDP 是专门针对于网络数据，是基于 eBPF 的高性能数据链路。可以在以下三种模式运行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Native：工作在网络驱动早期接收队列上&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Offload： 直接运行在网卡中，需要特定的智能网卡支持&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generic：对于不能支持 Native 和 Offload 模式下， 内核提供一种通用模式。该模式运行在网络协议栈处理早期，不需要特定网卡支持，但性能会远低于以上两种模式&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="ebpf" scheme="https://www.caiyifan.cn/tags/ebpf/"/>
    
      <category term="xdp" scheme="https://www.caiyifan.cn/tags/xdp/"/>
    
  </entry>
  
  <entry>
    <title>上古神器 awk 笔记</title>
    <link href="https://www.caiyifan.cn/p/682c5393.html"/>
    <id>https://www.caiyifan.cn/p/682c5393.html</id>
    <published>2021-05-01T18:51:25.000Z</published>
    <updated>2024-05-01T10:34:38.736Z</updated>
    
    <content type="html"><![CDATA[<h2 id="awk基本格式"><a href="#awk基本格式" class="headerlink" title="awk基本格式"></a><code>awk</code>基本格式</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'&#123; awk program &#125;'</span> file</span><br></pre></td></tr></table></figure><ul><li><p>file  为 <code>awk</code> 要读取的文件，可以是一个或多个文件。如果不指定文件，则从标准输入中读取</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'&#123; awk program &#125;'</span> a.txt b.txt c.txt</span><br></pre></td></tr></table></figure></li><li><p><strong>单引号</strong>内的是<code>awk</code>的程序，一般使用单引号而非双引号。 <code>awk</code>是按行处理文件，内部有一个<strong>隐藏的循环</strong>，即默认下逐行读取文件并运行程序</p><blockquote><p>使用单引号原因：双引号中的<code>$</code>会被 shell 解析成 shell 变量引用，于是会进行 shell 变量替换。为了表示<code>awk</code>程序使用的变量，所以尽可能使用单引号</p></blockquote></li></ul><a id="more"></a><ul><li><p><code>awk</code> 程序中的 <code>{}</code> 表示代码块</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'&#123;print $0&#125;'</span> a.txt</span><br><span class="line">awk <span class="string">'&#123;print $0&#125;&#123;print $0; print $0&#125;'</span> a.txt</span><br></pre></td></tr></table></figure></li></ul><h2 id="BEGIN-和-END-语句块"><a href="#BEGIN-和-END-语句块" class="headerlink" title="BEGIN 和 END 语句块"></a><code>BEGIN</code> 和 <code>END</code> 语句块</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'BEGIN&#123;print "俺要开始读文件啦"&#125;&#123;print $0&#125;END&#123;print "俺处理完文件啦"&#125;'</span> a.txt</span><br></pre></td></tr></table></figure><ul><li>BEGIN 代码块: 在读取文件前行执行一次，不参与<code>awk</code>的隐藏循环</li><li>END 代码块： 在读取文件完成后执行一次，不参与<code>awk</code>的隐藏循环</li><li>main 代码块：不以<code>BEGIN</code>或<code>END</code>开头的代码块都称之为 main 代码块， main 代码块会参与 <code>awk</code> 的隐藏循环</li></ul><h2 id="awkpattern-和-action"><a href="#awkpattern-和-action" class="headerlink" title="awkpattern 和 action"></a><code>awk</code>pattern 和 action</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'</span></span><br><span class="line"><span class="string">BEGIN &#123;</span></span><br><span class="line"><span class="string">    n=3</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">/^[0-9]/ &#123;</span></span><br><span class="line"><span class="string">    print $1</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">END &#123;</span></span><br><span class="line"><span class="string">    print "end"</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">'</span> a.txt</span><br></pre></td></tr></table></figure><p><code>awk</code>语法格式为<code>pattern { action }</code>模式， 称之为<code>awk</code>rule</p><ul><li>pattern 用于筛选符合的文本行</li><li>action 表示筛选通过后执行的操作</li><li>pattern 和 action 都可省略<ul><li>省略 pattern 则不筛选数据，表示对每一行数据都执行 action</li><li>省略 {action} 表示对每一行都执行 {print}</li><li>省略 action 表示对筛选的行不做任何操作，该语法实际使用中并无意义</li></ul></li></ul><p>可以将 BEGIN 与 END 代码块看成一种特殊的 pattern{action} 代码块</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># bool pattern</span></span><br><span class="line">/regular expression/   <span class="comment"># 正则匹配，e.g., /a.*ef/&#123;action&#125;</span></span><br><span class="line">relational expression  <span class="comment"># 大小关系匹配，e.g., 3&gt;2&#123;action&#125;</span></span><br><span class="line">pattern &amp;&amp; pattern     <span class="comment"># 逻辑与</span></span><br><span class="line">pattern || pattern     <span class="comment"># 逻辑或</span></span><br><span class="line">!pattern               <span class="comment"># 逻辑反</span></span><br><span class="line">pattern ? pattern : pattern  <span class="comment"># 三目运算符</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 范围 pattern</span></span><br><span class="line">pattern1, pattern2     <span class="comment"># 范围匹配，匹配从 pattern1 到 pattern2 之间的内容</span></span><br></pre></td></tr></table></figure><h2 id="awk-读取文件"><a href="#awk-读取文件" class="headerlink" title="awk 读取文件"></a><code>awk</code> 读取文件</h2><h3 id="记录分隔符"><a href="#记录分隔符" class="headerlink" title="记录分隔符"></a>记录分隔符</h3><p><code>awk</code>读取文件时， 每读取一条记录(Record)(默认下按行读取，一行就是一条记录). 每读取一条记录，将其保存到<code>$0</code>中，然后执行一次 main 代码段。</p><p>可通过修改预定义变量<code>RS</code>来改变每次读取的记录模式，<code>RS</code>变量表示输入记录分隔符(Record Separator)，默认值为<code>\n</code></p><p><code>RS</code>一般设置在 BEGIN 代码块中，因为需要在读取文件前确定好分隔符</p><blockquote><p>注：<code>RS</code>变量作为输入记录分割符，所读取的每条记录不包含<code>RS</code>变量值</p></blockquote><ul><li><code>RS</code> 为单个字符， 则直接用该字符来分割记录</li><li><code>RS</code> 为多个字符，则将其作为正则表达式，只要匹配上正则表达式都用来分割记录<ul><li>设置预定义变量<code>IGNORECASE</code>为非零值，正则匹配时忽略大小写</li></ul></li></ul><p>特殊<code>RS</code>值解决特定需求：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">RS=<span class="string">""</span>    <span class="comment"># 按段落读取</span></span><br><span class="line">RS=<span class="string">"^$"</span>  <span class="comment"># 一次性读取所有数据, 该正则只能匹配空文件</span></span><br><span class="line">RS=<span class="string">"\n+"</span> <span class="comment"># 按行读取，但忽略所有空行</span></span><br></pre></td></tr></table></figure><p><code>awk</code>每读取一条记录时，会设置预定义变量<code>RT</code>表示记录分割符(Record  Termination)。 当<code>RS</code>为单个字符时，<code>RT的值和</code>RS<code>值相同。 当</code>RS<code>为正则表达式时，</code>RT&#96;为正则匹配的记录分隔符</p><h3 id="行号"><a href="#行号" class="headerlink" title="行号"></a>行号</h3><p><code>awk</code>读取每条记录后，将其赋值给<code>$0</code>和设置<code>RT</code>外，还会设置<code>NR</code>和<code>FNR</code>这两个预定义变量</p><ul><li><code>NR</code>:  所有文件的行号计数器</li><li><code>FNR</code>: 各个文件的行号计数器，针对于多个文件输入的情况</li></ul><h3 id="字段分割"><a href="#字段分割" class="headerlink" title="字段分割"></a>字段分割</h3><p><code>awk</code>读取每条记录后，将其赋值给<code>$0</code>，同时还会对该条记录按照预定义变量<code>FS</code>划分字段，将划分后的各个字段依次存入<code>$1</code>，<code>$2</code>，<code>$3</code> …，同时将划分好的字段数量赋值给预定义变量<code>NF</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk <span class="string">'&#123;print $NF&#125;'</span> a.txt   <span class="comment"># 输出 a.txt 的最后一列</span></span><br></pre></td></tr></table></figure><p>未完待续 ~~</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;awk基本格式&quot;&gt;&lt;a href=&quot;#awk基本格式&quot; class=&quot;headerlink&quot; title=&quot;awk基本格式&quot;&gt;&lt;/a&gt;&lt;code&gt;awk&lt;/code&gt;基本格式&lt;/h2&gt;&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;awk &lt;span class=&quot;string&quot;&gt;&#39;&amp;#123; awk program &amp;#125;&#39;&lt;/span&gt; file&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;file  为 &lt;code&gt;awk&lt;/code&gt; 要读取的文件，可以是一个或多个文件。如果不指定文件，则从标准输入中读取&lt;/p&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;awk &lt;span class=&quot;string&quot;&gt;&#39;&amp;#123; awk program &amp;#125;&#39;&lt;/span&gt; a.txt b.txt c.txt&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;单引号&lt;/strong&gt;内的是&lt;code&gt;awk&lt;/code&gt;的程序，一般使用单引号而非双引号。 &lt;code&gt;awk&lt;/code&gt;是按行处理文件，内部有一个&lt;strong&gt;隐藏的循环&lt;/strong&gt;，即默认下逐行读取文件并运行程序&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;使用单引号原因：双引号中的&lt;code&gt;$&lt;/code&gt;会被 shell 解析成 shell 变量引用，于是会进行 shell 变量替换。为了表示&lt;code&gt;awk&lt;/code&gt;程序使用的变量，所以尽可能使用单引号&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="shell" scheme="https://www.caiyifan.cn/tags/shell/"/>
    
      <category term="awk" scheme="https://www.caiyifan.cn/tags/awk/"/>
    
  </entry>
  
  <entry>
    <title>内核链表介绍</title>
    <link href="https://www.caiyifan.cn/p/12ad7292.html"/>
    <id>https://www.caiyifan.cn/p/12ad7292.html</id>
    <published>2020-07-31T07:49:22.000Z</published>
    <updated>2024-05-01T10:34:38.736Z</updated>
    
    <content type="html"><![CDATA[<p>应要求分享一下内核链表结构，故写了本blog。本文对内核链表做一个简单介绍，以及引出内核中大量使用的分离思想和数据结构的定义。</p><h2 id="传统链表的困境"><a href="#传统链表的困境" class="headerlink" title="传统链表的困境"></a>传统链表的困境</h2><p>内核中数据结构千变万化，采用传统的链表结构形式，需要为各种数据都定义出一个链表。</p><a id="more"></a><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* data1 */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">data1_list_node</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">data1</span> <span class="title">data</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">data1_list_node</span> *<span class="title">next</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">data1_list_node</span> *<span class="title">prev</span>;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* data2 */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">data2_list_node</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">data2</span> <span class="title">data</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">data2_list_node</span> *<span class="title">next</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">data2_list_node</span> *<span class="title">prev</span>;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* data3 */</span></span><br><span class="line"><span class="comment">/* ... */</span></span><br></pre></td></tr></table></figure><p>以上会出现大量定义链表结构，而在c++的模板语法下， 可以定义一个链表模板来解决这个问题</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_node</span> &#123;</span></span><br><span class="line">    T data;</span><br><span class="line">    ListNode&lt;T&gt; *next;</span><br><span class="line">    ListNode&lt;T&gt; *prev;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">list_node&lt;<span class="class"><span class="keyword">struct</span> <span class="title">data1</span>&gt; <span class="title">data1_head</span>;</span></span><br><span class="line">list_node&lt;<span class="class"><span class="keyword">struct</span> <span class="title">data2</span>&gt; <span class="title">data2_head</span>;</span></span><br><span class="line"><span class="comment">/* data3 */</span></span><br><span class="line"><span class="comment">/* ... */</span></span><br></pre></td></tr></table></figure><h2 id="数据和结构分离"><a href="#数据和结构分离" class="headerlink" title="数据和结构分离"></a>数据和结构分离</h2><p>问题的核心在于数据的千变万化，但是所要表述的结构却是统一的！那如果将统一的部分抽取出来呢？让一切的一切都尘归尘，土归土。</p><p><img src="https://img.caiyifan.cn/kernel_list1.png"> </p><h2 id="内核链表"><a href="#内核链表" class="headerlink" title="内核链表"></a>内核链表</h2><p>内核链表正是采用了如上的思想进行设计的，内核链表位于内核代码的<code>include/linux/list.h</code>中，该链表定义为双向循环链表，所有的相关操作都定义在该头文件中，该文件中每个函数极为简洁。截取片段如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> *<span class="title">next</span>；</span></span><br><span class="line"><span class="class">    <span class="title">struct</span> <span class="title">list_head</span> *<span class="title">prev</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIST_HEAD_INIT(name) &#123; &amp;(name), &amp;(name) &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> LIST_HEAD(name) \</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">name</span> = <span class="title">LIST_HEAD_INIT</span>(<span class="title">name</span>)</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">static</span> <span class="title">inline</span> <span class="title">void</span> __<span class="title">list_add</span>(<span class="title">struct</span> <span class="title">list_head</span> *<span class="title">new</span>,</span></span><br><span class="line"><span class="class">      <span class="title">struct</span> <span class="title">list_head</span> *<span class="title">prev</span>,</span></span><br><span class="line"><span class="class">      <span class="title">struct</span> <span class="title">list_head</span> *<span class="title">next</span>)</span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    next-&gt;prev = <span class="keyword">new</span>;</span><br><span class="line">    <span class="keyword">new</span>-&gt;next = next;</span><br><span class="line">    <span class="keyword">new</span>-&gt;prev = prev;</span><br><span class="line">    prev-&gt;next = <span class="keyword">new</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">list_add</span><span class="params">(struct list_head *<span class="keyword">new</span>, struct list_head *head)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">__list_add(<span class="keyword">new</span>, head, head-&gt;next);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用内核链表的方式，将链表节点嵌入到数据结构体中。如内核驱动中对misc设备的描述</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">miscdevice</span>  &#123;</span></span><br><span class="line">    <span class="keyword">int</span> minor;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *name;</span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">file_operations</span> *<span class="title">fops</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">list</span>;</span>    <span class="comment">/* 用内核链表管理所有注册在内核中的misc设备 */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">device</span> *<span class="title">parent</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">device</span> *<span class="title">this_device</span>;</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *nodename;</span><br><span class="line">    <span class="keyword">mode_t</span> mode;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>核心问题</strong>：通过遍历所有的<code>struct list_head</code>即可拿到所有数据的list成员，而真正需要的是数据，那么如何从list成员获取到<code>struct miscdevice</code>, 最直接的做法是将list成员放置在<code>struct miscdevice</code>最开始处，只需指针强转即可获得，而如上所知该成员并未放到结构体的最开头，内核中的做法是可以放在任意位置，解析时使用了一个很强大的宏来进行获取。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> offsetof(TYPE, MEMBER) ((size_t) &amp;((TYPE *)0)-&gt;MEMBER)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> container_of(ptr, type, member) (&#123;    \</span></span><br><span class="line"><span class="function"><span class="keyword">const</span> <span class="title">typeof</span><span class="params">( ((type *)<span class="number">0</span>)-&gt;member )</span> *__mptr </span>= (ptr);    \ <span class="comment">/* 1 */</span></span><br><span class="line">(type *)( (<span class="keyword">char</span> *)__mptr - offsetof(type,member) );&#125;)     <span class="comment">/* 2 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 上面 typeof 为 GNU gcc 扩展语法，可用来获取一个变量的类型 */</span></span><br><span class="line"><span class="comment">/* 以上1代码去掉后，将2中的 __mptr替换为ptr也可正常工作。看似无用，实则用于编译期间类型检查 */</span></span><br><span class="line"><span class="comment">/*******************************************************************/</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> list_entry(ptr, type, member) \</span></span><br><span class="line">container_of(ptr, type, member)</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> list_first_entry(ptr, type, member) \</span></span><br><span class="line">list_entry((ptr)-&gt;next, type, member)</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> list_next_entry(pos, member) \</span></span><br><span class="line">list_entry((pos)-&gt;member.next, typeof(*(pos)), member)</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 内核链表的遍历接口 */</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> list_for_each_entry(pos, head, member)\</span></span><br><span class="line"><span class="keyword">for</span> (pos = list_first_entry(head, typeof(*pos), member);\</span><br><span class="line">     &amp;pos-&gt;member != (head);\</span><br><span class="line">     pos = list_next_entry(pos, member))</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>内核中大量使用了该思想，凡是在物理内存中离散分布的结构，均采用此思想将结构嵌入到具体数据中实现数据的结构组织，例如在 epoll 底层和进程调度使用到的 rbtree 。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rb_node</span> &#123;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span>  __rb_parent_color;   <span class="comment">/* 包含 parent 指针以及 color 信息 */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">rb_node</span> *<span class="title">rb_right</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">rb_node</span> *<span class="title">rb_left</span>;</span></span><br><span class="line">&#125; __attribute__((aligned(<span class="keyword">sizeof</span>(<span class="keyword">long</span>))));</span><br></pre></td></tr></table></figure><p>在总线设备驱动模型中，除了纵向的分层外还有横向的数据分离。包括设备和驱动的分离、主机和外设驱动的分离。</p><p><img src="https://img.caiyifan.cn/kernel_list2.png"> </p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;应要求分享一下内核链表结构，故写了本blog。本文对内核链表做一个简单介绍，以及引出内核中大量使用的分离思想和数据结构的定义。&lt;/p&gt;
&lt;h2 id=&quot;传统链表的困境&quot;&gt;&lt;a href=&quot;#传统链表的困境&quot; class=&quot;headerlink&quot; title=&quot;传统链表的困境&quot;&gt;&lt;/a&gt;传统链表的困境&lt;/h2&gt;&lt;p&gt;内核中数据结构千变万化，采用传统的链表结构形式，需要为各种数据都定义出一个链表。&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="c" scheme="https://www.caiyifan.cn/tags/c/"/>
    
  </entry>
  
  <entry>
    <title>OpenResty入门笔记</title>
    <link href="https://www.caiyifan.cn/p/17a7e047.html"/>
    <id>https://www.caiyifan.cn/p/17a7e047.html</id>
    <published>2020-06-03T17:34:04.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<h2 id="OpenResty-简介"><a href="#OpenResty-简介" class="headerlink" title="OpenResty 简介"></a>OpenResty 简介</h2><p>OpenResty 是一个高性能 Web 平台，打包了标准的 Nginx 核心，集成了很多常用的第三方模块。简单理解，<strong>OpenResty 是 Nginx 的加强</strong>。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>安装方法跟 Nginx 基本一致，笔者采用的是 Ubuntu 20.04 的环境。</p><ul><li>安装依赖库</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install libpcre3-dev    <span class="comment"># pcre库: 解析正则表达式</span></span><br><span class="line">sudo apt install libz-dev        <span class="comment"># zlib库: 压缩/解压缩功能</span></span><br><span class="line">sudo apt install libssl-dev      <span class="comment"># openssl库: ssl功能相关库，用于网站加密通讯</span></span><br></pre></td></tr></table></figure><a id="more"></a><ul><li>下载源码并解压</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">wget https://openresty.org/download/openresty-1.15.8.3.tar.gz</span><br><span class="line">tar xvf openresty-1.15.8.3.tar.gz</span><br></pre></td></tr></table></figure><ul><li>编译安装<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> openresty-1.15.8.3</span><br><span class="line">./configure</span><br><span class="line">make -j4 &amp;&amp; sudo make install</span><br></pre></td></tr></table></figure></li></ul><p>安装完后的路径位于<code>/usr/local/openresty/</code>下，可以看到<code>bin/openresty</code> 指向的正是 nginx</p><p><img src="https://img.caiyifan.cn/openresty_path.png"></p><p>为了方便使用，可将<code>/usr/local/openresty/bin</code>加入到环境变量中，在<code>~/.bashrc</code>或<code>~/.zshrc</code>中添加</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=/usr/<span class="built_in">local</span>/openresty/bin:<span class="variable">$PATH</span></span><br></pre></td></tr></table></figure><h2 id="启停"><a href="#启停" class="headerlink" title="启停"></a>启停</h2><p>启动和停止命令跟 Nginx 一致</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 启动</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/openresty</span><br><span class="line"><span class="comment"># 停止</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/openresty -s stop</span><br><span class="line"><span class="comment"># 热重启</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/openresty -s reload</span><br></pre></td></tr></table></figure><h2 id="opm常用命令"><a href="#opm常用命令" class="headerlink" title="opm常用命令"></a>opm常用命令</h2><p>node.js 有 npm 包管理工具，openresy 同样也有一个类似工具叫 opm</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">/usr/<span class="built_in">local</span>/openresty/bin/opm list        <span class="comment"># 列出当前已安装的组件</span></span><br><span class="line">/usr/<span class="built_in">local</span>/openresty/bin/opm search http <span class="comment"># 以关键字检索相关组件</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/opm get ledgetech/lua-resty-http   <span class="comment"># 安装组件</span></span><br><span class="line">/usr/<span class="built_in">local</span>/openresty/bin/opm info ledgetech/lua-resty-http       <span class="comment"># 查看已安装组件信息</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/opm ledgetech/lua-resty-http       <span class="comment"># 移除组件</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/opm ledgetech/lua-resty-http       <span class="comment"># 更新已安装组件</span></span><br><span class="line">sudo /usr/<span class="built_in">local</span>/openresty/bin/opm update                         <span class="comment"># 更新所有安装组件</span></span><br></pre></td></tr></table></figure><h2 id="参考手册"><a href="#参考手册" class="headerlink" title="参考手册"></a>参考手册</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/usr/<span class="built_in">local</span>/openresty/bin/restydoc nginx           <span class="comment"># 查看nginx说明</span></span><br><span class="line">/usr/<span class="built_in">local</span>/openresty/bin/restydoc -s proxy_pass   <span class="comment"># -s 查看手册里小节</span></span><br></pre></td></tr></table></figure><p>稍后补充剩下内容QAQ</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;OpenResty-简介&quot;&gt;&lt;a href=&quot;#OpenResty-简介&quot; class=&quot;headerlink&quot; title=&quot;OpenResty 简介&quot;&gt;&lt;/a&gt;OpenResty 简介&lt;/h2&gt;&lt;p&gt;OpenResty 是一个高性能 Web 平台，打包了标准的 Nginx 核心，集成了很多常用的第三方模块。简单理解，&lt;strong&gt;OpenResty 是 Nginx 的加强&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;安装&quot;&gt;&lt;a href=&quot;#安装&quot; class=&quot;headerlink&quot; title=&quot;安装&quot;&gt;&lt;/a&gt;安装&lt;/h2&gt;&lt;p&gt;安装方法跟 Nginx 基本一致，笔者采用的是 Ubuntu 20.04 的环境。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装依赖库&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight bash&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;sudo apt install libpcre3-dev    &lt;span class=&quot;comment&quot;&gt;# pcre库: 解析正则表达式&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sudo apt install libz-dev        &lt;span class=&quot;comment&quot;&gt;# zlib库: 压缩/解压缩功能&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;sudo apt install libssl-dev      &lt;span class=&quot;comment&quot;&gt;# openssl库: ssl功能相关库，用于网站加密通讯&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="openresty" scheme="https://www.caiyifan.cn/tags/openresty/"/>
    
      <category term="nginx" scheme="https://www.caiyifan.cn/tags/nginx/"/>
    
      <category term="lua" scheme="https://www.caiyifan.cn/tags/lua/"/>
    
  </entry>
  
  <entry>
    <title>rk3399-android9.0-secureboot介绍</title>
    <link href="https://www.caiyifan.cn/p/49010c0.html"/>
    <id>https://www.caiyifan.cn/p/49010c0.html</id>
    <published>2020-01-09T11:58:04.000Z</published>
    <updated>2024-05-01T10:34:38.736Z</updated>
    
    <content type="html"><![CDATA[<p>RK完整的Secureboot包括两部分，第一部分为Linux的Secureboot，第二部分为Android特有的AVB（Android Verified Boot）。开启了Secureboot的设备，会在启动时逐级校验各分区，一旦某一级校验不通过，则设备就无法启动。</p><p>Secureboot分为安全性校验与完整性校验。</p><ul><li><strong>安全性校验</strong>: 为公钥的校验，借助于芯片的一次性可编程安全存储模块（OTP 或 efuse）, 在rk3399上称为efuse。该检验流程为从efuse中读取公钥 hash，与计算的公钥 hash 先做对比，如果相同，则再将公钥用于解密固件 hash。</li><li><strong>完整性校验</strong>: 为校验固件的完整性，计算固件的 hash 与用公钥解密出来的 hash 对比是否一致。</li></ul><a id="more"></a><p>AVB阶段安全性校验和完整性校验需要依赖于vbmeta.img，相关的公钥及描述信息存储在vbmeta.img中。</p><h2 id="Secureboot流程"><a href="#Secureboot流程" class="headerlink" title="Secureboot流程"></a>Secureboot流程</h2><p>Secureboot涉及到的两级：maskrom —&gt; miniloader、miniloader —&gt; uboot、uboot—&gt; kernel，但在Android上Secureboot部分只实现前两级，uboot—&gt; kernel以及之后的启动校验交由AVB进行处理。以下以maskrom —&gt; miniloader为例讲解Secureboot流程。</p><h3 id="pc加密过程"><a href="#pc加密过程" class="headerlink" title="pc加密过程"></a>pc加密过程</h3><p><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-2683615680573215" data-ad-slot="7267550779"></ins></p><script>     (adsbygoogle = window.adsbygoogle || []).push({});</script><p>使用rk提供的签名工具（rk_sign_tool）进行签名步骤及原理如下</p><p>1.该工具首先会产生一对密钥对，即：public key和privete key</p><p>2.使用SHA256计算镜像的hash，并使用privete key对镜像的hash进行RSA2048签名</p><p>3.使用SHA256计算出public key的hash</p><p>4.将镜像+第2步中签名+public key进行打包形成新的镜像</p><p>5.第3步中的hash将会烧写到efuse中</p><p><img src="https://img.caiyifan.cn/loader.png" alt="loader"> </p><h3 id="设备解密过程"><a href="#设备解密过程" class="headerlink" title="设备解密过程"></a>设备解密过程</h3><p>1.首先从新的镜像中获取public key计算hash值</p><p>2.从efuse中读取public key的hash值进行对比，如果相同则继续，否则启动失败</p><p>3.从镜像中获取签名，然后使用RSA2048计算hash</p><p>4.使用SHA256计算镜像的hash值，与第三步计算出来的hash进行对比，相同则继续，否则启动失败</p><p><img src="https://img.caiyifan.cn/secureboot_flow.png" alt="secureboot_flow"> </p><h2 id="AVB流程"><a href="#AVB流程" class="headerlink" title="AVB流程"></a>AVB流程</h2><p>AVB的核心结构为vbmeta，vbmeta分区存储了<code>boot</code>分区的hash，而对于<code>system</code>和<code>vender</code>分区，哈希树紧随在各自的分区数据之后，vbmeta分区只保存哈希树描述符中哈希树的根哈希(root hash)，盐(salt)和偏移量(offset)。</p><p><img src="https://img.caiyifan.cn/vbmeta.webp" alt="vbmeta"> </p><p>uboot启动后，首先需要进行vbmeta的合法性验证，即安全性校验，RK的做法是将验证vbmeta的公钥信息经过trust加密后存储在security分区，其中trust分区的安全性又是受efuse验证的Secureboot进行保证的。uboot启动kernel前先验签vbmeta，vbmeta可信后，再取出vbmeta中的相关信息来进行其他分区的校验。</p><h2 id="Merkle-Tree"><a href="#Merkle-Tree" class="headerlink" title="Merkle Tree"></a>Merkle Tree</h2><h3 id="hash-list"><a href="#hash-list" class="headerlink" title="hash list"></a>hash list</h3><p>AVB在验证system分区时采用了动态校验的方式进行完整性校验，所以采用了分块进行hash的方式来校验。那么如何存储该数据块的hash，直接采用最暴力的方式，自然而然想到的是使用一个hash列表来存储。但是使用Hash列表来保证数据块的正确性还不够，黑客修改数据的同时，如果将Hash列表也对应修改了，这就无法保证数据块的正确性了。所以需要引入一个顶层的hash，将hash列表里的每个hash字符串拼在一起后再做一次hash运算，最后的hash值称之为root hash，只要保证该root hash的正确性即可。</p><p><img src="https://img.caiyifan.cn/avb_hash_list.png"></p><p>但是AVB并未采用该简单结构。假设system的大小为1GB，数据块大小为4KB，则有26万个数据块，对应着hash列表就有26万个元素。AVB进行运行时校验，设备运行时读到哪个块就会对哪个块校验，将需要校验的块进行hash后更新具有26万个元素的hash列表中的一个元素后计算root hash，再与vbmeta中root hash作对比来判断数据是否正确。这个效率可想而知非常糟糕，所以AVB采用了一种称为<strong>Merkle Tree</strong>的树结构。</p><h3 id="hash-tree"><a href="#hash-tree" class="headerlink" title="hash tree"></a>hash tree</h3><p><strong>Merkle Tree</strong>，通常也被称作Hash Tree，其叶子节点是数据块或者文件的hash值。非叶节点是其对应子节点串联字符串的hash。Hash 列表可以看作一种特殊的Merkle Tree，即树高为2的多叉Merkle Tree。</p><p><img src="https://img.caiyifan.cn/merkle_tree.png"></p><p>建树过程：<br>在树的最底层，和hash列表一样，将数据分成若干个小的数据块，有相应的hash与之对应。但是往上走，并不是直接去计算root hash，而是把相邻的两个hash合并成一个字符串，然后计算这个字符串的hash，将这个hash值作为两个节点的父节点。按照同样的方式，可以得到数目更少的新一级hash，最终必然形成一棵树，树的根节点即为root hash。</p><p>Merkle Tree的结构非常易于同步大文件或文件集合，按照查找树的查找思路，从root hash开始比对，依次往下查找到叶子节点即能找到需要重新同步或下载的数据块，其时间复杂度为O(logN)，如果采用hash列表的方式，需要完整进行一遍遍历才能定位到不同的数据块，其时间复杂度为O(N)。Merkle Tree在数字签名、P2P网络、区块链等技术都有应用。回到本文介绍的AVB，AVB在运行时校验某一块时只需要更新Merkle Tree的一个分支即可计算出hash root，其运算时间比hash列表大大减少。在Android9上使用avbtool的python代码进行hash tree的生成，该算法跟上文描述略有不同，当1G的system进行4KB大小的划分，其生成的hash tree只有四层(包括root hash这一层)，所以运行时计算hash只要沿着这个四层树的分支计算即可，可想而知效率大大提升。</p><h3 id="avbtool中建树源码分析"><a href="#avbtool中建树源码分析" class="headerlink" title="avbtool中建树源码分析"></a>avbtool中建树源码分析</h3><p>以下分析一下Android9上hash tree的生成过程，涉及到用Python实现的avbtool源码的两个函数：<code>calc_hash_level_offsets</code>，<code>generate_hash_tree</code></p><blockquote><p>calc_hash_level_offsets</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">calc_hash_level_offsets</span><span class="params">(image_size, block_size, digest_size)</span>:</span></span><br><span class="line">  <span class="string">"""Calculate the offsets of all the hash-levels in a Merkle-tree.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  Arguments:</span></span><br><span class="line"><span class="string">    image_size: The size of the image to calculate a Merkle-tree for.</span></span><br><span class="line"><span class="string">    block_size: The block size, e.g. 4096.</span></span><br><span class="line"><span class="string">    digest_size: The size of each hash, e.g. 32 for SHA-256.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  Returns:</span></span><br><span class="line"><span class="string">    A tuple where the first argument is an array of offsets and the</span></span><br><span class="line"><span class="string">    second is size of the tree, in bytes.</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  level_offsets = []  <span class="comment"># 用来存储每一层在bytearray中的偏移</span></span><br><span class="line">  level_sizes = []    <span class="comment"># 每一层占用的大小</span></span><br><span class="line">  tree_size = <span class="number">0</span>       <span class="comment"># 树的大小</span></span><br><span class="line"></span><br><span class="line">  num_levels = <span class="number">0</span>      <span class="comment"># 树的层数</span></span><br><span class="line">  <span class="comment"># size用于计算时表示当前层的下一层的数据大小，从第0层（计算数据块hash）开始，</span></span><br><span class="line">  <span class="comment"># 所以初始值为image的大小</span></span><br><span class="line">  size = image_size   </span><br><span class="line">  <span class="keyword">while</span> size &gt; block_size:</span><br><span class="line">    <span class="comment"># 计算当前层数据需要多少个块</span></span><br><span class="line">    num_blocks = (size + block_size - <span class="number">1</span>) / block_size</span><br><span class="line">    <span class="comment"># round_to_multiple函数用来将第一个参数舍入到最接近第二个参数的倍数</span></span><br><span class="line">    <span class="comment"># 在这里就是对齐到block_size的整数倍</span></span><br><span class="line">    <span class="comment"># 计算当前层的hash digest需要占用的大小</span></span><br><span class="line">    level_size = round_to_multiple(num_blocks * digest_size, block_size)</span><br><span class="line"></span><br><span class="line">    level_sizes.append(level_size)</span><br><span class="line">    tree_size += level_size</span><br><span class="line">    num_levels += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 循环往上计算，所以更新size为当前层，用于计算上一层</span></span><br><span class="line">    size = level_size</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 计算每一层在bytearray中的偏移  </span></span><br><span class="line">  <span class="keyword">for</span> n <span class="keyword">in</span> range(<span class="number">0</span>, num_levels):</span><br><span class="line">    offset = <span class="number">0</span></span><br><span class="line">    <span class="keyword">for</span> m <span class="keyword">in</span> range(n + <span class="number">1</span>, num_levels):</span><br><span class="line">      offset += level_sizes[m]</span><br><span class="line">    level_offsets.append(offset)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> level_offsets, tree_size</span><br></pre></td></tr></table></figure><p>Android9上将hash tree存储在bytearray中，所以需要事先计算好树的每一层在bytearray中的偏移，以及整个树需要多长的bytearray存储。注意，hash tree的建树过程上自下往上的。其实从<code>calc_hash_level_offsets</code>函数就可大致看出Android上hash tree的存储形态了，但更为形象的存储结构还是需要看<code>generate_hash_tree</code>函数。</p><blockquote><p>generate_hash_tree</p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">generate_hash_tree</span><span class="params">(image, image_size, block_size, hash_alg_name, salt,</span></span></span><br><span class="line"><span class="function"><span class="params">                       digest_padding, hash_level_offsets, tree_size)</span>:</span></span><br><span class="line">  <span class="string">"""Generates a Merkle-tree for a file.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  Args:</span></span><br><span class="line"><span class="string">    image: The image, as a file.</span></span><br><span class="line"><span class="string">    image_size: The size of the image.</span></span><br><span class="line"><span class="string">    block_size: The block size, e.g. 4096.</span></span><br><span class="line"><span class="string">    hash_alg_name: The hash algorithm, e.g. 'sha256' or 'sha1'.</span></span><br><span class="line"><span class="string">    salt: The salt to use.</span></span><br><span class="line"><span class="string">    digest_padding: The padding for each digest.</span></span><br><span class="line"><span class="string">    hash_level_offsets: The offsets from calc_hash_level_offsets().</span></span><br><span class="line"><span class="string">    tree_size: The size of the tree, in number of bytes.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">  Returns:</span></span><br><span class="line"><span class="string">    A tuple where the first element is the top-level hash and the</span></span><br><span class="line"><span class="string">    second element is the hash-tree.</span></span><br><span class="line"><span class="string">  """</span></span><br><span class="line">  hash_ret = bytearray(tree_size)</span><br><span class="line">  hash_src_offset = <span class="number">0</span></span><br><span class="line">  hash_src_size = image_size</span><br><span class="line">  level_num = <span class="number">0</span></span><br><span class="line">  <span class="keyword">while</span> hash_src_size &gt; block_size:</span><br><span class="line">    level_output = <span class="string">''</span></span><br><span class="line">    remaining = hash_src_size</span><br><span class="line">    <span class="keyword">while</span> remaining &gt; <span class="number">0</span>:</span><br><span class="line">      hasher = hashlib.new(name=hash_alg_name, string=salt)</span><br><span class="line">      <span class="comment"># Only read from the file for the first level - for subsequent</span></span><br><span class="line">      <span class="comment"># levels, access the array we're building.</span></span><br><span class="line">      <span class="comment"># 第0层直接按照block_size读取image来进行hash</span></span><br><span class="line">      <span class="keyword">if</span> level_num == <span class="number">0</span>:</span><br><span class="line">        image.seek(hash_src_offset + hash_src_size - remaining)</span><br><span class="line">        data = image.read(min(remaining, block_size))</span><br><span class="line">      <span class="comment"># 第0层之上的每一层都由取其下一层来进行hash，eg: 将第m-1层的数据分块hash后生成m层数据</span></span><br><span class="line">      <span class="keyword">else</span>:</span><br><span class="line">        offset = hash_level_offsets[level_num - <span class="number">1</span>] + hash_src_size - remaining</span><br><span class="line">        <span class="comment"># 以block_size为单位进行分块</span></span><br><span class="line">        data = hash_ret[offset:offset + block_size]</span><br><span class="line">      hasher.update(data)</span><br><span class="line"></span><br><span class="line">      remaining -= len(data)</span><br><span class="line">      <span class="keyword">if</span> len(data) &lt; block_size:</span><br><span class="line">        hasher.update(<span class="string">'\0'</span> * (block_size - len(data)))</span><br><span class="line">      level_output += hasher.digest()</span><br><span class="line">      <span class="keyword">if</span> digest_padding &gt; <span class="number">0</span>:</span><br><span class="line">        level_output += <span class="string">'\0'</span> * digest_padding</span><br><span class="line"></span><br><span class="line">    padding_needed = (round_to_multiple(</span><br><span class="line">        len(level_output), block_size) - len(level_output))</span><br><span class="line">    level_output += <span class="string">'\0'</span> * padding_needed</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Copy level-output into resulting tree.</span></span><br><span class="line">    offset = hash_level_offsets[level_num]</span><br><span class="line">    hash_ret[offset:offset + len(level_output)] = level_output</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Continue on to the next level.</span></span><br><span class="line">    hash_src_size = len(level_output)</span><br><span class="line">    level_num += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">  <span class="comment"># 建树完成后，单独计算root hash</span></span><br><span class="line">  hasher = hashlib.new(name=hash_alg_name, string=salt)</span><br><span class="line">  hasher.update(level_output)</span><br><span class="line">  <span class="keyword">return</span> hasher.digest(), hash_ret</span><br></pre></td></tr></table></figure><p>通过<code>calc_hash_level_offsets</code>函数计算好偏移和大小后，即可将参数传递给<code>generate_hash_tree</code>函数来建树了。 从建树代码的循环过程可以看出，该树的实现是将生成的hash拼接在一起作为这一层的数据，然后分块进行hash后再拼接在一起给到父层，而不是之前的描述Merkle Tree的两两子节点合并后计算hash作为父节点。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;RK完整的Secureboot包括两部分，第一部分为Linux的Secureboot，第二部分为Android特有的AVB（Android Verified Boot）。开启了Secureboot的设备，会在启动时逐级校验各分区，一旦某一级校验不通过，则设备就无法启动。&lt;/p&gt;
&lt;p&gt;Secureboot分为安全性校验与完整性校验。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;安全性校验&lt;/strong&gt;: 为公钥的校验，借助于芯片的一次性可编程安全存储模块（OTP 或 efuse）, 在rk3399上称为efuse。该检验流程为从efuse中读取公钥 hash，与计算的公钥 hash 先做对比，如果相同，则再将公钥用于解密固件 hash。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完整性校验&lt;/strong&gt;: 为校验固件的完整性，计算固件的 hash 与用公钥解密出来的 hash 对比是否一致。&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="python" scheme="https://www.caiyifan.cn/tags/python/"/>
    
      <category term="rk3399" scheme="https://www.caiyifan.cn/tags/rk3399/"/>
    
      <category term="android" scheme="https://www.caiyifan.cn/tags/android/"/>
    
  </entry>
  
  <entry>
    <title>Express入门笔记</title>
    <link href="https://www.caiyifan.cn/p/258d0040.html"/>
    <id>https://www.caiyifan.cn/p/258d0040.html</id>
    <published>2019-11-21T14:10:32.000Z</published>
    <updated>2024-05-01T10:34:38.734Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Node-js简述"><a href="#Node-js简述" class="headerlink" title="Node.js简述"></a>Node.js简述</h2><p>Node.js是基于chrome浏览器中的v8引擎而构建的js运行时环境, 并提供了一系列的工具模块和一个包管理工具npm. Node脱离于浏览器运行, 并提供了一系列自带的os相关接口, 从而使其能像传统后端语言一样操作文件、获取os相关信息等.</p><ul><li><a href="https://nodejs.org/en/" target="_blank" rel="external nofollow noopener noreferrer">node.js官网</a></li><li><a href="http://nodejs.cn/" target="_blank" rel="external nofollow noopener noreferrer">node.js中文网</a></li><li><a href="https://www.npmjs.com/" target="_blank" rel="external nofollow noopener noreferrer">npm官网</a></li></ul><a id="more"></a><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install nodejs         <span class="comment"># 安装nodejs</span></span><br><span class="line">sudo apt-get install npm            <span class="comment"># 安装npm (node package manager)</span></span><br><span class="line">node -v                             <span class="comment"># 查看node版本</span></span><br><span class="line">npm -v                              <span class="comment"># 查看npm版本</span></span><br></pre></td></tr></table></figure><h3 id="npm常用命令"><a href="#npm常用命令" class="headerlink" title="npm常用命令"></a>npm常用命令</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">npm <span class="built_in">help</span>                             <span class="comment"># 查看帮助</span></span><br><span class="line">npm 命令 <span class="built_in">help</span></span><br><span class="line">npm config list                      <span class="comment"># 查看npm配置信息</span></span><br><span class="line">npm install --global npm             <span class="comment"># 升级npm</span></span><br><span class="line">npm init                             <span class="comment"># 初始化项目(生成package.json文件)      </span></span><br><span class="line">npm install                          <span class="comment"># 根据package.json文件下载安装所有node依赖包</span></span><br><span class="line">npm install 包名                      <span class="comment"># 下载安装node包</span></span><br><span class="line">npm install 包名@x.x.x                <span class="comment"># 下载安装指定版本的node包</span></span><br><span class="line">npm install 包名 --save               <span class="comment"># 下载安装node包且保存依赖性到package.json中</span></span><br><span class="line">npm uninstall 包名                    <span class="comment"># 卸载node包, 保留package.json中的依赖性</span></span><br><span class="line">npm uninstall 包名 --save             <span class="comment"># 卸载node包并清除package.json中的依赖性</span></span><br></pre></td></tr></table></figure><h3 id="安装使用cnpm"><a href="#安装使用cnpm" class="headerlink" title="安装使用cnpm"></a>安装使用cnpm</h3><p><code>npm</code>会去国外服务器下载包, 淘宝在国内做了完整的<code>npmjs.org</code>镜像, 可以用<code>cnpm</code>代替<code>npm</code>下载包.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装cnpm</span></span><br><span class="line">sudo npm install --global cnpm</span><br><span class="line"></span><br><span class="line"><span class="comment"># 之后使用cnpm代替npm进行install</span></span><br><span class="line">cnpm install 包名</span><br></pre></td></tr></table></figure><p>或者仍然使用<code>npm</code>, 但指定使用淘宝的镜像源进行下载</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 每次下载都指定--registry参数</span></span><br><span class="line">npm install 包名 --registry=https://registry.npm.taobao.org</span><br><span class="line"></span><br><span class="line"><span class="comment"># 或者直接将该选项加入配置文件中</span></span><br><span class="line">npm config <span class="built_in">set</span> registry https://registry.npm.taobao.org</span><br></pre></td></tr></table></figure><h3 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h3><p>Node.js中的模块概念, 类似于python中的模块, 一个js文件即是一个模块.</p><h4 id="加载模块"><a href="#加载模块" class="headerlink" title="加载模块"></a>加载模块</h4><p>通过<code>require</code>函数加载模块, 加载模块时会去执行模块内的代码. Node.js加载模块跟python一样, 只有第一次加载时会去执行模块内的代码后将其加载到内存中, 随后再去加载仅仅是将在内存中存在的模块增加一次引用而已, 并不会再去执行模块内代码.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 加载自定义模块, 一个js文件就是一个模块, 去掉后缀名即模块名</span></span><br><span class="line"><span class="keyword">const</span> myModule = <span class="built_in">require</span>(<span class="string">'./cai.js'</span>)      <span class="comment">// 加载当前路径下的cai模块</span></span><br><span class="line"><span class="keyword">const</span> myModlue  = <span class="built_in">require</span>(<span class="string">'./cai'</span>)        <span class="comment">// 与上一句等价</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载内置模块: 当require参数不含路径时, 表示加载内置模块</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)          <span class="comment">// 加载file system模块</span></span><br><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>)      <span class="comment">// 加载http模块</span></span><br></pre></td></tr></table></figure><h4 id="对外导出变量"><a href="#对外导出变量" class="headerlink" title="对外导出变量"></a>对外导出变量</h4><p>Node.js是模块作用域, 各模块之间相互隔离, 如果需要将模块内变量暴露出去,  则需要通过node的内置对象<code>module.exports</code>导出.  <code>require</code>函数的返回值即是导入的模块的<code>module.exports</code>对象.</p><blockquote><p>cai.js</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> add = <span class="function"><span class="keyword">function</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line">&#125; </span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> str = <span class="string">'hello world'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过exports对象导出add函数和str变量</span></span><br><span class="line"><span class="built_in">module</span>.exports.add = add</span><br><span class="line"><span class="built_in">module</span>.exports.str = str</span><br></pre></td></tr></table></figure><blockquote><p>hello.js</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// require返回值即是cai.js中的module.exports对象</span></span><br><span class="line"><span class="keyword">const</span> cai = <span class="built_in">require</span>(<span class="string">'./cai'</span>)</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 通过module.exports对象即可访问到cai模块中的add函数和str变量</span></span><br><span class="line"><span class="keyword">let</span> res = cai.add(<span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line"><span class="built_in">console</span>.log(res)</span><br><span class="line"><span class="built_in">console</span>.log(cai.str)</span><br></pre></td></tr></table></figure><blockquote><p>注: 为了使用方便, node内置<code>exports = module.exports</code>, 所以也可以使用<code>exports</code>对象暴露, 但如果需要让<code>require</code>函数返回自定义值, 则必须向<code>module.exports</code>赋值而不是<code>exports</code></p></blockquote><h2 id="Express"><a href="#Express" class="headerlink" title="Express"></a>Express</h2><p>express是基于node.js的web框架, 是node.js的一个第三方模块.   </p><ul><li><a href="http://expressjs.com/" target="_blank" rel="external nofollow noopener noreferrer">express官网</a></li><li><a href="http://www.expressjs.com.cn/" target="_blank" rel="external nofollow noopener noreferrer">express中文官网</a></li></ul><h3 id="安装-1"><a href="#安装-1" class="headerlink" title="安装"></a>安装</h3> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install express --save</span><br></pre></td></tr></table></figure><h3 id="server-demo"><a href="#server-demo" class="headerlink" title="server demo"></a>server demo</h3><blockquote><p>express_demo.js</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>)</span><br><span class="line">                 </span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line">                 </span><br><span class="line"><span class="comment">/* 开放静态资源 */</span>                                                                        </span><br><span class="line"><span class="comment">// 127.0.0.1/public/xxx</span></span><br><span class="line">app.use(<span class="string">'/public'</span>, express.static(path.join(__dirname, <span class="string">'./public'</span>)))</span><br><span class="line"><span class="comment">// 127.0.0.1/xxx</span></span><br><span class="line"><span class="comment">//app.use(express.static(path.join(__dirname, './public')))</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 响应get请求</span></span><br><span class="line"><span class="comment"> * req: request 请求</span></span><br><span class="line"><span class="comment"> * res: response 响应</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">app.get(<span class="string">'/'</span>, (req, res) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 通过send方法向浏览器返回响应</span></span><br><span class="line">    res.status(<span class="number">200</span>).send(<span class="string">'hello express!'</span>)</span><br><span class="line">&#125;) </span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 响应post请求</span></span><br><span class="line"><span class="comment"> * req: request 请求</span></span><br><span class="line"><span class="comment"> * res: response 响应</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">app.post(<span class="string">'/login'</span>, (req, res) =&gt; &#123;</span><br><span class="line">    <span class="comment">//   通过json方法向浏览器返回Json数据</span></span><br><span class="line">    res.status(<span class="number">200</span>).json(&#123;</span><br><span class="line">        error: <span class="number">0</span>,</span><br><span class="line">        msg: <span class="string">'login success'</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;)</span><br><span class="line">  </span><br><span class="line"><span class="comment">/* 运行服务器, 监听80端口 */</span></span><br><span class="line">app.listen(<span class="number">80</span>, () =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'server is running, listening port 80 ...'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>运行server</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo node express_demo.js</span><br></pre></td></tr></table></figure><p>每次修改代码都要重新运行, 解决方法是用<code>nodemon</code>代替<code>node</code>运行js代码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装nodemon</span></span><br><span class="line">sudo npm install --global nodemon</span><br><span class="line"><span class="comment"># 运行server</span></span><br><span class="line">sudo nodemon express_demo.js</span><br></pre></td></tr></table></figure><h3 id="模板引擎art-template"><a href="#模板引擎art-template" class="headerlink" title="模板引擎art-template"></a>模板引擎art-template</h3><p><a href="http://aui.github.io/art-template/docs/syntax.html" target="_blank" rel="external nofollow noopener noreferrer">art-template语法</a></p><p>安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install --save art-template</span><br><span class="line">npm install --save express-art-template</span><br></pre></td></tr></table></figure><p>配置使用</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只需这一行配置就可使用express-art-template, 第一个参数为模板文件的后缀名</span></span><br><span class="line"><span class="comment">//app.engine('art', require('express-art-template'))</span></span><br><span class="line">app.engine(<span class="string">'html'</span>, <span class="built_in">require</span>(<span class="string">'express-art-template'</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// express-art-template默认去当前项目的views目录寻找模板文件</span></span><br><span class="line"><span class="comment">// 如果需要更改该默认路径, 使用下面这一行代码</span></span><br><span class="line"><span class="comment">//app.set('views', path.join(__dirname, './views))</span></span><br><span class="line"></span><br><span class="line">app.get(<span class="string">'/'</span>, (req, res) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 调用render方法渲染模板, 并向模板传递模板对象, expres会自动填充模板变量</span></span><br><span class="line">    res.render(<span class="string">'index.html'</span>, &#123;</span><br><span class="line">        title: <span class="string">'index'</span></span><br><span class="line">    &#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">80</span>, () =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'server is running, listening port 80 ...'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a>中间件</h3><p>**中间件(middleware)**是介于请求到响应的整个流程的一道过程, express中使用<code>app.use</code>方法注册中间件,  每个中间件是一个回调函数, 接收三个参数, 依次为request、response、next回调函数(代表下一个中间件). 在中间件中调用<code>next</code>函数则会将request和response传递给下一个中间件.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line">        </span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"> </span><br><span class="line"><span class="comment">// 通过use方法注册中间件</span></span><br><span class="line">app.use(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'the first middleware'</span>)</span><br><span class="line">    <span class="comment">// 调用下一个中间件</span></span><br><span class="line">    next()</span><br><span class="line">&#125;)   </span><br><span class="line">        </span><br><span class="line">app.use(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'the second middleware'</span>)</span><br><span class="line">    next()</span><br><span class="line">&#125;)   </span><br><span class="line">        </span><br><span class="line">app.get(<span class="string">'/'</span>, (req, res) =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'get /'</span>)                                                                </span><br><span class="line">    res.status(<span class="number">200</span>).send(<span class="string">'middleware demo'</span>)</span><br><span class="line">&#125;)   </span><br><span class="line">        </span><br><span class="line">app.listen(<span class="number">80</span>, () =&gt; &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'server is running, listening port 80 ...'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>运行后, 访问<code>127.0.0.1</code>, 控制台输出以下内容</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">server is running, listening<span class="built_in"> port </span>80 <span class="built_in">..</span>.</span><br><span class="line">the first middleware</span><br><span class="line">the second middleware</span><br><span class="line"><span class="builtin-name">get</span> /</span><br></pre></td></tr></table></figure><p>中间件默认对所有url进行处理, 如果需要对特定的url进行处理, 可以通过<code>req.url</code>参数来判断</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">app.use(<span class="function">(<span class="params">req, res, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (req.url === <span class="string">'/'</span>) &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">'the first middleware'</span>)</span><br><span class="line">            <span class="comment">// 调用下一个中间件</span></span><br><span class="line">            next()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>除了通过request对象来获取url外, <code>app.use</code>方法允许接收一个url字符串作为第一个参数</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">app.use(<span class="string">'/'</span>, (req, res, next) =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'the first middleware'</span>)</span><br><span class="line">    <span class="comment">// 调用下一个中间件</span></span><br><span class="line">    next()</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="获取请求参数"><a href="#获取请求参数" class="headerlink" title="获取请求参数"></a>获取请求参数</h3><p><strong>get请求</strong>的参数可以通过<code>req.query</code>获取</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">app.get(<span class="string">'/login'</span>, (req, res) =&gt; &#123;</span><br><span class="line">    <span class="comment">/* 通过req.query获取get请求时url的参数, 获取的是key-value形式的object */</span></span><br><span class="line">    <span class="built_in">console</span>.log(req.query)</span><br><span class="line">    <span class="comment">/* res.send方法向浏览器返回响应 */</span></span><br><span class="line">    res.send(<span class="string">'login cuccessful !'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>而<strong>post请求</strong>, 在express中没有内置获取post请求参数的api, 需要使用第三方模块<code>body-parser</code>作为中间件进行注册.</p><p><a href="http://www.expressjs.com.cn/en/resources/middleware/body-parser.html" target="_blank" rel="external nofollow noopener noreferrer">body-parser文档</a></p><p>安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install body-parser --save</span><br></pre></td></tr></table></figure><p>配置使用</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="comment">// 加载body-parser模块</span></span><br><span class="line"><span class="keyword">const</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 配置body-parser */</span></span><br><span class="line">app.use(bodyParser.urlencoded(&#123; <span class="attr">extended</span>: <span class="literal">false</span> &#125;))</span><br><span class="line">app.use(bodyParser.json())</span><br><span class="line"></span><br><span class="line">app.post(<span class="string">'/login'</span>, (req, res) = &gt; &#123;</span><br><span class="line">    <span class="comment">/* 通过req.body获取post请求的参数, 获取的是key-value形式的object */</span></span><br><span class="line">    consloe.log(req.body)</span><br><span class="line">    <span class="comment">/* res.send方法向浏览器返回响应 */</span></span><br><span class="line">    res.send(<span class="string">'login cuccessful !'</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">80</span>, () =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'server is running, listening port 80 ...'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="路由模块化"><a href="#路由模块化" class="headerlink" title="路由模块化"></a>路由模块化</h3><p>可以将路由相关代码从主入口文件中单独抽离出来, 然后在主入口文件中引用.</p><blockquote><p>router.js</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = express.Router()</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">'/'</span>, (req, res) =&gt; &#123;</span><br><span class="line">   res.render(<span class="string">'index.html'</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">router.get(<span class="string">'/login'</span>, (req, res) =&gt; &#123;</span><br><span class="line">   res.render(<span class="string">'login.html'</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// ......</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = router</span><br></pre></td></tr></table></figure><blockquote><p>app.js</p></blockquote><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="comment">// 加载路由</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="built_in">require</span>(<span class="string">'./router.js'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()  </span><br><span class="line"></span><br><span class="line">app.engine(<span class="string">'html'</span>, <span class="built_in">require</span>(<span class="string">'express-art-template'</span>))</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用路由</span></span><br><span class="line">app.use(router)</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">80</span>, () =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'Server is running ...'</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="状态保持"><a href="#状态保持" class="headerlink" title="状态保持"></a>状态保持</h3><p>在express中默认不支持<strong>Cookie</strong>和<strong>Session</strong>, 需要通过第三方模块<code>express-session</code>解决.</p><p>安装</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm instlal cookie-parser --save  </span><br><span class="line">npm install express-session --save</span><br></pre></td></tr></table></figure><ul><li><a href="http://www.expressjs.com.cn/en/resources/middleware/cookie-parser.html" target="_blank" rel="external nofollow noopener noreferrer">cookie-parse文档</a>   </li><li><a href="http://www.expressjs.com.cn/en/resources/middleware/session.html" target="_blank" rel="external nofollow noopener noreferrer">express-session文档</a></li></ul><p>配置</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>)</span><br><span class="line"><span class="keyword">const</span> cookieParser = <span class="built_in">require</span>(<span class="string">'cookie-parser'</span>);  </span><br><span class="line"><span class="keyword">const</span> session = <span class="built_in">require</span>(<span class="string">'express-session'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = express()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 配置cookie</span></span><br><span class="line">app.use(cookieParser())</span><br><span class="line"><span class="comment">// 配置session</span></span><br><span class="line">app.use(session(&#123;</span><br><span class="line">  secret: <span class="string">'keyboard cat'</span>, </span><br><span class="line">  resave: <span class="literal">false</span>,</span><br><span class="line">  saveUninitialized: <span class="literal">true</span>,</span><br><span class="line">  cookie: &#123; <span class="attr">secure</span>: <span class="literal">true</span> &#125;</span><br><span class="line">&#125;))</span><br></pre></td></tr></table></figure><p>使用</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 设置cookie, maxAge为过期时间, 以ms为单位</span></span><br><span class="line">res.cookie(<span class="string">'username'</span>, <span class="string">'caicai'</span>, &#123; <span class="attr">maxAge</span>: <span class="number">7</span> * <span class="number">24</span> * <span class="number">3600</span> * <span class="number">1000</span>, <span class="attr">httpOnly</span>: <span class="literal">true</span>&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取cookie</span></span><br><span class="line">res.cookies.username</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除cookie</span></span><br><span class="line">res.clearCookie(<span class="string">'username'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置session</span></span><br><span class="line">req.session.passwd = <span class="string">'xixixi'</span></span><br><span class="line">    </span><br><span class="line"><span class="comment">// 获取session</span></span><br><span class="line">req.session.passwd</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除session</span></span><br><span class="line"><span class="keyword">delete</span> req.session.passwd</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;Node-js简述&quot;&gt;&lt;a href=&quot;#Node-js简述&quot; class=&quot;headerlink&quot; title=&quot;Node.js简述&quot;&gt;&lt;/a&gt;Node.js简述&lt;/h2&gt;&lt;p&gt;Node.js是基于chrome浏览器中的v8引擎而构建的js运行时环境, 并提供了一系列的工具模块和一个包管理工具npm. Node脱离于浏览器运行, 并提供了一系列自带的os相关接口, 从而使其能像传统后端语言一样操作文件、获取os相关信息等.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en/&quot; target=&quot;_blank&quot; rel=&quot;external nofollow noopener noreferrer&quot;&gt;node.js官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://nodejs.cn/&quot; target=&quot;_blank&quot; rel=&quot;external nofollow noopener noreferrer&quot;&gt;node.js中文网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/&quot; target=&quot;_blank&quot; rel=&quot;external nofollow noopener noreferrer&quot;&gt;npm官网&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="js" scheme="https://www.caiyifan.cn/tags/js/"/>
    
      <category term="node" scheme="https://www.caiyifan.cn/tags/node/"/>
    
      <category term="express" scheme="https://www.caiyifan.cn/tags/express/"/>
    
  </entry>
  
  <entry>
    <title>SELinux之解决avc denied</title>
    <link href="https://www.caiyifan.cn/p/38175be7.html"/>
    <id>https://www.caiyifan.cn/p/38175be7.html</id>
    <published>2019-11-18T09:04:01.000Z</published>
    <updated>2024-05-01T10:34:38.736Z</updated>
    
    <content type="html"><![CDATA[<h2 id="SELinux简述"><a href="#SELinux简述" class="headerlink" title="SELinux简述"></a>SELinux简述</h2><p>安全增强型 Linux（Security-Enhanced Linux）简称 SELinux，它是 Linux 的一个安全子系统。SELinux 主要作用是<strong>最大限度地减小系统中服务进程可访问的资源</strong>（最小权限原则）。对资源的访问控制分为两类: <strong>DAC</strong>和<strong>MAC</strong>.</p><h3 id="DAC"><a href="#DAC" class="headerlink" title="DAC"></a>DAC</h3><p>在未使用SELinux的系统上, 对资源的访问是通过权限位来确定, 比如一个文件对所属用户是否有读、写、执行权限, 其他用户的访问可由所属用户进行配置. 这种由所属用户自己决定是否将资源的访问权或部分访问权授予其他用户，这种控制方式是自主的，即<strong>自主访问控制</strong>（Discretionary Access Control, <strong>DAC</strong>）.</p><a id="more"></a><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt; ls -l note</span><br><span class="line">-rw-rw-r-- 1 ifantsai ifantsai 37 6月  17 13:36 note</span><br></pre></td></tr></table></figure><h3 id="MAC"><a href="#MAC" class="headerlink" title="MAC"></a>MAC</h3><p>在使用了 SELinux 的系统上，对资源的访问除了通过权限位判定外，还需要判断<strong>每一类进程是否拥有对某一类资源的访问权限</strong>。这种方式对资源的访问控制, 称之为<strong>强制访问控制</strong>（Mandatory Access Control, <strong>MAC</strong>）.只给每个进程开放所需要的资源, 将权限开放到最小, 当进程出现漏洞时也只会影响到该进程所涉及的资源, 这大大提升了安全性.</p><h2 id="SELinux工作模式"><a href="#SELinux工作模式" class="headerlink" title="SELinux工作模式"></a>SELinux工作模式</h2><p>SELinux 有三种工作模式，分别为:</p><ul><li><strong>enforcing</strong>: 强制模式, 执行SELinux规则, 违反的行为会被阻止</li><li><strong>permissive</strong>: 宽容模式, 执行SELinux规则, 违反的行不会被阻止</li><li><strong>disabled</strong>: 关闭SELinux</li></ul><p>通过执行<code>getenforce</code>命令可以获取当前SELinux的工作模式</p><p>在Android系统开发中, 可能会遇到SELinux的权限不足而引起的各种问题. 可以尝试将SELinux工作模式临时改为宽容模式看问题是否解决, 来判定是否是SELinux引起的问题.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 修改工作模式为宽容模式</span></span><br><span class="line">setenforce permissive</span><br></pre></td></tr></table></figure><p><ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-2683615680573215" data-ad-slot="4705074417"></ins></p><script>     (adsbygoogle = window.adsbygoogle || []).push({});</script><p>遇到权限问题时, 在log中会打印avc denied提示缺少什么权限, 可以通过<code>dmesg | grep avc</code>过滤出所有avc denied.</p><h2 id="解决avc-denied"><a href="#解决avc-denied" class="headerlink" title="解决avc denied"></a>解决avc denied</h2><p>笔者在RK Android9.0上进行操作, 权限文件以<code>.te</code>为后缀, 涉及到需要修改的路径:</p><ul><li><code>android/device/rockchip/common/sepolicy</code></li><li><code>android/device/rockchip/rk3399/sepolicy</code></li></ul><p>Android自带的进程服务通过以上目录配置即可 , 自己添加的第三方进程需要添加到自定义的目录下</p><p>以如下所示的avc denied为例讲解</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># avc: denied &#123; 操作权限 &#125; for pid=7201 comm=“进程名” scontext=u:r:源类型:s0 tcontext=u:r:目标类型:s0 tclass=访问类别 permissive=0</span></span><br><span class="line">avc: denied &#123; <span class="built_in">read</span> &#125; <span class="keyword">for</span> pid=7517 comm=<span class="string">"audio@2.0-servi"</span> name=<span class="string">"u:object_r:default_prop:s0"</span> dev=<span class="string">"tmpfs"</span> ino=11426 scontext=u:r:hal_audio_default:s0 tcontext=u:object_r:default_prop:s0 tclass=file permissive=0</span><br></pre></td></tr></table></figure><p>主要关注以下内容:</p><ul><li><code>denied {read}</code>: 表示缺少<strong>read</strong>权限</li><li><code>scontext=u:r:hal_audio_default:s0</code>: 表示<strong>hal_audio_default</strong>缺少了权限</li><li><code>tcontext=u:object_r:default_prop:s0</code>: 表示是对<strong>default_prop</strong>缺少了权限</li><li><code>tclass=file</code>: 表示缺少权限的资源类型为<strong>file</strong></li></ul><p>因此只要在hal_audio_default.te文件中加入下面内容即可xia</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">allow hal_audio_default tcontext:file <span class="built_in">read</span>;</span><br></pre></td></tr></table></figure><p>如果需要赋予read, open权限, 当有多个权限时用<code>{}</code>包裹</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">allow hal_audio_default tcontext:file &#123; <span class="built_in">read</span> open &#125;;</span><br></pre></td></tr></table></figure><p>或者参考<code>android/system/sepolicy/public/global_macros</code>中赋予一个复合权限, 如<code>r_file_perms</code> 表示<code>{ getattr open read ioctl lock map }</code></p><h2 id="待补充"><a href="#待补充" class="headerlink" title="待补充"></a>待补充</h2><p>以上内容都是基于Android中自带的进程服务进行配置, 如果是自己引入的进程服务, 则需要自行创建<code>.te</code>文件, 这部分内容后续再来填坑QAQ</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;SELinux简述&quot;&gt;&lt;a href=&quot;#SELinux简述&quot; class=&quot;headerlink&quot; title=&quot;SELinux简述&quot;&gt;&lt;/a&gt;SELinux简述&lt;/h2&gt;&lt;p&gt;安全增强型 Linux（Security-Enhanced Linux）简称 SELinux，它是 Linux 的一个安全子系统。SELinux 主要作用是&lt;strong&gt;最大限度地减小系统中服务进程可访问的资源&lt;/strong&gt;（最小权限原则）。对资源的访问控制分为两类: &lt;strong&gt;DAC&lt;/strong&gt;和&lt;strong&gt;MAC&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;DAC&quot;&gt;&lt;a href=&quot;#DAC&quot; class=&quot;headerlink&quot; title=&quot;DAC&quot;&gt;&lt;/a&gt;DAC&lt;/h3&gt;&lt;p&gt;在未使用SELinux的系统上, 对资源的访问是通过权限位来确定, 比如一个文件对所属用户是否有读、写、执行权限, 其他用户的访问可由所属用户进行配置. 这种由所属用户自己决定是否将资源的访问权或部分访问权授予其他用户，这种控制方式是自主的，即&lt;strong&gt;自主访问控制&lt;/strong&gt;（Discretionary Access Control, &lt;strong&gt;DAC&lt;/strong&gt;）.&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="selinux" scheme="https://www.caiyifan.cn/tags/selinux/"/>
    
  </entry>
  
  <entry>
    <title>Java之JNI开发流程</title>
    <link href="https://www.caiyifan.cn/p/684115c2.html"/>
    <id>https://www.caiyifan.cn/p/684115c2.html</id>
    <published>2019-08-25T08:21:30.000Z</published>
    <updated>2024-05-01T10:34:38.734Z</updated>
    
    <content type="html"><![CDATA[<p>​    之前介绍过<a href="https://www.caiyifan.cn/p/1432d61d.html">C&#x2F;C++和Python的相互调用</a>，这一次笔者讲解C&#x2F;C++和Java的相互调用。Java与C的相互调用需要使用JNI，JNI即Java Native Interface(Java本地接口)。Google提供了NDK(Native Development Kit), NDK包含了一套Android的交叉编译环境和开发库，使用它可以编写C&#x2F;C++程序后编译成Android环境下使用的动态链接库，Java代码使用JNI规范调用C&#x2F;C++实现的动态链接库。本文先介绍在命令行下使用JNI，随后介绍在Android Studio中使用JNI。</p><a id="more"></a><h2 id="Java在命令行下使用JNI"><a href="#Java在命令行下使用JNI" class="headerlink" title="Java在命令行下使用JNI"></a>Java在命令行下使用JNI</h2><p>笔者以Java中调用C编写的add函数为例讲解，首先创建<code>Hello.java</code>和<code>native.c</code>。在<a href="#Android-Studio%E4%B8%8B%E4%BD%BF%E7%94%A8JNI">Android Studio下使用JNI</a>中会讲解C与C++在JNI中的不同，并采用C++来讲解JNI。</p><h3 id="声明本地方法"><a href="#声明本地方法" class="headerlink" title="声明本地方法"></a>声明本地方法</h3><p>在<code>Hello.java</code>中声明一个本地方法，并在静态代码块中加载对应的动态链接库。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Hello</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="comment">// 加载动态链接库    注意：对于libnative.so只需要写native</span></span><br><span class="line">        System.loadLibrary(<span class="string">"native"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 声明本地方法</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">int</span> <span class="title">addFromC</span><span class="params">(<span class="keyword">int</span> a, <span class="keyword">int</span> b)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] argv)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 调用本地方法</span></span><br><span class="line">        System.out.println(<span class="string">"1 + 2 = "</span> + addFromC(<span class="number">1</span>, <span class="number">2</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="实现C函数"><a href="#实现C函数" class="headerlink" title="实现C函数"></a>实现C函数</h3><p>Java调用C函数需要做C函数和Java本地方法的映射，建立该映射有两种方式: 显式映射和隐式映射。</p><h4 id="显式映射"><a href="#显式映射" class="headerlink" title="显式映射"></a>显式映射</h4><p>确保Java文件中不指定包名，指定了包名后在命令行下可能会出错，一般步骤如下：</p><p>1.包含<code>jni.h</code>头文件</p><blockquote><p>&#x2F;usr&#x2F;lib&#x2F;jvm&#x2F;java-1.8.0-openjdk-amd64&#x2F;include</p></blockquote><p>其中<code>jin.h</code>又包含了<code>jni_md.h</code></p><blockquote><p>&#x2F;usr&#x2F;lib&#x2F;jvm&#x2F;java-1.8.0-openjdk-amd64&#x2F;include&#x2F;linux</p></blockquote><p>2.实现C函数</p><p>3.将C函数加入到映射数组中</p><p>4.实现<code>JNI_OnLoad</code>函数</p><p>在<code>native.c</code>中实现以上步骤</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;jni.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ARRAY_SIZE(arr)   (sizeof(arr) / sizeof((arr)[0]))</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// C函数需要比Java本地方法多出两个参数，这两个参数之后的参数列表与Java本地方法保持一致</span></span><br><span class="line"><span class="comment">// 第一个参数表示JNI环境，该环境封装了所有JNI的操作函数</span></span><br><span class="line"><span class="comment">// 第二个参数为Java代码中调用该C函数的对象</span></span><br><span class="line"><span class="comment">// jint表示JNI的int类型，在本文后面会给出所有JNI类型</span></span><br><span class="line"><span class="function">jint <span class="title">add</span><span class="params">(JNIEnv *env, jobject thiz, jint a, jint b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> JNINativeMethod methods[] = &#123;</span><br><span class="line">    <span class="comment">// 第一个参数为Java本地方法名</span></span><br><span class="line">    <span class="comment">// 第二个参数为函数签名：(参数签名)返回值签名， 在本文后面会给出所有签名符号</span></span><br><span class="line">    <span class="comment">// 第三个参数为C函数</span></span><br><span class="line">    &#123;<span class="string">"addFromC"</span>, <span class="string">"(II)I"</span>, (<span class="keyword">void</span> *)add&#125;,   <span class="comment">// 建立Java本地方法和C函数的映射</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 在Java中调用System.loadLibrary方法时会调用到该函数</span></span><br><span class="line">JNIEXPORT jint JNICALL</span><br><span class="line">JNI_OnLoad(JavaVM *jvm, <span class="keyword">void</span> *reserved)</span><br><span class="line">&#123;</span><br><span class="line">    JNIEnv *env;</span><br><span class="line">    jclass cls;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取JNI环境</span></span><br><span class="line">    <span class="keyword">if</span> ((*jvm)-&gt;GetEnv(jvm, (<span class="keyword">void</span> **)&amp;env, JNI_VERSION_1_8)) &#123;</span><br><span class="line">        <span class="keyword">return</span> JNI_ERR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取Java类</span></span><br><span class="line">    <span class="comment">// JNI_OnLoad函数写法基本固定， 唯一需要修改的是FindClass的第二个参数，即类名</span></span><br><span class="line">    cls = (*env)-&gt;FindClass(env, <span class="string">"Hello"</span>);</span><br><span class="line">    <span class="keyword">if</span> (cls == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> JNI_ERR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注册本地方法</span></span><br><span class="line">    <span class="keyword">if</span> ((*env)-&gt;RegisterNatives(env, cls, methods, ARRAY_SIZE(methods)) &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> JNI_ERR;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> JNI_VERSION_1_8;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译运行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 生成动态链接库</span></span><br><span class="line">gcc -shared -fPIC -I/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux/ -o libnative.so native.c </span><br><span class="line">javac  Hello.java   <span class="comment"># 编译Java</span></span><br><span class="line">java  -Djava.library.path=.   Hello   <span class="comment"># 运行Java，并指定动态链接库的路径</span></span><br></pre></td></tr></table></figure><h4 id="隐式映射"><a href="#隐式映射" class="headerlink" title="隐式映射"></a>隐式映射</h4><p>在<code>Hello.java</code>的第一行指定包名</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.caiyifan.jni;</span><br></pre></td></tr></table></figure><p>采用隐式映射的方式不需要程序员去手动建立链接，JNI规范已经使用了一套映射规范，在C函数中实现的函数名格式：Java_包名_类名_Java方法名，需要注意的是包名以’_‘隔开，而不是’.‘</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;jni.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// C函数需要比Java本地方法多出两个参数，这两个参数之后的参数列表与Java本地方法保持一致</span></span><br><span class="line"><span class="comment">// 第一个参数表示JNI环境，该环境封装了所有JNI的操作函数</span></span><br><span class="line"><span class="comment">// 第二个参数为Java代码中调用该C函数的对象</span></span><br><span class="line"><span class="comment">// 函数名格式: Java_包名_类名_Java方法名</span></span><br><span class="line"><span class="function">jint <span class="title">Java_cn_caiyifan_jni_Hello_addFromC</span><span class="params">(JNIEnv *env, jobject thiz, jint a, jint b)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>编译运行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 生成动态链接库</span></span><br><span class="line">gcc -shared -fPIC -I/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux/ -o libnative.so native.c </span><br><span class="line">javac -d . Hello.java    <span class="comment"># 编译Java并生成完整包名路径</span></span><br><span class="line">java -Djava.library.path=. cn.caiyifan.jni.Hello   <span class="comment"># 运行Java，并指定动态链接库的路径</span></span><br></pre></td></tr></table></figure><h2 id="Android-Studio下使用JNI"><a href="#Android-Studio下使用JNI" class="headerlink" title="Android Studio下使用JNI"></a>Android Studio下使用JNI</h2><p> 在Android Studio中使用JNI，借助IDE带来的自动生成功能，就变得很方便。注意笔者使用的Android Studio版本是3.4.2。先讲解JNI中Ｃ与C++的不同后，再在Android Studio下使用C++来进行JNI开发。</p><h3 id="JNI中C与C-的不同"><a href="#JNI中C与C-的不同" class="headerlink" title="JNI中C与C++的不同"></a>JNI中C与C++的不同</h3><p>从<code>jni.h</code>源码中可以看到<code>JNIEnv</code>的类型是不同的</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(__cplusplus)</span></span><br><span class="line"><span class="keyword">typedef</span> _JNIEnv JNIEnv;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line"><span class="keyword">typedef</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">JNINativeInterface</span>* <span class="title">JNIEnv</span>;</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>由于C++是面向对象的，而C非面向对象，但C如果需要以面向对象方式封装JNI的操作函数，则需要将函数指针封装在结构体内，调用的时候需要传递本结构体的地址，所以在C中调用JNI的方法是下面这样调用的，以<code>NewStringUTF</code>为例</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(*env)-&gt;NewStringUTF(env, <span class="string">"hello world"</span>);</span><br></pre></td></tr></table></figure><p>通过<code>jni.h</code>源码可知，C++的<code>JNIEnv</code>的作法是包裹C的<code>JNIEnv</code>后，在内部传递this指针进行调用的。所以在C++中直接以对象调用方法的方式调用即可</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">env-&gt;NewStringUTF(<span class="string">"hello world"</span>);</span><br></pre></td></tr></table></figure><h3 id="安装JNI开发插件"><a href="#安装JNI开发插件" class="headerlink" title="安装JNI开发插件"></a>安装JNI开发插件</h3><img src="https://img.caiyifan.cn/as_jni.png" width="80%" height="80%">  <h3 id="创建工程"><a href="#创建工程" class="headerlink" title="创建工程"></a>创建工程</h3><p>创建Android工程时，选择Native C++。</p><img src="https://img.caiyifan.cn/as_new_jni_prj.png" width="80%" height="80%">  <p>创建完的工程会比常规的Android工程在src&#x2F;main下多出一个cpp目录，这是IDE自动生成，编写的C&#x2F;C++函数放在这个目录下即可。</p><p><img src="https://img.caiyifan.cn/as_jni_dir_struct.png">  </p><h3 id="Java中调用C"><a href="#Java中调用C" class="headerlink" title="Java中调用C++"></a>Java中调用C++</h3><p>创建一个<code>Jni.java</code> 文件，将Jni的native接口封装成一个单例类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.caiyifan.jnidemo;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用来封装Jni的native接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Jni</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        System.loadLibrary(<span class="string">"native-lib"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Jni jni;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Jni</span><span class="params">()</span> </span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Jni <span class="title">getInstance</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (jni == <span class="keyword">null</span>) &#123;</span><br><span class="line">            jni = <span class="keyword">new</span> Jni();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> jni;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>并在Jni类中添加一个<code>getStringFromJni</code>的native方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> String <span class="title">getStringFromJni</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><p>这时候Android IDE会报错，提示Cannot resolve corresponding JNI function Java_cn_caiyifan_jnidemo_Jni_getStringFromJni，这个报错是因为没有实现对应的本地函数，只需要按下快捷键Alt+enter，就会在对应的C&#x2F;C++文件中生成对应的函数接口。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span></span><br><span class="line">JNIEXPORT jstring JNICALL</span><br><span class="line">Java_cn_caiyifan_jnidemo_Jni_getStringFromJni(JNIEnv *env, jobject instance) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// TODO</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> env-&gt;NewStringUTF(returnValue);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到函数名正是JNI规范要求的格式。修改该函数</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span></span><br><span class="line">JNIEXPORT jstring JNICALL</span><br><span class="line">Java_cn_caiyifan_jnidemo_Jni_getStringFromJni(JNIEnv *env, jobject instance) &#123;</span><br><span class="line">    <span class="comment">// env-&gt;NewStringUTF 将 char *转换成jstring类型</span></span><br><span class="line">    <span class="keyword">return</span> env-&gt;NewStringUTF(<span class="string">"hello from cpp"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 然后就可以在<code>MainActivity</code>中调用cpp函数了</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> cn.caiyifan.jnidemo;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> androidx.appcompat.app.AppCompatActivity;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> android.os.Bundle;</span><br><span class="line"><span class="keyword">import</span> android.widget.Toast;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MainActivity</span> <span class="keyword">extends</span> <span class="title">AppCompatActivity</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 调用 getStringFromJni native方法</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="comment">// 获取Jni对象</span></span><br><span class="line">        Jni jni = Jni.getInstance();</span><br><span class="line">        <span class="comment">// 调用native方法</span></span><br><span class="line">        String str = jni.getStringFromJni();</span><br><span class="line">        <span class="comment">// 显示到Toast上</span></span><br><span class="line">        Toast.makeText(<span class="keyword">this</span>, str, Toast.LENGTH_LONG).show();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 运行到模拟器后，就可以发现成功调用了。</p><p><img src="https://img.caiyifan.cn/as_jni_run.png">  </p><h2 id="C-中调用Java"><a href="#C-中调用Java" class="headerlink" title="C++中调用Java"></a>C++中调用Java</h2><p>在C++中调用Java一般分为四步：</p><p>1.获取字节码对象<br>2.获取jmethodID对象<br>3.通过字节码对象创建jobject对象<br>4.通过jobject对象调用方法</p><blockquote><p>其中第3步可视情况省略，当需要调用的Java方法正好位于调用该本地函数的类内，那么JNI函数的第二个参数即表示该对象</p></blockquote><p>在<code>Jni.java</code>中创建一个log_i方法，该方法用来输出log，供C++调用。并且声明一个native方法，在对于的Jni函数中来回调log_i方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">log_i</span><span class="params">(String tag, String msg)</span> </span>&#123;</span><br><span class="line">    Log.i(tag, msg);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">callBackFromCpp</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure><p>在对应的Cpp函数中回调该log_i方法。对象</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="string">"C"</span></span><br><span class="line">JNIEXPORT <span class="keyword">void</span> JNICALL</span><br><span class="line">Java_cn_caiyifan_jnidemo_Jni_callBackFromCpp(JNIEnv *env, jobject thiz) &#123;</span><br><span class="line">    <span class="comment">// 1. 获取字节码对象</span></span><br><span class="line">    <span class="comment">//    参数: 要调用的Java方法所在类的路径</span></span><br><span class="line">    jclass clazz = env-&gt;FindClass(<span class="string">"cn/caiyifan/jnidemo/Jni"</span>);</span><br><span class="line">    <span class="comment">// 2. 获取jmethodID对象</span></span><br><span class="line">    <span class="comment">//    第一个参数: 字节码对象对象对象</span></span><br><span class="line">    <span class="comment">//    第二个参数: Java方法名</span></span><br><span class="line">    <span class="comment">//    第三个参数: Java方法签名     该签名如何编写见文末</span></span><br><span class="line">    jmethodID methodId = env-&gt;GetMethodID(clazz, <span class="string">"log_i"</span>, <span class="string">"(Ljava/lang/String;Ljava/lang/String;)V"</span>);</span><br><span class="line">    <span class="comment">// 3. 通过字节码对象创建jobject对象    此时Jni函数的第二个参数即为jobject对象，所以无需再创建</span></span><br><span class="line">    <span class="comment">// 4. 通过jobject对象调用方法</span></span><br><span class="line">    <span class="comment">//    第一个参数: Jobject对象</span></span><br><span class="line">    <span class="comment">//    第二个参数: jmethodID对象</span></span><br><span class="line">    <span class="comment">//    剩下的可选参数: 调用Java方法所传递的参数</span></span><br><span class="line">    env-&gt;CallVoidMethod(thiz, methodId, env-&gt;NewStringUTF(<span class="string">"test"</span>), env-&gt;NewStringUTF(<span class="string">"hello from java"</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后在<code>MainActivity.java</code>中调用该本地方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取Jni对象</span></span><br><span class="line">Jni jni = Jni.getInstance(getApplicationContext());</span><br><span class="line">jni.callBackFromCpp();</span><br></pre></td></tr></table></figure><p>运行后会发现成功在logcat上进行了打印。</p><h2 id="JNI类型与签名"><a href="#JNI类型与签名" class="headerlink" title="JNI类型与签名"></a>JNI类型与签名</h2><p>签名的格式为: (参数签名)返回值签名</p><table><thead><tr><th align="center"><strong>Java类型</strong></th><th align="center"><strong>JNI类型</strong></th><th align="center"><strong>C&#x2F;C++类型</strong></th><th align="center"><strong>签名</strong></th></tr></thead><tbody><tr><td align="center">boolean</td><td align="center">jboolean</td><td align="center">unsigned char</td><td align="center">Z</td></tr><tr><td align="center">byte</td><td align="center">jbyte</td><td align="center">char</td><td align="center">B</td></tr><tr><td align="center">char</td><td align="center">jchar</td><td align="center">unsigned short</td><td align="center">C</td></tr><tr><td align="center">short</td><td align="center">jshort</td><td align="center">short</td><td align="center">S</td></tr><tr><td align="center">int</td><td align="center">jint</td><td align="center">int</td><td align="center">I</td></tr><tr><td align="center">long</td><td align="center">jlong</td><td align="center">long long</td><td align="center">J</td></tr><tr><td align="center">float</td><td align="center">jfloat</td><td align="center">float</td><td align="center">F</td></tr><tr><td align="center">double</td><td align="center">jdouble</td><td align="center">double</td><td align="center">V</td></tr><tr><td align="center">类</td><td align="center">jobject</td><td align="center">void *</td><td align="center">L用&#x2F;隔开的全类名;</td></tr></tbody></table><blockquote><p>类: 例如String的签名为Ljava&#x2F;lang&#x2F;String;  注意: 包名和类名用&#x2F;隔开, 结尾有一个;</p><p>数组：用[表示数组签名, 例如int[]的签名为[I</p></blockquote><h3 id="javah和javap命令的使用"><a href="#javah和javap命令的使用" class="headerlink" title="javah和javap命令的使用"></a>javah和javap命令的使用</h3><p><code>javah</code>可以生成Java本地方法对应的C&#x2F;C++函数接口，用法是指定一个class文件，不过在Android Studio中已经可以快捷键生成了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javah cn.caiyifan.jnidemo.Jni</span><br></pre></td></tr></table></figure><p><code>javap -s</code>可以生成一个Java文件所有方法的签名，用法与<code>javah</code>一样</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javap -s cn.caiyifan.jnidemo.Jni</span><br></pre></td></tr></table></figure><p>但在Android Studio中目录结构确定编译后的class目录比较复杂，可以在工程根目录下使用以下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javap -s `find -name Jni.class`</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;​    之前介绍过&lt;a href=&quot;https://www.caiyifan.cn/p/1432d61d.html&quot;&gt;C&amp;#x2F;C++和Python的相互调用&lt;/a&gt;，这一次笔者讲解C&amp;#x2F;C++和Java的相互调用。Java与C的相互调用需要使用JNI，JNI即Java Native Interface(Java本地接口)。Google提供了NDK(Native Development Kit), NDK包含了一套Android的交叉编译环境和开发库，使用它可以编写C&amp;#x2F;C++程序后编译成Android环境下使用的动态链接库，Java代码使用JNI规范调用C&amp;#x2F;C++实现的动态链接库。本文先介绍在命令行下使用JNI，随后介绍在Android Studio中使用JNI。&lt;/p&gt;
    
    </summary>
    
    
      <category term="c" scheme="https://www.caiyifan.cn/tags/c/"/>
    
      <category term="c++" scheme="https://www.caiyifan.cn/tags/c/"/>
    
      <category term="java" scheme="https://www.caiyifan.cn/tags/java/"/>
    
      <category term="jni" scheme="https://www.caiyifan.cn/tags/jni/"/>
    
  </entry>
  
  <entry>
    <title>Linux驱动之PCI子系统剖析</title>
    <link href="https://www.caiyifan.cn/p/eff0e21b.html"/>
    <id>https://www.caiyifan.cn/p/eff0e21b.html</id>
    <published>2019-08-09T17:14:52.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<p>PCI是外围设备互连(Peripheral Component Interconnect)的简称，作为一种通用的总线接口标准，它已经普遍使用在了计算机中。<strong>PCI总线常见于x86体系，本文默认面向的体系为x86，注意x86架构下IO与内存是独立编址的</strong>。</p><blockquote><p>附： 本文默认读者熟悉Linux设备驱动模型，不熟悉的可以先阅读这两篇blog。<br><a href="https://www.caiyifan.cn/p/8d9c6db2.html">Linux驱动之I2C子系统剖析</a><br><a href="https://www.caiyifan.cn/p/41bdfd80.html">Linux驱动之SPI子系统剖析</a></p></blockquote><h2 id="PCI寻址"><a href="#PCI寻址" class="headerlink" title="PCI寻址"></a>PCI寻址</h2><p>PCI系统总体布局组织为树状，从CPU连接的Host Bridge引出PCI主桥，主桥连接的是PCI总线0，可以直接连接PCI设备，或者再挂上PCI桥引出下一级PCI总线。</p><a id="more"></a><p><img src="https://img.caiyifan.cn/PCI_sys.png">  </p><p>每个PCI设备由一个<strong>总线号</strong>、<strong>设备号</strong>和<strong>功能号</strong>确定。PCI规范允许一个系统最多拥有256条总线，每条总线最多带有32个设备，每个设备可以是最多8个功能的多功能板，但是对于大型系统而言总线数不够，故还支持<strong>PCI域</strong>，每个PCI域可最多支持256个总线。</p><ul><li><strong>PCI域</strong>: 16位</li><li><strong>总线号</strong>: 8位</li><li><strong>设备号</strong>: 5位</li><li><strong>功能号</strong>: 3位</li></ul><p>在PC机上可以使用<code>lspci</code>查看计算机上PCI设备信息，笔者在自己电脑上执行该命令后输出如下</p><p><img src="https://img.caiyifan.cn/PCI_lspci.png">  </p><p>每一行表示一个PCI设备或者PCI桥，而每行的开头即表示<strong>总线号</strong>、<strong>设备号</strong>和<strong>功能号</strong>。</p><h2 id="PCI配置寄存器"><a href="#PCI配置寄存器" class="headerlink" title="PCI配置寄存器"></a>PCI配置寄存器</h2><p>所有的PCI设备都有至少256字节的地址空间，其中前64字节是标准化的，被称为PCI配置寄存器，剩下的字节是设备相关的 (取决于具体的厂商，需要查看datasheet得知)。</p><p>PCI配置寄存器如下图所示。</p><p><img src="https://img.caiyifan.cn/PCI_config_register.png"></p><ul><li><strong>Vendor ID</strong>: 标识硬件厂商，需要向特定组织进行注册。</li><li><strong>Device ID</strong>: 由硬件厂商来分配的设备ID，无需对ID进行注册。</li><li><strong>Subsystem ID</strong>、<strong>Subsystem Vendor ID</strong>: 用来进一步标识设备。</li></ul><p>硬件标识信息在硬件出厂时就写入相应设备中了。</p><p>当BIOS启动时，会为每个PCI设备分配内存、IO空间以及irq号，并写入相应PCI设备的配置寄存器中。Linux内核启动时会从PCI设备的配置寄存器里读取内存&#x2F;IO起始地址以及irq，并把这些信息赋值给<code>struct pci_dev</code>的相应成员来生成软件描述的PCI设备。</p><p>从上图的寄存器分布中可以看到中间有一段地址空间描述BARS(Base Address Register)，这些寄存器组用来存储备PCI设备工作时的io地址、irq号和mem地址起始地址以及长度。这些信息存储的具体位置需要查阅相应PCI设备的datasheet方可得知，在内核中提供了以下几个接口来获取这些资源。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * dev为PCI设备的软件抽象，bar的取值为0 ~ 5</span></span><br><span class="line"><span class="comment"> * 这三个函数分别返回第bar个区域的首地址、尾地址和长度</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="title">pci_resource_start</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> bar)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="title">pci_resource_end</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> bar)</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="title">pci_resource_len</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> bar)</span></span>;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * 返回和这个bar相关联资源的标识</span></span><br><span class="line"><span class="comment"> * IORESOURCE_IO：io端口</span></span><br><span class="line"><span class="comment"> * IORESOURCE_MEM：内存</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">unsigned</span> <span class="keyword">long</span> <span class="title">pci_resource_flags</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> bar)</span></span>;</span><br></pre></td></tr></table></figure><p>内核提供了一组接口来访问配置空间。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pci_read_config_byte</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> where, u8 *val)</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pci_read_config_word</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> where, u16 *val)</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pci_read_config_dword</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> where, u32 *val)</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pci_write_config_byte</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> where, u8 val)</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pci_write_config_word</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> where, u16 val)</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">pci_write_config_dword</span><span class="params">(struct pci_dev *dev, <span class="keyword">int</span> where,  u32 val)</span></span></span><br></pre></td></tr></table></figure><h2 id="PCI驱动的注册及匹配"><a href="#PCI驱动的注册及匹配" class="headerlink" title="PCI驱动的注册及匹配"></a>PCI驱动的注册及匹配</h2><p>BIOS在启动时，会为每个PCI设备分配地址和irq等信息，并写入各个PCI设备的配置寄存器中，所以PCI设备无需像其他总线那样去注册设备。</p><p>内核中使用<code>struct pci_dev</code>来描述PCI设备的抽象。当linux系统启动时，会探测系统中的所有PCI设备，并为探测到的每个PCI设备做如下操作：<br>    1.分配一个<code>struct pci_dev</code>结构体，用来表示相应的PCI设备<br>    2.为这个结构体填充设备vendor id、device id、subvendor id、subdevice id以及地址和irq信息（通过读取PIC配置寄存器得到）<br>    3.最后把这个<code>struct pci_dev</code>结构体挂接到<code>pci_bus</code>上</p><p>内核中使用<code>struct pci_driver</code>来描述PCI驱动的抽象</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pci_driver</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">node</span>;</span></span><br><span class="line">    <span class="keyword">char</span> *name;</span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">pci_device_id</span> *<span class="title">id_table</span>;</span>   <span class="comment">/* must be non-NULL for probe to be called */</span></span><br><span class="line">    <span class="keyword">int</span>  (*probe)  (struct pci_dev *dev, <span class="keyword">const</span> struct pci_device_id *id);   <span class="comment">/* New device inserted */</span></span><br><span class="line">    <span class="keyword">void</span> (*<span class="built_in">remove</span>) (struct pci_dev *dev);   <span class="comment">/* Device removed (NULL if not a hot-plug capable driver) */</span></span><br><span class="line">    <span class="keyword">int</span>  (*suspend) (struct pci_dev *dev, <span class="keyword">pm_message_t</span> state);  <span class="comment">/* Device suspended */</span></span><br><span class="line">    <span class="keyword">int</span>  (*suspend_late) (struct pci_dev *dev, <span class="keyword">pm_message_t</span> state);</span><br><span class="line">    <span class="keyword">int</span>  (*resume_early) (struct pci_dev *dev);</span><br><span class="line">    <span class="keyword">int</span>  (*resume) (struct pci_dev *dev);                   <span class="comment">/* Device woken up */</span></span><br><span class="line">    <span class="keyword">void</span> (*<span class="built_in">shutdown</span>) (struct pci_dev *dev);</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">pci_error_handlers</span> *<span class="title">err_handler</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">device_driver</span>    <span class="title">driver</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">pci_dynids</span> <span class="title">dynids</span>;</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>其中<code>id_table</code>用来匹配设备</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pci_device_id</span> &#123;</span></span><br><span class="line">    __u32 vendor, device;       <span class="comment">/* Vendor and device ID or PCI_ANY_ID*/</span></span><br><span class="line">    __u32 subvendor, subdevice; <span class="comment">/* Subsystem ID's or PCI_ANY_ID */</span></span><br><span class="line">    __u32 <span class="class"><span class="keyword">class</span>, <span class="title">class_mask</span>;</span>    <span class="comment">/* (class,subclass,prog-if) triplet */</span></span><br><span class="line">    <span class="keyword">kernel_ulong_t</span> driver_data; <span class="comment">/* Data private to the driver */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>PCI驱动的注册接口为<code>pci_register_driver(struct pci_driver *drv)</code>，当调用该接口后，会调用PCI总线下的match方法来进行匹配</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">pci_bus_match</span><span class="params">(struct device *dev, struct device_driver *drv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">pci_dev</span> *<span class="title">pci_dev</span> = <span class="title">to_pci_dev</span>(<span class="title">dev</span>);</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">pci_driver</span> *<span class="title">pci_drv</span> = <span class="title">to_pci_driver</span>(<span class="title">drv</span>);</span></span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">pci_device_id</span> *<span class="title">found_id</span>;</span></span><br><span class="line"></span><br><span class="line">    found_id = pci_match_device(pci_drv, pci_dev);</span><br><span class="line">    <span class="keyword">if</span> (found_id)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到<code>pci_bus_match</code>调用的是<code>pci_match_device</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">const</span> struct pci_device_id *<span class="title">pci_match_device</span><span class="params">(struct pci_driver *drv,</span></span></span><br><span class="line"><span class="function"><span class="params">                            struct pci_dev *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">pci_dynid</span> *<span class="title">dynid</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Look at the dynamic ids first, before the static ones */</span></span><br><span class="line">    spin_lock(&amp;drv-&gt;dynids.lock);</span><br><span class="line">    list_for_each_entry(dynid, &amp;drv-&gt;dynids.<span class="built_in">list</span>, node) &#123;</span><br><span class="line">        <span class="keyword">if</span> (pci_match_one_device(&amp;dynid-&gt;id, dev)) &#123;</span><br><span class="line">            spin_unlock(&amp;drv-&gt;dynids.lock);</span><br><span class="line">            <span class="keyword">return</span> &amp;dynid-&gt;id;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    spin_unlock(&amp;drv-&gt;dynids.lock);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> pci_match_id(drv-&gt;id_table, dev);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最终调用的是<code>pci_match_id</code>匹配</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">const</span> struct pci_device_id *<span class="title">pci_match_id</span><span class="params">(<span class="keyword">const</span> struct pci_device_id *ids,</span></span></span><br><span class="line"><span class="function"><span class="params">                     struct pci_dev *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (ids) &#123;</span><br><span class="line">        <span class="keyword">while</span> (ids-&gt;vendor || ids-&gt;subvendor || ids-&gt;class_mask) &#123;</span><br><span class="line">            <span class="keyword">if</span> (pci_match_one_device(ids, dev))</span><br><span class="line">                <span class="keyword">return</span> ids;</span><br><span class="line">            ids++;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>遍历id_table，调用<code>pci_match_one_device</code>进行严格匹配</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">pci_device_id</span> *</span></span><br><span class="line"><span class="class"><span class="title">pci_match_one_device</span>(<span class="title">const</span> <span class="title">struct</span> <span class="title">pci_device_id</span> *<span class="title">id</span>, <span class="title">const</span> <span class="title">struct</span> <span class="title">pci_dev</span> *<span class="title">dev</span>)</span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="keyword">if</span> ((id-&gt;vendor == PCI_ANY_ID || id-&gt;vendor == dev-&gt;vendor) &amp;&amp;</span><br><span class="line">        (id-&gt;device == PCI_ANY_ID || id-&gt;device == dev-&gt;device) &amp;&amp;</span><br><span class="line">        (id-&gt;subvendor == PCI_ANY_ID || id-&gt;subvendor == dev-&gt;subsystem_vendor) &amp;&amp;</span><br><span class="line">        (id-&gt;subdevice == PCI_ANY_ID || id-&gt;subdevice == dev-&gt;subsystem_device) &amp;&amp;</span><br><span class="line">        !((id-&gt;class ^ dev-&gt;class) &amp; id-&gt;class_mask))</span><br><span class="line">        <span class="keyword">return</span> id;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>分别对vendor、device、subvendor、subdevice和class进行匹配，除非某一项配置为<code>PCI_ANY_ID</code>，否则都要进行严格匹配，只要有一项匹配不上则直接匹配失败。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;PCI是外围设备互连(Peripheral Component Interconnect)的简称，作为一种通用的总线接口标准，它已经普遍使用在了计算机中。&lt;strong&gt;PCI总线常见于x86体系，本文默认面向的体系为x86，注意x86架构下IO与内存是独立编址的&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;附： 本文默认读者熟悉Linux设备驱动模型，不熟悉的可以先阅读这两篇blog。&lt;br&gt;&lt;a href=&quot;https://www.caiyifan.cn/p/8d9c6db2.html&quot;&gt;Linux驱动之I2C子系统剖析&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.caiyifan.cn/p/41bdfd80.html&quot;&gt;Linux驱动之SPI子系统剖析&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;PCI寻址&quot;&gt;&lt;a href=&quot;#PCI寻址&quot; class=&quot;headerlink&quot; title=&quot;PCI寻址&quot;&gt;&lt;/a&gt;PCI寻址&lt;/h2&gt;&lt;p&gt;PCI系统总体布局组织为树状，从CPU连接的Host Bridge引出PCI主桥，主桥连接的是PCI总线0，可以直接连接PCI设备，或者再挂上PCI桥引出下一级PCI总线。&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="嵌入式" scheme="https://www.caiyifan.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
      <category term="linux驱动" scheme="https://www.caiyifan.cn/tags/linux%E9%A9%B1%E5%8A%A8/"/>
    
      <category term="PCI子系统" scheme="https://www.caiyifan.cn/tags/PCI%E5%AD%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>Linux驱动之SPI子系统剖析</title>
    <link href="https://www.caiyifan.cn/p/41bdfd80.html"/>
    <id>https://www.caiyifan.cn/p/41bdfd80.html</id>
    <published>2019-07-22T16:22:40.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<h2 id="SPI硬件基本概念"><a href="#SPI硬件基本概念" class="headerlink" title="SPI硬件基本概念"></a>SPI硬件基本概念</h2><p>SPI总线由四根通信线组成，<strong>全双工</strong>、主从方式串行同步通信，一次传输8bit，高位在前，低位在后。</p><ul><li><p>MOSI(Master Out Slave In): 主设备输出从设备输入</p></li><li><p>MISO(Master In Slave out):  从设备输入主设备输出</p></li><li><p>SCLK：同步信号的时钟线</p></li><li><p>CS: 片选线，通过片选来选择与哪一个从设备通信</p><blockquote><p>注: 与I2C对比，由于SPI采用的是两根单向的数据线，而不是I2C采用的双向数据线，所以SPI为全双工通信，而I2C半双工。</p><p>​      I2C选择总线上挂接的一个从设备是使用从地址来区分的，而SPI采用的是CS片选线</p></blockquote><a id="more"></a><p><img src="https://img.caiyifan.cn/spi"></p></li></ul><h2 id="SPI子系统框架"><a href="#SPI子系统框架" class="headerlink" title="SPI子系统框架"></a>SPI子系统框架</h2><ul><li>SPI核心层：  drivers&#x2F;spi&#x2F;spi.c </li><li>SPI总线驱动层(主机控制器驱动层)：drivers&#x2F;spi&#x2F;spi_s3c24xx. c</li><li>SPI设备驱动层：drivers&#x2F;spi&#x2F;spidev.c (内核提供的SPI通用设备驱动)</li></ul><p>Linux中的主从模式的总线子系统采用的是同一种分离思想，其分离的具体策略大同小异，同样分为<strong>设备驱动层</strong>、<strong>核心层</strong>、<strong>总线驱动层</strong>。具体的分离策略详细分析可参考<a href="https://www.caiyifan.cn/p/8d9c6db2.html">Linux驱动之I2C子系统剖析</a>中内核对I2C子系统框架的阐述。笔者在这与I2C子系统类比，列出数据结构名。</p><table><thead><tr><th align="center"></th><th align="center"><strong>I2C</strong></th><th align="center"><strong>SPI</strong></th></tr></thead><tbody><tr><td align="center"><strong>主机适配器(控制器)</strong></td><td align="center">struct i2c_adapter</td><td align="center">struct spi_master</td></tr><tr><td align="center"><strong>机控制器的操作方法</strong></td><td align="center">struct i2c_algorithm</td><td align="center">struct spi_bitbang</td></tr><tr><td align="center"><strong>从机设备</strong></td><td align="center">struct i2c_client</td><td align="center">struct spi_device</td></tr><tr><td align="center"><strong>从机设备板卡信息</strong></td><td align="center">struct i2c_board_info</td><td align="center">struct spi_board_info</td></tr><tr><td align="center"><strong>从机设备驱动</strong></td><td align="center">struct i2c_driver</td><td align="center">struct spi_driver</td></tr><tr><td align="center"><strong>一次完整的数据包</strong></td><td align="center">struct i2c_msg</td><td align="center">struct spi_transfer</td></tr><tr><td align="center"><strong>多个完整数据包的封装</strong></td><td align="center">无</td><td align="center">struct spi_message</td></tr></tbody></table><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>由于子系统架构与I2C等总线类似，所以不会在一些重复部分展开，具体分析可以参考的<a href="https://www.caiyifan.cn/p/8d9c6db2.html">Linux驱动之I2C子系统剖析</a>中的分析方法。</p><h3 id="SPI核心层"><a href="#SPI核心层" class="headerlink" title="SPI核心层"></a>SPI核心层</h3><p>SPI核心层代码位于<code>drivers/spi/spi.c </code>中,  从init函数开始分析</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">spi_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> status;</span><br><span class="line"></span><br><span class="line">    buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);</span><br><span class="line">    <span class="keyword">if</span> (!buf) &#123;</span><br><span class="line">        status = -ENOMEM;</span><br><span class="line">        <span class="keyword">goto</span> err0;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    status = bus_register(&amp;spi_bus_type);     <span class="comment">/* 注册SPI总线 */</span></span><br><span class="line">    <span class="keyword">if</span> (status &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">goto</span> err1;</span><br><span class="line"></span><br><span class="line">    status = class_register(&amp;spi_master_class);     <span class="comment">/* 注册SPI类 */</span></span><br><span class="line">    <span class="keyword">if</span> (status &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">goto</span> err2;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">err2:</span><br><span class="line">    bus_unregister(&amp;spi_bus_type);</span><br><span class="line">err1:</span><br><span class="line">    kfree(buf);</span><br><span class="line">    buf = <span class="literal">NULL</span>;</span><br><span class="line">err0:</span><br><span class="line">    <span class="keyword">return</span> status;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>spi_init</code>函数如同I2C核心层中的init函数一样做了两件事，注册SPI总线和创建SPI类，这是内核驱动模型的基本套路，就不比多说了。接下来看下match函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">const</span> struct spi_device_id *<span class="title">spi_match_id</span><span class="params">(<span class="keyword">const</span> struct spi_device_id *id,</span></span></span><br><span class="line"><span class="function"><span class="params">                        <span class="keyword">const</span> struct spi_device *sdev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">while</span> (id-&gt;name[<span class="number">0</span>]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">strcmp</span>(sdev-&gt;modalias, id-&gt;name))</span><br><span class="line">            <span class="keyword">return</span> id;</span><br><span class="line">        id++;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* SPI总线的match方法 */</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">spi_match_device</span><span class="params">(struct device *dev, struct device_driver *drv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">spi_device</span> *<span class="title">spi</span> = <span class="title">to_spi_device</span>(<span class="title">dev</span>);</span></span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">spi_driver</span> *<span class="title">sdrv</span> = <span class="title">to_spi_driver</span>(<span class="title">drv</span>);</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (sdrv-&gt;id_table)</span><br><span class="line">        <span class="keyword">return</span> !!spi_match_id(sdrv-&gt;id_table, spi);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">strcmp</span>(spi-&gt;modalias, drv-&gt;name) == <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，SPI设备和驱动的匹配是先匹配id_table中的name和设备的modalias，然后匹配驱动的name和设备的modalias。</p><h3 id="SPI总线驱动层"><a href="#SPI总线驱动层" class="headerlink" title="SPI总线驱动层"></a>SPI总线驱动层</h3><p>SPI的控制器驱动，即总线驱动层位于<code>drivers/spi/spi_s3c24xx. c</code>中，从init函数开始分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">s3c24xx_spi_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;   </span><br><span class="line">    <span class="keyword">return</span> platform_driver_probe(&amp;s3c24xx_spi_driver, s3c24xx_spi_probe);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>会发现SPI控制器驱动并不是用的是<code>platform_driver_register</code>接口来注册的，而是使用了另一个接口<code>platform_driver_probe</code>, 其实这是内核提供的不支持热插拔方式的专用平台总线驱动的注册接口，该接口接受两个参数，第一个就是熟知的<code>struct platform_driver</code>，第二个则是probe函数，当驱动和设备匹配上后就会调用这个probe函数。进入到 <code>s3c24xx_spi_probe</code>函数进行分析，probe函数的代码比较多，分段进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">s3c2410_spi_info</span> *<span class="title">pdata</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">s3c24xx_spi</span> *<span class="title">hw</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">spi_master</span> *<span class="title">master</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">resource</span> *<span class="title">res</span>;</span></span><br><span class="line"><span class="keyword">int</span> err = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 实例化spi控制器 */</span></span><br><span class="line">master = spi_alloc_master(&amp;pdev-&gt;dev, squdongqudongizeof(struct s3c24xx_spi));</span><br><span class="line"><span class="keyword">if</span> (master == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"No memory for spi_master\n"</span>);</span><br><span class="line">    err = -ENOMEM;</span><br><span class="line">    <span class="keyword">goto</span> err_nomem;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 获取spi的私有数据结构体并初始化为空 */</span></span><br><span class="line">hw = spi_master_get_devdata(master);</span><br><span class="line"><span class="built_in">memset</span>(hw, <span class="number">0</span>, <span class="keyword">sizeof</span>(struct s3c24xx_spi));</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 设置spi的私有数据*/</span></span><br><span class="line">hw-&gt;master = spi_master_get(master);</span><br><span class="line">hw-&gt;pdata = pdata = pdev-&gt;dev.platform_data;</span><br><span class="line">hw-&gt;dev = &amp;pdev-&gt;dev;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (pdata == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"No platform data supplied\n"</span>);</span><br><span class="line">    err = -ENOENT;</span><br><span class="line">    <span class="keyword">goto</span> err_no_pdata;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">platform_set_drvdata(pdev, hw);</span><br><span class="line">init_completion(&amp;hw-&gt;done);     <span class="comment">/* 初始化completion,  用于IO的同步*/</span></span><br></pre></td></tr></table></figure><p>实例化SPI控制器后设置SPI的私有数据，然后初始化completion。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* initialise fiq handler */</span></span><br><span class="line"></span><br><span class="line">s3c24xx_spi_initfiq(hw);       <span class="comment">/* 初始化s3c24xx_spi结构体中的handler，为其绑定中断处理函数 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* setup the master state. */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* the spi-&gt;mode bits understood by this driver: */</span></span><br><span class="line">master-&gt;mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;</span><br><span class="line"></span><br><span class="line">master-&gt;num_chipselect = hw-&gt;pdata-&gt;num_cs;</span><br><span class="line">master-&gt;bus_num = pdata-&gt;bus_num;</span><br></pre></td></tr></table></figure><p>这一段初始化<code>s3c24xx_spi</code>结构体中的handler，为其绑定中断处理函数，然后设置了主机控制器支持的SPI模式，设置master的片选线编号和总线编号。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">hw-&gt;bitbang.master         = hw-&gt;master;</span><br><span class="line">hw-&gt;bitbang.setup_transfer = s3c24xx_spi_setupxfer;</span><br><span class="line">hw-&gt;bitbang.chipselect     = s3c24xx_spi_chipsel;</span><br><span class="line">hw-&gt;bitbang.txrx_bufs      = s3c24xx_spi_txrx;</span><br><span class="line"></span><br><span class="line">hw-&gt;master-&gt;<span class="built_in">setup</span>  = s3c24xx_spi_setup;</span><br><span class="line">hw-&gt;master-&gt;cleanup = s3c24xx_spi_cleanup;</span><br></pre></td></tr></table></figure><p>bitbang表示的是SPI的操作方法，这一段关键是填充了setup_transfer，即传输方法。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">res = platform_get_resource(pdev, IORESOURCE_MEM, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (res == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"Cannot get IORESOURCE_MEM\n"</span>);</span><br><span class="line">    err = -ENOENT;</span><br><span class="line">    <span class="keyword">goto</span> err_no_iores;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">hw-&gt;ioarea = request_mem_region(res-&gt;start, resource_size(res),</span><br><span class="line">                pdev-&gt;name);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (hw-&gt;ioarea == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"Cannot reserve region\n"</span>);</span><br><span class="line">    err = -ENXIO;</span><br><span class="line">    <span class="keyword">goto</span> err_no_iores;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">hw-&gt;regs = ioremap(res-&gt;start, resource_size(res));</span><br><span class="line"><span class="keyword">if</span> (hw-&gt;regs == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"Cannot map IO\n"</span>);</span><br><span class="line">    err = -ENXIO;</span><br><span class="line">    <span class="keyword">goto</span> err_no_iomap;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">hw-&gt;irq = platform_get_irq(pdev, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (hw-&gt;irq &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"No IRQ specified\n"</span>);</span><br><span class="line">    err = -ENOENT;</span><br><span class="line">    <span class="keyword">goto</span> err_no_irq;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">err = request_irq(hw-&gt;irq, s3c24xx_spi_irq, <span class="number">0</span>, pdev-&gt;name, hw);</span><br><span class="line"><span class="keyword">if</span> (err) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"Cannot claim IRQ\n"</span>);</span><br><span class="line">    <span class="keyword">goto</span> err_no_irq;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">hw-&gt;clk = clk_get(&amp;pdev-&gt;dev, <span class="string">"spi"</span>);</span><br><span class="line"><span class="keyword">if</span> (IS_ERR(hw-&gt;clk)) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"No clock for device\n"</span>);</span><br><span class="line">    err = PTR_ERR(hw-&gt;clk);</span><br><span class="line">    <span class="keyword">goto</span> err_no_clk;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* setup any gpio we can */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!pdata-&gt;set_cs) &#123;</span><br><span class="line">    <span class="keyword">if</span> (pdata-&gt;pin_cs &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        dev_err(&amp;pdev-&gt;dev, <span class="string">"No chipselect pin\n"</span>);</span><br><span class="line">        <span class="keyword">goto</span> err_register;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    err = gpio_request(pdata-&gt;pin_cs, dev_name(&amp;pdev-&gt;dev));</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">        dev_err(&amp;pdev-&gt;dev, <span class="string">"Failed to get gpio for cs\n"</span>);</span><br><span class="line">        <span class="keyword">goto</span> err_register;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    hw-&gt;set_cs = s3c24xx_spi_gpiocs;</span><br><span class="line">    gpio_direction_output(pdata-&gt;pin_cs, <span class="number">1</span>);</span><br><span class="line">&#125; <span class="keyword">else</span></span><br><span class="line">    hw-&gt;set_cs = pdata-&gt;set_cs;</span><br><span class="line"></span><br><span class="line">s3c24xx_spi_initialsetup(hw);  </span><br><span class="line">    s3c24xx_spi_initialsetup(hw);  <span class="comment">/* spi 控制器初始化 */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* register our spi controller */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/* 内部最后spi_register_master来注册SPI控制器 */</span></span><br><span class="line">err = spi_bitbang_start(&amp;hw-&gt;bitbang);</span><br><span class="line"><span class="keyword">if</span> (err) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"Failed to register SPI master\n"</span>);</span><br><span class="line">    <span class="keyword">goto</span> err_register;weiyu</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 这一段是跟具体硬件息息相关的，从获取平台资源开始，然后分别做了IO的映射、中断的申请与中断处理函数的绑定、时钟的初始化和片选的GPIO的申请和拉高电平。最后关键是调用了<code>s3c24xx_spi_initialsetup</code>函数，该函数内部最后调用了<code>spi_register_master</code>方法来注册SPI控制器。类比I2C在probe函数中调用的<code>i2c_add_numbered_adapter</code>函数，其内部会扫描SPI的板卡信息，然后利用板卡信息生成SPI设备，并将控制器<code>spi_master</code>挂接到<code>spi_device</code>上，随后在SPI设备驱动层中注册设备驱动后调用probe函数会获取到该<code>spi_device</code>，然后即可通过<code>spi_device</code> 中挂接的<code>spi_master</code>来调用控制器的操作方法<code>spi_bitbang_transfer</code>来传输数据。要注意的是SPI与I2C提供的通用设备驱动不同，其设备节点的生成并不是在注册主机控制器中完成的，而是在通用设备中完成的，这一段从之后设备驱动层的分析可以看出。这一段逻辑类似于I2C，就不参考源码分析了。(好吧，一如既往的懒QAQ)</p><h3 id="SPI设备驱动层"><a href="#SPI设备驱动层" class="headerlink" title="SPI设备驱动层"></a>SPI设备驱动层</h3><p>SPI通用设备驱动位于<code>drivers/spi/spidev.c</code>中，从init函数开始。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">spi_driver</span> <span class="title">spidev_spi_driver</span> = &#123;</span></span><br><span class="line">    .driver = &#123;</span><br><span class="line">        .name =     <span class="string">"spidev"</span>,</span><br><span class="line">        .owner =    THIS_MODULE,</span><br><span class="line">    &#125;,</span><br><span class="line">    .probe =    spidev_probe,</span><br><span class="line">    .<span class="built_in">remove</span> =   __devexit_p(spidev_remove),</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">spidev_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> status;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Claim our 256 reserved device numbers.  Then register a class</span></span><br><span class="line"><span class="comment">     * that will key udev/mdev to add/remove /dev nodes.  Last, register</span></span><br><span class="line"><span class="comment">     * the driver which manages those device numbers.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    BUILD_BUG_ON(N_SPI_MINORS &gt; <span class="number">256</span>);</span><br><span class="line">    <span class="comment">/* 注册为字符设备驱动，为应用层提供调用接口 */</span></span><br><span class="line">    status = register_chrdev(SPIDEV_MAJOR, <span class="string">"spi"</span>, &amp;spidev_fops);</span><br><span class="line">    <span class="keyword">if</span> (status &lt; <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">return</span> status;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 创建spidev类 */</span></span><br><span class="line">    spidev_class = class_create(THIS_MODULE, <span class="string">"spidev"</span>);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(spidev_class)) &#123;</span><br><span class="line">        unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);</span><br><span class="line">        <span class="keyword">return</span> PTR_ERR(spidev_class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 调用核心层提供的接口来注册设备驱动 */</span></span><br><span class="line">    status = spi_register_driver(&amp;spidev_spi_driver);</span><br><span class="line">    <span class="keyword">if</span> (status &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        class_destroy(spidev_class);</span><br><span class="line">        unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> status;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>有空再写了，先休息啦</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;SPI硬件基本概念&quot;&gt;&lt;a href=&quot;#SPI硬件基本概念&quot; class=&quot;headerlink&quot; title=&quot;SPI硬件基本概念&quot;&gt;&lt;/a&gt;SPI硬件基本概念&lt;/h2&gt;&lt;p&gt;SPI总线由四根通信线组成，&lt;strong&gt;全双工&lt;/strong&gt;、主从方式串行同步通信，一次传输8bit，高位在前，低位在后。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;MOSI(Master Out Slave In): 主设备输出从设备输入&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MISO(Master In Slave out):  从设备输入主设备输出&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SCLK：同步信号的时钟线&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CS: 片选线，通过片选来选择与哪一个从设备通信&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注: 与I2C对比，由于SPI采用的是两根单向的数据线，而不是I2C采用的双向数据线，所以SPI为全双工通信，而I2C半双工。&lt;/p&gt;
&lt;p&gt;​      I2C选择总线上挂接的一个从设备是使用从地址来区分的，而SPI采用的是CS片选线&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="嵌入式" scheme="https://www.caiyifan.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
      <category term="linux驱动" scheme="https://www.caiyifan.cn/tags/linux%E9%A9%B1%E5%8A%A8/"/>
    
      <category term="SPI子系统" scheme="https://www.caiyifan.cn/tags/SPI%E5%AD%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>Linux驱动之Misc子系统剖析</title>
    <link href="https://www.caiyifan.cn/p/ac4a5699.html"/>
    <id>https://www.caiyifan.cn/p/ac4a5699.html</id>
    <published>2019-07-03T10:21:40.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<h2 id="何为Misc设备"><a href="#何为Misc设备" class="headerlink" title="何为Misc设备"></a>何为Misc设备</h2><p>Linux驱动分为字符设备驱动、块设备驱动和网络设备驱动，而字符设备又包括很多种，内核使用主设备号来区分各个字符设备驱动，在<code>include/linux/major.h</code>文件中已经预先定义好了各类字符设备的主设备号，但是即便如此，仍然存在着大量字符设备无法准确归类，对于这些设备，内核提供了一种Misc（杂项）设备来安放它们的去处。</p><a id="more"></a><h2 id="使用Misc设备的好处"><a href="#使用Misc设备的好处" class="headerlink" title="使用Misc设备的好处"></a>使用Misc设备的好处</h2><p>Misc子系统使用一个统一的主设备号来管理，当需要注册Misc驱动时，内核会为其分配次设备号。而如果采用普通字符设备驱动的方式，无论主设备号是静态分配还是动态分配，都会消耗掉一个主设备号，而且如果系统存在着大量的无法准确归类的字符设备驱动，那会大量浪费主设备号;当需要开发一个功能较简单的字符设备驱动，导出接口让用户空间程序方便地控制硬件，只需要使用Misc框架提供的接口即可快速地实现一个Misc设备驱动。</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>Misc框架位于<code>driver/char/misc.c</code>文件中，从<code>misc_init</code>函数开始分析</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">misc_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> err;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_PROC_FS</span></span><br><span class="line">    proc_create(<span class="string">"misc"</span>, <span class="number">0</span>, <span class="literal">NULL</span>, &amp;misc_proc_fops);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    misc_class = class_create(THIS_MODULE, <span class="string">"misc"</span>);      <span class="comment">// 创建misc类</span></span><br><span class="line">    err = PTR_ERR(misc_class);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(misc_class))</span><br><span class="line">        <span class="keyword">goto</span> fail_remove;</span><br><span class="line"></span><br><span class="line">    err = -EIO;</span><br><span class="line">    <span class="keyword">if</span> (register_chrdev(MISC_MAJOR,<span class="string">"misc"</span>,&amp;misc_fops))  <span class="comment">// misc通过实现为字符设备驱动来注册</span></span><br><span class="line">        <span class="keyword">goto</span> fail_printk;</span><br><span class="line">    misc_class-&gt;devnode = misc_devnode;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">fail_printk:</span><br><span class="line">    printk(<span class="string">"unable to get major %d for misc devices\n"</span>, MISC_MAJOR);</span><br><span class="line">    class_destroy(misc_class);</span><br><span class="line">fail_remove:</span><br><span class="line">    remove_proc_entry(<span class="string">"misc"</span>, <span class="literal">NULL</span>);</span><br><span class="line">    <span class="keyword">return</span> err;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先是创建了Misc类，随后将Misc子系统实现为字符设备驱动来注册到内核中，并为其绑定了fops。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">file_operations</span> <span class="title">misc_fops</span> = &#123;</span></span><br><span class="line">    .owner      = THIS_MODULE,</span><br><span class="line">    .<span class="built_in">open</span>       = misc_open,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>fops只实现了open方法，暂且不分析fops，先分析内核为驱动开发人员导出的注册接口<code>misc_register</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">misc_register</span><span class="params">(struct miscdevice * misc)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">miscdevice</span> *<span class="title">c</span>;</span></span><br><span class="line">    <span class="keyword">dev_t</span> dev;</span><br><span class="line">    <span class="keyword">int</span> err = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    INIT_LIST_HEAD(&amp;misc-&gt;<span class="built_in">list</span>);</span><br><span class="line"></span><br><span class="line">    mutex_lock(&amp;misc_mtx);</span><br><span class="line">    <span class="comment">// 查找设备是否已经注册 </span></span><br><span class="line">    list_for_each_entry(c, &amp;misc_list, <span class="built_in">list</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (c-&gt;minor == misc-&gt;minor) &#123;</span><br><span class="line">            mutex_unlock(&amp;misc_mtx);</span><br><span class="line">            <span class="keyword">return</span> -EBUSY;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 动态分配次设备号</span></span><br><span class="line">    <span class="keyword">if</span> (misc-&gt;minor == MISC_DYNAMIC_MINOR) &#123;</span><br><span class="line">        <span class="keyword">int</span> i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);   <span class="comment">// 找到位图中第一个为0的bit</span></span><br><span class="line">        <span class="keyword">if</span> (i &gt;= DYNAMIC_MINORS) &#123;       <span class="comment">// 没有找到</span></span><br><span class="line">            mutex_unlock(&amp;misc_mtx);</span><br><span class="line">            <span class="keyword">return</span> -EBUSY;</span><br><span class="line">        &#125;</span><br><span class="line">        misc-&gt;minor = DYNAMIC_MINORS - i - <span class="number">1</span>;   <span class="comment">// 分配次设备号</span></span><br><span class="line">        set_bit(i, misc_minors);                <span class="comment">// 将分配的次设备号加入位图</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 生成设备号</span></span><br><span class="line">    dev = MKDEV(MISC_MAJOR, misc-&gt;minor);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注册设备</span></span><br><span class="line">    misc-&gt;this_device = device_create(misc_class, misc-&gt;parent, dev,</span><br><span class="line">                      misc, <span class="string">"%s"</span>, misc-&gt;name);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(misc-&gt;this_device)) &#123;</span><br><span class="line">        <span class="keyword">int</span> i = DYNAMIC_MINORS - misc-&gt;minor - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (i &lt; DYNAMIC_MINORS &amp;&amp; i &gt;= <span class="number">0</span>)</span><br><span class="line">            clear_bit(i, misc_minors);</span><br><span class="line">        err = PTR_ERR(misc-&gt;this_device);</span><br><span class="line">        <span class="keyword">goto</span> out;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Add it to the front, so that later devices can "override"</span></span><br><span class="line"><span class="comment">     * earlier defaults</span></span><br><span class="line"><span class="comment">     */</span> </span><br><span class="line">    <span class="comment">// 将已注册的驱动添加到链表上，open时可遍历链表来替换真正的fops</span></span><br><span class="line">    list_add(&amp;misc-&gt;<span class="built_in">list</span>, &amp;misc_list);</span><br><span class="line"> out:</span><br><span class="line">    mutex_unlock(&amp;misc_mtx);</span><br><span class="line">    <span class="keyword">return</span> err;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上面可以看到，先查找设备是否已经注册（内核采用一个链表来管理已经注册的Misc设备驱动），然后判断是否需要动态分配次设备号（内核使用位图来管理已经注册的Misc次设备号），然后生成设备号，通过<code>device_create</code>函数在Misc类下创建设备，这时候<code>/dev</code>目录下就会根据<code>misc-&gt;name</code>的值生成设备节点，然后将已经注册的驱动添加到链表上。</p><p>把关注点放到该接口需要传递的结构体<code>struct miscdevice</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">miscdevice</span>  &#123;</span></span><br><span class="line">    <span class="keyword">int</span> minor;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *name;</span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">file_operations</span> *<span class="title">fops</span>;</span>   <span class="comment">// 真正的fops</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">list</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">device</span> *<span class="title">parent</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">device</span> *<span class="title">this_device</span>;</span></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">char</span> *nodename;</span><br><span class="line">    <span class="keyword">mode_t</span> mode;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以看到该结构体内部也定义了一个fops，需要驱动开发者使用该接口时实现一个fops，其实这个才是真正的fops，而在<code>misc_init</code>函数中调用<code>register_chrdev</code>来绑定的fops是用来<strong>中转数据</strong>的，具体情况可以从其open方法可以分析出来。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">misc_open</span><span class="params">(struct inode * inode, struct file * file)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> minor = iminor(inode);</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">miscdevice</span> *<span class="title">c</span>;</span></span><br><span class="line">    <span class="keyword">int</span> err = -ENODEV;</span><br><span class="line">    <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">file_operations</span> *<span class="title">old_fops</span>, *<span class="title">new_fops</span> = <span class="title">NULL</span>;</span></span><br><span class="line"></span><br><span class="line">    mutex_lock(&amp;misc_mtx);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 遍历链表来查找真正的fops</span></span><br><span class="line">    list_for_each_entry(c, &amp;misc_list, <span class="built_in">list</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (c-&gt;minor == minor) &#123;</span><br><span class="line">            new_fops = fops_get(c-&gt;fops);   <span class="comment">// 获取真正的fops</span></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">if</span> (!new_fops) &#123;</span><br><span class="line">        mutex_unlock(&amp;misc_mtx);</span><br><span class="line">        request_module(<span class="string">"char-major-%d-%d"</span>, MISC_MAJOR, minor);</span><br><span class="line">        mutex_lock(&amp;misc_mtx);</span><br><span class="line"></span><br><span class="line">        list_for_each_entry(c, &amp;misc_list, <span class="built_in">list</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (c-&gt;minor == minor) &#123;</span><br><span class="line">                new_fops = fops_get(c-&gt;fops);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!new_fops)</span><br><span class="line">            <span class="keyword">goto</span> fail;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    err = <span class="number">0</span>;</span><br><span class="line">    old_fops = file-&gt;f_op;</span><br><span class="line">    <span class="comment">// 替换真正的fops，之后再调用其他接口（write、ioctl、close）时调用的是真正的fops</span></span><br><span class="line">    file-&gt;f_op = new_fops;</span><br><span class="line">    <span class="comment">// 调用真正的fops中的open方法</span></span><br><span class="line">    <span class="keyword">if</span> (file-&gt;f_op-&gt;<span class="built_in">open</span>) &#123;</span><br><span class="line">        file-&gt;private_data = c;</span><br><span class="line">        err=file-&gt;f_op-&gt;<span class="built_in">open</span>(inode,file);</span><br><span class="line">        <span class="keyword">if</span> (err) &#123;</span><br><span class="line">            fops_put(file-&gt;f_op);</span><br><span class="line">            file-&gt;f_op = fops_get(old_fops);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    fops_put(old_fops);</span><br><span class="line">fail:</span><br><span class="line">    mutex_unlock(&amp;misc_mtx);</span><br><span class="line">    <span class="keyword">return</span> err;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>遍历用来管理Misc设备驱动的链表，根据次设备号来找到真正的由驱动开发者用<code>misc_register</code>接口注册的Misc驱动，然后获取其fops，该fops就是真正的fops。然后替换真正的fops，之后再调用其他接口（write、ioctl、close）时调用的则是真正的fops，所以用来中转数据的那个fops只定义了一个open方法。最后，该open方法并不是真正的open方法，所以需要调用真正的fops中的open方法。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Misc子系统<strong><em>使用同一个驱动</em>来向上提供多个设备文件节点，向下控制多个(相应的)设备</strong>。Misc驱动本质上也是字符驱动，只是它借用Misc子系统的框架来更方便的向内核注册而已。驱动开发人员只需要把Misc设备的一些基本信息通过<code>struct miscdevice</code>来构建，再通过<code>misc_register</code>接口向内核注册即可。</p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;何为Misc设备&quot;&gt;&lt;a href=&quot;#何为Misc设备&quot; class=&quot;headerlink&quot; title=&quot;何为Misc设备&quot;&gt;&lt;/a&gt;何为Misc设备&lt;/h2&gt;&lt;p&gt;Linux驱动分为字符设备驱动、块设备驱动和网络设备驱动，而字符设备又包括很多种，内核使用主设备号来区分各个字符设备驱动，在&lt;code&gt;include/linux/major.h&lt;/code&gt;文件中已经预先定义好了各类字符设备的主设备号，但是即便如此，仍然存在着大量字符设备无法准确归类，对于这些设备，内核提供了一种Misc（杂项）设备来安放它们的去处。&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="嵌入式" scheme="https://www.caiyifan.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
      <category term="linux驱动" scheme="https://www.caiyifan.cn/tags/linux%E9%A9%B1%E5%8A%A8/"/>
    
      <category term="Misc子系统" scheme="https://www.caiyifan.cn/tags/Misc%E5%AD%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>Linux驱动之I2C子系统剖析</title>
    <link href="https://www.caiyifan.cn/p/8d9c6db2.html"/>
    <id>https://www.caiyifan.cn/p/8d9c6db2.html</id>
    <published>2019-06-30T04:38:38.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<p>I2C是广泛应用于计算机中的串行总线，用于处理器和其外设之间的通信。</p><h2 id="I2C硬件基本概念"><a href="#I2C硬件基本概念" class="headerlink" title="I2C硬件基本概念"></a>I2C硬件基本概念</h2><ul><li><p>I2C总线由两根传递数据的双向信号线与一根地线组成，半双工、主从方式通信。</p><ul><li>Serial Clock Line (SCL)</li><li>Serial Data Address (SDA)</li></ul></li><li><p>每个设备都有一个唯一设备地址，一次传输8bit，高位在前，低位在后。 </p></li><li><p>一次完整的I2C通信需要经历一个完整的时序，I2C总线通信完整时序如下图。一般在驱动中无需关心具体时序，只需操作SoC中的I2C控制器即可，只有在裸机下需要用GPIO模拟I2C通信时才需用到，所以笔者在本文不阐述I2C时序（其实就是懒 O__O “…）。</p><a id="more"></a><p><img src="https://img.caiyifan.cn/I2C%E6%97%B6%E5%BA%8F.png"></p></li><li><p>总线速度有三种模式</p><ul><li>标准模式 100kbps</li><li>快速模式 400kbps</li><li>高速模式 3.4Mbps</li></ul></li></ul><h2 id="I2C子系统框架"><a href="#I2C子系统框架" class="headerlink" title="I2C子系统框架"></a>I2C子系统框架</h2><ul><li>I2C设备驱动层：drivers&#x2F;i2c&#x2F;i2c-dev.c (通用型) 或者为特定设备定制的设备驱动（比如E2PROM驱动）</li><li>I2C核心层： drivers&#x2F;i2c&#x2F;i2c-coere.c</li><li>I2C总线驱动层(主机控制器驱动层)：drivers&#x2F;i2c&#x2F;busses&#x2F;i2c-s3c2410.c</li></ul><h3 id="I2C设备驱动层"><a href="#I2C设备驱动层" class="headerlink" title="I2C设备驱动层"></a>I2C设备驱动层</h3><ul><li>是I2C从机的驱动程序</li><li>给用户提供调用接口</li><li>内核提供两种方式来实现设备驱动：<ul><li>第一种是内核默认实现的<strong>通用型的I2C设备驱动</strong>，位于<code>drivers/i2c/i2c-dev.c</code>中。 这种方式<strong>仅仅只是封装了I2C的基本操作</strong>，相当于<strong>只是封装了I2C的基本时序</strong>，向应用层只提供了I2C基本操作的接口，该接口通用于所有的I2C设备。具体设备相关的操作，需要开发者在应用层根据硬件特性来完成对设备的操作。<strong>该方式的优点就是通用，而缺点也很明显，封装的不够彻底，需要应用开发人员对硬件有一定程度的了解</strong>。</li><li>第二种是根据特定设备来编写的<strong>特定的I2C设备驱动</strong>, 该方式<strong>彻底封装了硬件的操作</strong>，提供给应用层的接口<strong>彻底屏蔽I2C的通信细节</strong>。该方式的<strong>优点就是应用开发人员无需关心硬件</strong>。</li></ul></li></ul><h3 id="I2C核心层"><a href="#I2C核心层" class="headerlink" title="I2C核心层"></a>I2C核心层</h3><ul><li>注册I2C总线</li><li>由内核开发人员编写的，不涉及具体硬件</li><li>给驱动编程人员提供编程接口</li></ul><h3 id="I2C总线驱动层"><a href="#I2C总线驱动层" class="headerlink" title="I2C总线驱动层"></a>I2C总线驱动层</h3><ul><li>是I2C主机适配器的驱动程序</li><li>初始化I2C适配器(控制器)</li><li>实现操作方法：根据I2C操作时序进行操作I2C控制器实现收发数据</li></ul><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>源码中会涉及到一部分SMBus相关内容，SMBus是Intel在I2C的基础上开发的类似I2C的总线，本文不探讨SMBus相关内容（其实说白了，还是懒QAQ）。笔者会大体上对I2C子系统的源码进行分析，如若分析的有出入，还望指出。</p><h3 id="I2C核心层-1"><a href="#I2C核心层-1" class="headerlink" title="I2C核心层"></a>I2C核心层</h3><p>I2C核心层的实现位于<code>drivers/i2c/i2c-core.c</code>中，笔者从<code>i2c_init</code>函数开始分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">i2c_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> retval;</span><br><span class="line"></span><br><span class="line">    retval = bus_register(&amp;i2c_bus_type);     <span class="comment">// 注册I2C总线 </span></span><br><span class="line">    <span class="keyword">if</span> (retval)</span><br><span class="line">        <span class="keyword">return</span> retval;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_I2C_COMPAT</span></span><br><span class="line">    i2c_adapter_compat_class = class_compat_register(<span class="string">"i2c-adapter"</span>);</span><br><span class="line">    <span class="keyword">if</span> (!i2c_adapter_compat_class) &#123;</span><br><span class="line">        retval = -ENOMEM;</span><br><span class="line">        <span class="keyword">goto</span> bus_err;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    retval = i2c_add_driver(&amp;dummy_driver);    <span class="comment">// 注册了一个虚假的I2C驱动</span></span><br><span class="line">    <span class="keyword">if</span> (retval)</span><br><span class="line">        <span class="keyword">goto</span> class_err;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">class_err:</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> CONFIG_I2C_COMPAT</span></span><br><span class="line">    class_compat_unregister(i2c_adapter_compat_class);</span><br><span class="line">bus_err:</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    bus_unregister(&amp;i2c_bus_type);</span><br><span class="line">    <span class="keyword">return</span> retval;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该函数先是调用了<code>bus_register</code>函数注册了I2C总线，随后调用<code>i2c_add_driver</code>函数来注册了一个虚假的I2C驱动。</p><p>先对注册的I2C总线<code>i2c_bus_type</code>进行分析</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">bus_type</span> <span class="title">i2c_bus_type</span> = &#123;</span></span><br><span class="line">    .name       = <span class="string">"i2c"</span>,</span><br><span class="line">    .match      = i2c_device_match,</span><br><span class="line">    .probe      = i2c_device_probe,</span><br><span class="line">    .<span class="built_in">remove</span>     = i2c_device_remove,</span><br><span class="line">    .<span class="built_in">shutdown</span>   = i2c_device_shutdown,</span><br><span class="line">    .pm     = &amp;i2c_device_pm_ops,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>根据Linux设备驱动模型的原理，I2C总线下会挂载两条链表，分别为设备链和驱动链，只要其中一个链表有结点插入，即会通过<code>i2c_device_match</code>函数来遍历另一条链表去匹配设备与驱动，一旦匹配上则会调用<code>i2c_device_probe</code>函数，而<code>i2c_device_probe</code>函数又会调用i2c_driver的<code>probe</code>函数。进到<code>i2c_device_match</code>和<code>i2c_device_probe</code>进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">i2c_device_match</span><span class="params">(struct device *dev, struct device_driver *drv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_client</span>   *<span class="title">client</span> = <span class="title">i2c_verify_client</span>(<span class="title">dev</span>);</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_driver</span>   *<span class="title">driver</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!client)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    driver = to_i2c_driver(drv);</span><br><span class="line">    <span class="comment">/* match on an id table if there is one */</span></span><br><span class="line">    <span class="keyword">if</span> (driver-&gt;id_table)</span><br><span class="line">        <span class="keyword">return</span> i2c_match_id(driver-&gt;id_table, client) != <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到, <code>i2c_device_match</code>函数调用的是<code>i2c_match_id</code>函数来进行匹配。从源码中可见，<strong>需要注意的是I2C总线匹配方式不同于Platform总线，I2C总线只匹配<code>id_table</code>中的name，并不会去匹配driver中的name</strong>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">i2c_device_probe</span><span class="params">(struct device *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_client</span>  *<span class="title">client</span> = <span class="title">i2c_verify_client</span>(<span class="title">dev</span>);</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_driver</span>  *<span class="title">driver</span>;</span></span><br><span class="line">    <span class="keyword">int</span> status;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!client)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    driver = to_i2c_driver(dev-&gt;driver);</span><br><span class="line">    <span class="keyword">if</span> (!driver-&gt;probe || !driver-&gt;id_table)</span><br><span class="line">        <span class="keyword">return</span> -ENODEV;</span><br><span class="line">    client-&gt;driver = driver;</span><br><span class="line">    <span class="keyword">if</span> (!device_can_wakeup(&amp;client-&gt;dev))</span><br><span class="line">        device_init_wakeup(&amp;client-&gt;dev,</span><br><span class="line">                    client-&gt;flags &amp; I2C_CLIENT_WAKE);</span><br><span class="line">    dev_dbg(dev, <span class="string">"probe\n"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 调用driver中的probe函数 */</span></span><br><span class="line">    status = driver-&gt;probe(client, i2c_match_id(driver-&gt;id_table, client));</span><br><span class="line">    <span class="keyword">if</span> (status) &#123;</span><br><span class="line">        client-&gt;driver = <span class="literal">NULL</span>;</span><br><span class="line">        i2c_set_clientdata(client, <span class="literal">NULL</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> status;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，的确是调用<code>driver-&gt;probe</code>来进行真正的probe。<strong>需要注意的是<code>if (!driver-&gt;probe || !driver-&gt;id_table)   return -ENODEV;</code>中对<code>id_table</code>进行了非空判断，所以如果采用设备树方式进行匹配也需要对<code>.id_table</code>进行有效赋值，否则会出现match上了但probe函数不会调用的奇怪现象</strong>，个人感觉这应该是个bug，毕竟这个核心层在设备树出现之前就已经存在了。</p><p>回到<code>i2c_init</code>函数，然后注册了一个空的名为<code>dummy</code>的i2c_driver。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">dummy_probe</span><span class="params">(struct i2c_client *client,</span></span></span><br><span class="line"><span class="function"><span class="params">               <span class="keyword">const</span> struct i2c_device_id *id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">dummy_remove</span><span class="params">(struct i2c_client *client)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">i2c_driver</span> <span class="title">dummy_driver</span> = &#123;</span></span><br><span class="line">    .driver.name    = <span class="string">"dummy"</span>,</span><br><span class="line">    .probe      = dummy_probe,</span><br><span class="line">    .<span class="built_in">remove</span>     = dummy_remove,</span><br><span class="line">    .id_table   = dummy_id,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>可以看到这是一个完全空的虚假驱动，而I2C核心层为何要注册一个假的驱动不得而知，笔者查阅了网上资料也没法得知，但是<code>/sys/bus/i2c/drivers/dummy</code>确实存在，所以笔者猜测应该纯粹是开发该层次调试用的。</p><p>核心层还提供了一系列函数接口供驱动开发者注册和注销驱动：</p><ul><li>i2c_add_adapter  注册I2C主机适配器驱动 (动态分配总线号)</li><li>i2c_add_numbered_adapter 注册I2C主机适配器驱动 (静态指定总线号)</li><li>i2c_del_adapter  注销I2C主机适配器驱动</li><li>i2c_add_driver   注册I2C从机设备驱动   </li><li>i2c_del_driver   注销I2C从机设备驱动</li></ul><p>其他函数暂不分析，在分析其他层的时候调用时再进行分析。</p><h3 id="I2C设备驱动层-1"><a href="#I2C设备驱动层-1" class="headerlink" title="I2C设备驱动层"></a>I2C设备驱动层</h3><p>笔者先从内核提供的通用驱动开始分析，最后在文末给出特定驱动的分析。内核提供了一个通用于所有设备的I2C设备驱动，用户可以在应用层实现对I2C的驱动，其实现位于<code>drivers/i2c/i2c-dev.c</code>中。同样从init函数开始，笔者从<code>i2c_dev_init</code>函数开始分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">i2c_dev_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> res;</span><br><span class="line"></span><br><span class="line">    printk(KERN_INFO <span class="string">"i2c /dev entries driver\n"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 将通用驱动注册为字符设备驱动，并提供file_operations 操作方法 */</span></span><br><span class="line">    res = register_chrdev(I2C_MAJOR, <span class="string">"i2c"</span>, &amp;i2cdev_fops);</span><br><span class="line">    <span class="keyword">if</span> (res)</span><br><span class="line">        <span class="keyword">goto</span> out;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 创建类 */</span></span><br><span class="line">    i2c_dev_class = class_create(THIS_MODULE, <span class="string">"i2c-dev"</span>);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(i2c_dev_class)) &#123;</span><br><span class="line">        res = PTR_ERR(i2c_dev_class);</span><br><span class="line">        <span class="keyword">goto</span> out_unreg_chrdev;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 注册I2C从机设备驱动 */</span></span><br><span class="line">    res = i2c_add_driver(&amp;i2cdev_driver);</span><br><span class="line">    <span class="keyword">if</span> (res)</span><br><span class="line">        <span class="keyword">goto</span> out_unreg_class;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">out_unreg_class:</span><br><span class="line">    class_destroy(i2c_dev_class);</span><br><span class="line">out_unreg_chrdev:</span><br><span class="line">    unregister_chrdev(I2C_MAJOR, <span class="string">"i2c"</span>);</span><br><span class="line">out:</span><br><span class="line">    printk(KERN_ERR <span class="string">"%s: Driver Initialisation failed\n"</span>, __FILE__);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>i2c_dev_init</code>函数先是调用了<code>register_chrdev</code>函数注册了一个字符设备驱动，并提供了一个file_operations。由此可见，是将通用驱动实现为字符设备驱动，并由其file_operations结构体的方法为应用层提供通用接口。然后调用<code>class_create</code>创建了一个类，但是可以看到并没有调用<code>device_create</code>在该类下创建设备，所以注意在这里并没有生成设备节点。最后调用<code>i2c_add_driver</code>注册了一个I2C从机设备驱动<code>i2cdev_driver</code>。<code>i2cdev_driver</code>定义如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">i2c_driver</span> <span class="title">i2cdev_driver</span> = &#123;</span></span><br><span class="line">    .driver = &#123;</span><br><span class="line">        .name   = <span class="string">"dev_driver"</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    .attach_adapter = i2cdev_attach_adapter,</span><br><span class="line">    .detach_adapter = i2cdev_detach_adapter,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>从上可以看到并没有对<code>id_table</code>进行赋值，从上文在I2C核心层分析可知，I2C总线是根据<code>id_table</code>进行匹配，所以这里并不会按照常规的Linux驱动模型进行match后probe，况且这个驱动里也没有probe方法。所以这到底是什么情况？别慌，虽然没有id_table和probe，但是它单独提供了两个方法<code>attach_adapter</code>和<code>detach_adapter</code>。这里先埋个伏笔，不做分析，到I2C总线驱动层分析后自然会柳暗花明。</p><h3 id="I2C总线驱动层-1"><a href="#I2C总线驱动层-1" class="headerlink" title="I2C总线驱动层"></a>I2C总线驱动层</h3><p>笔者使用的SoC是S5PV210，其控制器跟S3C2410基本一致，所以三星的驱动开发者并没有再去写一份S5PV210的主机适配器驱动，而是使用了S3C2410的主机适配器驱动，其位于<code>drivers/i2c/busses/i2c-s3c2410.c</code>中。</p><p>从<code>i2c_adap_s3c_init</code>函数开始分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">i2c_adap_s3c_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> platform_driver_register(&amp;s3c24xx_i2c_driver);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到其作为平台设备驱动而实现，注册了<code>s3c24xx_i2c_driver</code>驱动。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">platform_device_id</span> <span class="title">s3c24xx_driver_ids</span>[] = &#123;</span></span><br><span class="line">    &#123;</span><br><span class="line">        .name       = <span class="string">"s3c2410-i2c"</span>,</span><br><span class="line">        .driver_data    = TYPE_S3C2410,</span><br><span class="line">    &#125;, &#123;</span><br><span class="line">        .name       = <span class="string">"s3c2440-i2c"</span>,</span><br><span class="line">        .driver_data    = TYPE_S3C2440,</span><br><span class="line">    &#125;, &#123; &#125;,</span><br><span class="line">&#125;;</span><br><span class="line">MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">platform_driver</span> <span class="title">s3c24xx_i2c_driver</span> = &#123;</span></span><br><span class="line">    .probe      = s3c24xx_i2c_probe,</span><br><span class="line">    .<span class="built_in">remove</span>     = s3c24xx_i2c_remove,</span><br><span class="line">    .id_table   = s3c24xx_driver_ids,</span><br><span class="line">    .driver     = &#123;</span><br><span class="line">        .owner  = THIS_MODULE,</span><br><span class="line">        .name   = <span class="string">"s3c-i2c"</span>,</span><br><span class="line">        .pm = S3C24XX_DEV_PM_OPS,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>根据平台总线的原理，很容易得知在<code>arch/arm/mach-s5pv210/mach-x210.c</code>中对其驱动对应的设备进行了注册，其注册的设备定义位于<code>dev-i2c0.c</code>，这是I2C的资源文件。其定义的资源如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">resource</span> <span class="title">s3c_i2c_resource</span>[] = &#123;</span></span><br><span class="line">    [<span class="number">0</span>] = &#123;</span><br><span class="line">        .start = S3C_PA_IIC,</span><br><span class="line">        .<span class="built_in">end</span>   = S3C_PA_IIC + SZ_4K - <span class="number">1</span>,</span><br><span class="line">        .flags = IORESOURCE_MEM,</span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="number">1</span>] = &#123;</span><br><span class="line">        .start = IRQ_IIC,</span><br><span class="line">        .<span class="built_in">end</span>   = IRQ_IIC,</span><br><span class="line">        .flags = IORESOURCE_IRQ,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">platform_device</span> <span class="title">s3c_device_i2c0</span> = &#123;</span></span><br><span class="line">    .name         = <span class="string">"s3c2410-i2c"</span>,</span><br><span class="line">    .id       = <span class="number">0</span>,</span><br><span class="line">    .num_resources    = ARRAY_SIZE(s3c_i2c_resource),</span><br><span class="line">    .resource     = s3c_i2c_resource,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>由name可知，与<code>s3c24xx_i2c_driver</code>是匹配的。除此之外，还定义了平台数据<code>default_i2c_data0</code>和<code>default_i2c_data0</code>函数。其相关的调用还是在<code>arch/arm/mach-s5pv210/mach-x210.c</code>中进行的，在<code>mach-x210.c</code>中的<code>smdkc110_machine_init</code>函数中进行了如下调用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* i2c */</span></span><br><span class="line"><span class="comment">// 设置I2C平台数据       NULL表示设置默认的平台数据</span></span><br><span class="line">s3c_i2c0_set_platdata(<span class="literal">NULL</span>);</span><br><span class="line">s3c_i2c1_set_platdata(<span class="literal">NULL</span>);</span><br><span class="line">s3c_i2c2_set_platdata(<span class="literal">NULL</span>);</span><br></pre></td></tr></table></figure><p>现在进到<code>s3c_i2c0_set_platdata</code>函数进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">s3c2410_platform_i2c</span> <span class="title">default_i2c_data0</span> __<span class="title">initdata</span> = &#123;</span></span><br><span class="line">    .flags      = <span class="number">0</span>,</span><br><span class="line">    .slave_addr = <span class="number">0x10</span>,          <span class="comment">// I2C控制器作为从设备时使用的地址</span></span><br><span class="line">    .frequency  = <span class="number">400</span>*<span class="number">1000</span>,      <span class="comment">// 400kbps</span></span><br><span class="line">    .sda_delay  = S3C2410_IICLC_SDA_DELAY15 | S3C2410_IICLC_FILTER_ON,   <span class="comment">// 间隔时间</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> __init <span class="title">s3c_i2c0_set_platdata</span><span class="params">(struct s3c2410_platform_i2c *pd)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">s3c2410_platform_i2c</span> *<span class="title">npd</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!pd)   <span class="comment">// 参数为NULL则使用该函数上面定义的默认的平台数据</span></span><br><span class="line">        pd = &amp;default_i2c_data0;</span><br><span class="line"></span><br><span class="line">    npd = kmemdup(pd, <span class="keyword">sizeof</span>(struct s3c2410_platform_i2c), GFP_KERNEL);</span><br><span class="line">    <span class="keyword">if</span> (!npd)</span><br><span class="line">        printk(KERN_ERR <span class="string">"%s: no memory for platform data\n"</span>, __func__);</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!npd-&gt;cfg_gpio)</span><br><span class="line">        npd-&gt;cfg_gpio = s3c_i2c0_cfg_gpio;  <span class="comment">// GPIO初始化方法</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 设置为平台数据</span></span><br><span class="line">    s3c_device_i2c0.dev.platform_data = npd;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到传递NULL则使用了默认的平台数据, 将<code>s3c_i2c0_cfg_gpio</code>函数设置到了平台数据<code>cfg_gpio</code>方法中，最后将平台数据挂接到<code>s3c_device_i2c0</code>这个设备上。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">s3c_i2c0_cfg_gpio</span><span class="params">(struct platform_device *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    s3c_gpio_cfgpin(S5PV210_GPD1(<span class="number">0</span>), S3C_GPIO_SFN(<span class="number">2</span>));      <span class="comment">// 设置控制寄存器为I2C0_SDA模式</span></span><br><span class="line">    s3c_gpio_setpull(S5PV210_GPD1(<span class="number">0</span>), S3C_GPIO_PULL_NONE);     </span><br><span class="line">    s3c_gpio_cfgpin(S5PV210_GPD1(<span class="number">1</span>), S3C_GPIO_SFN(<span class="number">2</span>));      <span class="comment">// 设置控制寄存器为I2C0_SCL模式</span></span><br><span class="line">    s3c_gpio_setpull(S5PV210_GPD1(<span class="number">1</span>), S3C_GPIO_PULL_NONE);  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到<code>s3c_i2c0_cfg_gpio</code>函数只是对I2C控制器两根通信线的GPIO初始化。</p><p>接下去回到I2C总线驱动层<code>i2c-s3c2410.c</code>中, 进入到<code>s3c24xx_i2c_probe</code>函数进行分析。 probe函数的代码比较多，分段进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">s3c24xx_i2c</span> *<span class="title">i2c</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">s3c2410_platform_i2c</span> *<span class="title">pdata</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">resource</span> *<span class="title">res</span>;</span></span><br><span class="line"><span class="keyword">int</span> ret;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取I2C平台数据</span></span><br><span class="line">pdata = pdev-&gt;dev.platform_data;</span><br><span class="line"><span class="keyword">if</span> (!pdata) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"no platform data\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> -EINVAL;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">i2c = kzalloc(<span class="keyword">sizeof</span>(struct s3c24xx_i2c), GFP_KERNEL);</span><br><span class="line"><span class="keyword">if</span> (!i2c) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"no memory for state\n"</span>);</span><br><span class="line">    <span class="keyword">return</span> -ENOMEM;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">strlcpy(i2c-&gt;adap.name, <span class="string">"s3c2410-i2c"</span>, <span class="keyword">sizeof</span>(i2c-&gt;adap.name));</span><br><span class="line">i2c-&gt;adap.owner   = THIS_MODULE;</span><br><span class="line">i2c-&gt;adap.algo    = &amp;s3c24xx_i2c_algorithm;      <span class="comment">// I2C主机控制器的操作方法</span></span><br><span class="line">i2c-&gt;adap.retries = <span class="number">2</span>;</span><br><span class="line">i2c-&gt;adap<span class="class">.<span class="keyword">class</span>   = <span class="title">I2C_CLASS_HWMON</span> | <span class="title">I2C_CLASS_SPD</span>;</span></span><br><span class="line">i2c-&gt;tx_setup     = <span class="number">50</span>;</span><br></pre></td></tr></table></figure><p>三星采用<code>struct s3c24xx_i2c</code>结构体来对SoC的控制器进行抽象，该结构体继承于<code>struct i2c_adapter</code>。该段代码先是从device中获取了平台数据，该平台数据即是上文调用<code>s3c_i2c0_set_platdata</code>函数时设置的。然后对<code>i2c-&gt;adap</code>进行了相关赋值，关键部分是<code>i2c-&gt;adap.algo = &amp;s3c24xx_i2c_algorithm; </code>，<code>adap.algo</code>表示I2C主机控制器的操作方法，将该SoC的操作方法挂接到了适配器上。<code>s3c24xx_i2c_algorithm</code>定义了两个操作方法，主要是<code>master_xfer</code>方法，用来发送消息。代码如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">i2c_algorithm</span> <span class="title">s3c24xx_i2c_algorithm</span> = &#123;</span></span><br><span class="line">    .master_xfer        = s3c24xx_i2c_xfer,</span><br><span class="line">    .functionality      = s3c24xx_i2c_func,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><code>s3c24xx_i2c_xfer</code>涉及到对具体控制器的操作，不进行展开，但是注意的是其内部调用的是<code>s3c24xx_i2c_doxfer</code>，在<code>s3c24xx_i2c_doxfer</code>函数内部发送完数据后，调用<code>wait_event_timeout</code>函数来进行睡眠等待从机响应。因此可知内核中I2C的等待从机的ACK信号是通过中断实现的，即主机发送完数据后进入睡眠等待从机，从机响应后通过中断通知主机后唤醒。</p><p>probe函数接着做了获取时钟和使能时钟，相关代码如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取时钟</span></span><br><span class="line">    i2c-&gt;clk = clk_get(&amp;pdev-&gt;dev, <span class="string">"i2c"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(i2c-&gt;clk)) &#123;</span><br><span class="line">        dev_err(&amp;pdev-&gt;dev, <span class="string">"cannot get clock\n"</span>);</span><br><span class="line">        ret = -ENOENT;</span><br><span class="line">        <span class="keyword">goto</span> err_noclk;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    dev_dbg(&amp;pdev-&gt;dev, <span class="string">"clock source %p\n"</span>, i2c-&gt;clk);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使能时钟</span></span><br><span class="line">    clk_enable(i2c-&gt;clk);</span><br></pre></td></tr></table></figure><p>紧接着对具体IO和IRQ进行操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取I2C平台资源（IO内存地址、IRQ）</span></span><br><span class="line">res = platform_get_resource(pdev, IORESOURCE_MEM, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (res == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"cannot find IO resource\n"</span>);</span><br><span class="line">    ret = -ENOENT;</span><br><span class="line">    <span class="keyword">goto</span> err_clk;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">i2c-&gt;ioarea = request_mem_region(res-&gt;start, resource_size(res),</span><br><span class="line">                 pdev-&gt;name);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (i2c-&gt;ioarea == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"cannot request IO\n"</span>);</span><br><span class="line">    ret = -ENXIO;</span><br><span class="line">    <span class="keyword">goto</span> err_clk;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将物理地址映射为虚拟地址</span></span><br><span class="line">i2c-&gt;regs = ioremap(res-&gt;start, resource_size(res));</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (i2c-&gt;regs == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"cannot map IO\n"</span>);</span><br><span class="line">    ret = -ENXIO;</span><br><span class="line">    <span class="keyword">goto</span> err_ioarea;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">dev_dbg(&amp;pdev-&gt;dev, <span class="string">"registers %p (%p, %p)\n"</span>,</span><br><span class="line">    i2c-&gt;regs, i2c-&gt;ioarea, res);</span><br><span class="line"></span><br><span class="line"><span class="comment">/* setup info block for the i2c core */</span></span><br><span class="line"></span><br><span class="line">i2c-&gt;adap.algo_data = i2c;</span><br><span class="line">i2c-&gt;adap.dev.parent = &amp;pdev-&gt;dev;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* initialise the i2c controller */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 初始化I2C控制器</span></span><br><span class="line">ret = s3c24xx_i2c_init(i2c);    </span><br><span class="line"><span class="keyword">if</span> (ret != <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">goto</span> err_iomap;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取IRQ资源</span></span><br><span class="line">i2c-&gt;irq = ret = platform_get_irq(pdev, <span class="number">0</span>);   </span><br><span class="line"><span class="keyword">if</span> (ret &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"cannot find IRQ\n"</span>);</span><br><span class="line">    <span class="keyword">goto</span> err_iomap;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 申请IRQ (裸机一般使用查询法来判断从机的响应，而内核一般采用中断方式等待从机响应)</span></span><br><span class="line">ret = request_irq(i2c-&gt;irq, s3c24xx_i2c_irq, IRQF_DISABLED,</span><br><span class="line">          dev_name(&amp;pdev-&gt;dev), i2c);</span><br></pre></td></tr></table></figure><p>把关注点放在初始化I2C控制器的<code>s3c24xx_i2c_init</code>函数和申请IRQ上。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">s3c24xx_i2c_init</span><span class="params">(struct s3c24xx_i2c *i2c)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">s3c2410_platform_i2c</span> *<span class="title">pdata</span>;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> freq;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* get the plafrom data */</span></span><br><span class="line"></span><br><span class="line">    pdata = i2c-&gt;dev-&gt;platform_data;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* inititalise the gpio */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (pdata-&gt;cfg_gpio)</span><br><span class="line">        pdata-&gt;cfg_gpio(to_platform_device(i2c-&gt;dev));      <span class="comment">// 设置I2C对应的管脚</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* write slave address */</span></span><br><span class="line">    <span class="comment">// 设置I2C控制器作为从设备时的地址</span></span><br><span class="line">    writeb(pdata-&gt;slave_addr, i2c-&gt;regs + S3C2410_IICADD);  </span><br><span class="line"></span><br><span class="line">    dev_dbg(i2c-&gt;dev, <span class="string">"slave address 0x%02x\n"</span>, pdata-&gt;slave_addr);</span><br><span class="line"></span><br><span class="line">    writel(iicon, i2c-&gt;regs + S3C2410_IICCON);        <span class="comment">// 使能 Tx/Rx Interrupt 和 ACK信号</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* we need to work out the divisors for the clock... */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 配置I2C的时钟频率</span></span><br><span class="line">    <span class="keyword">if</span> (s3c24xx_i2c_clockrate(i2c, &amp;freq) != <span class="number">0</span>) &#123;</span><br><span class="line">        writel(<span class="number">0</span>, i2c-&gt;regs + S3C2410_IICCON);</span><br><span class="line">        dev_err(i2c-&gt;dev, <span class="string">"cannot meet bus frequency required\n"</span>);</span><br><span class="line">        <span class="keyword">return</span> -EINVAL;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* todo - check that the i2c lines aren't being dragged anywhere */</span></span><br><span class="line"></span><br><span class="line">    dev_dbg(i2c-&gt;dev, <span class="string">"bus frequency set to %d KHz\n"</span>, freq);</span><br><span class="line">    dev_dbg(i2c-&gt;dev, <span class="string">"S3C2410_IICCON=0x%02lx\n"</span>, iicon);</span><br><span class="line"></span><br><span class="line">    dev_dbg(i2c-&gt;dev, <span class="string">"S3C2440_IICLC=%08x\n"</span>, pdata-&gt;sda_delay);</span><br><span class="line">    writel(pdata-&gt;sda_delay, i2c-&gt;regs + S3C2440_IICLC);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到设置I2C对应的管脚是调用平台数据中的<code>cfg_gpio</code>，其实看到这里如果还有印象的话就能反应出来这是在调用<code>s3c_i2c0_set_platdata</code>中设置的。该函数还设置了I2C控制器的从地址，该地址用来在控制器作为从地址时使用，但是这种情况的出现微乎其微。除此之外使能Tx&#x2F;Rx Interrupt和ACK信号，配置了I2C的时钟频率。</p><p>注意从前一段分析中得知，内核中I2C采用中断方式等待从机响应，所以probe函数这一段代码中申请了IRQ并绑定了中断处理函数<code>s3c24xx_i2c_irq</code>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">irqreturn_t</span> <span class="title">s3c24xx_i2c_irq</span><span class="params">(<span class="keyword">int</span> irqno, <span class="keyword">void</span> *dev_id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">s3c24xx_i2c</span> *<span class="title">i2c</span> = <span class="title">dev_id</span>;</span></span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> status;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> tmp;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取I2CSTAT寄存器的值</span></span><br><span class="line">    status = readl(i2c-&gt;regs + S3C2410_IICSTAT);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (status &amp; S3C2410_IICSTAT_ARBITR) &#123;   <span class="comment">// I2C总线仲裁失败</span></span><br><span class="line">        <span class="comment">/* deal with arbitration loss */</span></span><br><span class="line">        dev_err(i2c-&gt;dev, <span class="string">"deal with arbitration loss\n"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (i2c-&gt;state == STATE_IDLE) &#123;</span><br><span class="line">        dev_dbg(i2c-&gt;dev, <span class="string">"IRQ: error i2c-&gt;state == IDLE\n"</span>);</span><br><span class="line"></span><br><span class="line">        tmp = readl(i2c-&gt;regs + S3C2410_IICCON);</span><br><span class="line">        tmp &amp;= ~S3C2410_IICCON_IRQPEND;</span><br><span class="line">        writel(tmp, i2c-&gt;regs +  S3C2410_IICCON);</span><br><span class="line">        <span class="keyword">goto</span> out;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* pretty much this leaves us with the fact that we've</span></span><br><span class="line"><span class="comment">     * transmitted or received whatever byte we last sent */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 处理I2C的收发数据</span></span><br><span class="line">    i2c_s3c_irq_nextbyte(i2c, status);</span><br><span class="line"></span><br><span class="line"> out:</span><br><span class="line">    <span class="keyword">return</span> IRQ_HANDLED;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>具体也不展开分析了，但是要注意的是有这么一条线：该中断处理函数调用了<code>i2c_s3c_irq_nextbyte</code>，然后内部调用了<code>s3c24xx_i2c_stop</code>，再内部调用了<code>s3c24xx_i2c_master_complete</code>，最后再内部执行了一个关键代码<code>wake_up(&amp;i2c-&gt;wait);</code>，这就是通过中断方式唤醒之前在发送数据时进行的睡眠等待。</p><p>回到probe函数，最后分析重头戏。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ret = i2c_add_numbered_adapter(&amp;i2c-&gt;adap);</span><br><span class="line"><span class="keyword">if</span> (ret &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    dev_err(&amp;pdev-&gt;dev, <span class="string">"failed to add bus to i2c core\n"</span>);</span><br><span class="line">    <span class="keyword">goto</span> err_cpufreq;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该代码将I2C适配器注册到了内核中。<code>i2c_add_numbered_adapter</code>函数由核心层提供，其定义位于I2C核心层<code>drivers/i2c/i2c-core.c</code>中，用来注册I2C适配器。其实在内核中提供了两个adapter注册接口,分别为<code>i2c_add_adapter</code>和<code>i2c_add_numbered_adapter</code>由于在系统中可能存在多个adapter, 所以将每一条I2C总线(控制器)对应一个编号，这个总线号（可以称这个编号为总线号码）与PCI中的总线号不同。它和硬件无关, 只是软件上便于区分而已。对于<code>i2c_add_adapter</code>而言, 它使用的是动态总线号, 即由系统给其分配一个总线号, 而<code>i2c_add_numbered_adapter</code>则是自己指定总线号, 如果这个总线号非法或者是被占用, 就会注册失败。不管哪个注册接口，其核心都是调用<code>i2c_register_adapter</code>函数来进行真正的注册。取出<code>i2c_register_adapter</code>函数的关键部分进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">res = device_register(&amp;adap-&gt;dev);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (adap-&gt;nr &lt; __i2c_first_dynamic_bus_num)</span><br><span class="line">    i2c_scan_static_board_info(adap);</span><br><span class="line"></span><br><span class="line">dummy = bus_for_each_drv(&amp;i2c_bus_type, <span class="literal">NULL</span>, adap,</span><br><span class="line">             __process_new_adapter);</span><br></pre></td></tr></table></figure><p><code>device_register(&amp;adap-&gt;dev);</code>表示主机适配器adapter的注册。</p><p><code>i2c_scan_static_board_info(adap);</code>内部先遍历<code>__i2c_board_list</code>取出板卡信息（描述的是板子上的I2C外设的信息，即I2C从机的信息），该链表的生成是在<code>arch/arm/mach-s5pv210/mach-x210.c</code>中进行的，在<code>mach-x210.c</code>中的<code>smdkc110_machine_init</code>函数中进行了除之前分析的调用<code>s3c_i2c0_set_platdata</code>外，还调用了<code>i2c_register_board_info</code>对板卡信息进行了注册。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> __init</span><br><span class="line">i2c_register_board_info(<span class="keyword">int</span> busnum,</span><br><span class="line">    struct i2c_board_info <span class="keyword">const</span> *info, <span class="keyword">unsigned</span> len)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> status;</span><br><span class="line"></span><br><span class="line">    down_write(&amp;__i2c_board_lock);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* dynamic bus numbers will be assigned after the last static one */</span></span><br><span class="line">    <span class="comment">// __i2c_first_dynamic_bus_num为全局未显式初始化变量，所以第一次进到这个函数，值为0</span></span><br><span class="line">    <span class="keyword">if</span> (busnum &gt;= __i2c_first_dynamic_bus_num)</span><br><span class="line">        __i2c_first_dynamic_bus_num = busnum + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (status = <span class="number">0</span>; len; len--, info++) &#123;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">i2c_devinfo</span>  *<span class="title">devinfo</span>;</span></span><br><span class="line"></span><br><span class="line">        devinfo = kzalloc(<span class="keyword">sizeof</span>(*devinfo), GFP_KERNEL);</span><br><span class="line">        <span class="keyword">if</span> (!devinfo) &#123;</span><br><span class="line">            pr_debug(<span class="string">"i2c-core: can't register boardinfo!\n"</span>);</span><br><span class="line">            status = -ENOMEM;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        devinfo-&gt;busnum = busnum;</span><br><span class="line">        devinfo-&gt;board_info = *info;</span><br><span class="line">        list_add_tail(&amp;devinfo-&gt;<span class="built_in">list</span>, &amp;__i2c_board_list);    <span class="comment">// 将board_info用链表管理起来 </span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    up_write(&amp;__i2c_board_lock);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> status;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>板卡信息的描述，主要对其设备名和从地址进行赋值，示例如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> I2C_BOARD_INFO(dev_type, dev_addr) \</span></span><br><span class="line">    .type = dev_type, .addr = (dev_addr)</span><br><span class="line"></span><br><span class="line">#ifdef CONFIG_TOUCHSCREEN_GSLX680</span><br><span class="line">    &#123;</span><br><span class="line">        I2C_BOARD_INFO(<span class="string">"gslX680"</span>, <span class="number">0x40</span>),  <span class="comment">// 主要对其设备名和从地址进行赋值</span></span><br><span class="line">    &#125;,</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p>然后在<code>i2c_scan_static_board_info</code>内部利用板卡信息作为原料调用<code>i2c_new_device</code>来创建了client，表示从机设备，并将adapter挂接到了client结构体内部的指针上。<code>i2c_scan_static_board_info</code>代码如下。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">i2c_scan_static_board_info</span><span class="params">(struct i2c_adapter *adapter)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_devinfo</span>  *<span class="title">devinfo</span>;</span></span><br><span class="line"></span><br><span class="line">    down_read(&amp;__i2c_board_lock);</span><br><span class="line">    <span class="comment">// __i2c_board_list在调用i2c_register_board_info时链接起来的</span></span><br><span class="line">    list_for_each_entry(devinfo, &amp;__i2c_board_list, <span class="built_in">list</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (devinfo-&gt;busnum == adapter-&gt;nr</span><br><span class="line">                &amp;&amp; !i2c_new_device(adapter,</span><br><span class="line">                        &amp;devinfo-&gt;board_info))</span><br><span class="line">            dev_err(&amp;adapter-&gt;dev,</span><br><span class="line">                <span class="string">"Can't create device at 0x%02x\n"</span>,</span><br><span class="line">                devinfo-&gt;board_info.addr);</span><br><span class="line">    &#125;</span><br><span class="line">    up_read(&amp;__i2c_board_lock);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>创建完client后，回到<code>i2c_register_adapter</code>函数，最后执行了<code>dummy = bus_for_each_drv(&amp;i2c_bus_type, NULL, adap, __process_new_adapter);</code>，<strong>该函数是遍历在I2C总线上已经注册的driver，通过回调<code>__process_new_adapter</code>函数的方式，遍历到i2c-dev这个通用驱动后就会用其<code>i2cdev_attach_adapter</code>方法来挂接到在i2c-dev中注册的字符设备驱动，并使用这个字符设备驱动的主设备号和adapter中的总线号（作为次设备号）来创建名为i2c-x的设备节点，应用层访问这个设备节点后即可调用在i2c-dev中注册的file_operations中的操作方法，从操作方法源码知，最终读写调用的是adapter中的读写方法（即在本平台中为i2c-s3c2410.c中定义的方法）</strong>。下面对其进行验证。</p><p><code>__process_new_adapter</code>展开如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">i2c_do_add_adapter</span><span class="params">(struct i2c_driver *driver,</span></span></span><br><span class="line"><span class="function"><span class="params">                  struct i2c_adapter *adap)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">/* Detect supported devices on that bus, and instantiate them */</span></span><br><span class="line">    i2c_detect(adap, driver);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Let legacy drivers scan this bus for matching devices */</span></span><br><span class="line">    <span class="keyword">if</span> (driver-&gt;attach_adapter) &#123;</span><br><span class="line">        <span class="comment">/* We ignore the return code; if it fails, too bad */</span></span><br><span class="line">        driver-&gt;attach_adapter(adap);   <span class="comment">// 调用i2c-dev中的i2cdev_attach_adapter方法</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">int</span> __process_new_adapter(struct device_driver *d, <span class="keyword">void</span> *data)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">return</span> i2c_do_add_adapter(to_i2c_driver(d), data);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到<code>driver-&gt;attach_adapter(adap);</code>，的确是调用I2C总线下的驱动中的<code>attach_adapter</code>方法，到了这里在I2C设备驱动层埋下的悬念终于要水落石出了（不容易啊啊啊啊啊啊），穿越回到I2C设备驱动层进行分析，进入<code>drivers/i2c/i2c-dev.c</code>分析<code>i2cdev_attach_adapter</code>方法。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">i2cdev_attach_adapter</span><span class="params">(struct i2c_adapter *adap)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_dev</span> *<span class="title">i2c_dev</span>;</span></span><br><span class="line">    <span class="keyword">int</span> res;</span><br><span class="line"></span><br><span class="line">    i2c_dev = get_free_i2c_dev(adap);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(i2c_dev))</span><br><span class="line">        <span class="keyword">return</span> PTR_ERR(i2c_dev);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* register this i2c device with the driver core */</span></span><br><span class="line">    <span class="comment">/* 使用主设备号和adapter中的总线号（作为次设备号）来创建名为i2c-x的设备节点 */</span></span><br><span class="line">    i2c_dev-&gt;dev = device_create(i2c_dev_class, &amp;adap-&gt;dev,</span><br><span class="line">                     MKDEV(I2C_MAJOR, adap-&gt;nr), <span class="literal">NULL</span>,</span><br><span class="line">                     <span class="string">"i2c-%d"</span>, adap-&gt;nr);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(i2c_dev-&gt;dev)) &#123;</span><br><span class="line">        res = PTR_ERR(i2c_dev-&gt;dev);</span><br><span class="line">        <span class="keyword">goto</span> error;</span><br><span class="line">    &#125;</span><br><span class="line">    res = device_create_file(i2c_dev-&gt;dev, &amp;dev_attr_name);</span><br><span class="line">    <span class="keyword">if</span> (res)</span><br><span class="line">        <span class="keyword">goto</span> error_destroy;</span><br><span class="line"></span><br><span class="line">    pr_debug(<span class="string">"i2c-dev: adapter [%s] registered as minor %d\n"</span>,</span><br><span class="line">         adap-&gt;name, adap-&gt;nr);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">error_destroy:</span><br><span class="line">    device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap-&gt;nr));</span><br><span class="line">error:</span><br><span class="line">    return_i2c_dev(i2c_dev);</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>i2c_dev-&gt;dev = device_create(i2c_dev_class, &amp;adap-&gt;dev, MKDEV(I2C_MAJOR, adap-&gt;nr), NULL, &quot;i2c-%d&quot;, adap-&gt;nr);</code>使用主设备号和adapter中的总线号（作为次设备号）来创建名为i2c-x的设备节点。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">ssize_t</span> <span class="title">i2cdev_write</span><span class="params">(struct file *file, <span class="keyword">const</span> <span class="keyword">char</span> __user *buf,</span></span></span><br><span class="line"><span class="function"><span class="params">        <span class="keyword">size_t</span> count, <span class="keyword">loff_t</span> *offset)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret;</span><br><span class="line">    <span class="keyword">char</span> *tmp;</span><br><span class="line">    <span class="comment">// 取出i2c_client</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_client</span> *<span class="title">client</span> = <span class="title">file</span>-&gt;<span class="title">private_data</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (count &gt; <span class="number">8192</span>)</span><br><span class="line">        count = <span class="number">8192</span>;</span><br><span class="line"></span><br><span class="line">    tmp = kmalloc(count, GFP_KERNEL);</span><br><span class="line">    <span class="keyword">if</span> (tmp == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> -ENOMEM;</span><br><span class="line">    <span class="comment">// 拷贝用户数据到内核空间</span></span><br><span class="line">    <span class="keyword">if</span> (copy_from_user(tmp, buf, count)) &#123;</span><br><span class="line">        kfree(tmp);</span><br><span class="line">        <span class="keyword">return</span> -EFAULT;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    pr_debug(<span class="string">"i2c-dev: i2c-%d writing %zu bytes.\n"</span>,</span><br><span class="line">        iminor(file-&gt;f_path.dentry-&gt;d_inode), count);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送I2C数据</span></span><br><span class="line">    ret = i2c_master_send(client, tmp, count);</span><br><span class="line">    kfree(tmp);</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以write函数为例，可以看到写数据通过<code>ret = i2c_master_send(client, tmp, count);</code>完成的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">i2c_master_send</span><span class="params">(struct i2c_client *client, <span class="keyword">const</span> <span class="keyword">char</span> *buf, <span class="keyword">int</span> count)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret;</span><br><span class="line">    <span class="comment">// 获取I2C适配器</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_adapter</span> *<span class="title">adap</span> = <span class="title">client</span>-&gt;<span class="title">adapter</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_msg</span> <span class="title">msg</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 封装I2C数据包</span></span><br><span class="line">    msg.addr = client-&gt;addr;</span><br><span class="line">    msg.flags = client-&gt;flags &amp; I2C_M_TEN;   <span class="comment">// 发送标志位</span></span><br><span class="line">    msg.len = count;</span><br><span class="line">    msg.buf = (<span class="keyword">char</span> *)buf;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 发送I2C数据包</span></span><br><span class="line">    ret = i2c_transfer(adap, &amp;msg, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* If everything went ok (i.e. 1 msg transmitted), return #bytes</span></span><br><span class="line"><span class="comment">       transmitted, else error code. */</span></span><br><span class="line">    <span class="keyword">return</span> (ret == <span class="number">1</span>) ? count : ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，经过I2C数据包的封装后，真正的最终写数据通过<code>ret = i2c_transfer(adap, &amp;msg, 1);</code>完成的。进入到<code>i2c_transfer</code>函数，截取关键部分。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (ret = <span class="number">0</span>, <span class="keyword">try</span> = <span class="number">0</span>; <span class="keyword">try</span> &lt;= adap-&gt;retries; <span class="keyword">try</span>++) &#123;</span><br><span class="line">    <span class="comment">// 调用具体的SoC的I2C总线驱动的发送方法</span></span><br><span class="line">    ret = adap-&gt;algo-&gt;master_xfer(adap, msgs, num);</span><br><span class="line">    <span class="keyword">if</span> (ret != -EAGAIN)</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">if</span> (time_after(jiffies, orig_jiffies + adap-&gt;timeout))</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>山回路转不见君，雪上空留马行处。</p></blockquote><p><code>adap-&gt;algo-&gt;master_xfer(adap, msgs, num);</code>终于回到了原点见到了I2C总线驱动层中定义的操作方法。</p><p>可以看到过程的确如上文所说，表现为<strong>从I2C总线驱动层自底向上后又由自顶向下的调用流程</strong>，简直一跃千里后又倾泻而下。</p><h2 id="I2C特定设备驱动分析"><a href="#I2C特定设备驱动分析" class="headerlink" title="I2C特定设备驱动分析"></a>I2C特定设备驱动分析</h2><p>笔者以S5PV210的E2PROM驱动为例讲解, 源码见<a href="https://github.com/IfanTsai/LinuxDriver/blob/master/i2c/at24c02.c" target="_blank" rel="external nofollow noopener noreferrer">github链接</a>。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">e2prom_device</span> &#123;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_client</span> *<span class="title">at24c02_client</span>;</span>   <span class="comment">/* I2C client(从设备) */</span></span><br><span class="line">    <span class="comment">/* class和device用来自动创建设备节点 */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">class</span>      *<span class="title">at24c02_class</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">device</span>     *<span class="title">at24c02_device</span>;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">e2prom_device</span> *<span class="title">e2prom_dev</span>;</span></span><br></pre></td></tr></table></figure><p>封装一个<code>e2prom_device</code>结构体表示对E2PROM的抽象，其中包含I2C client（用来表示I2C从设备）以及class和device（这两者单纯是用来自动创建设备节点的）。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">i2c_device_id</span> <span class="title">e2prom_table</span>[] = &#123;</span></span><br><span class="line">    [<span class="number">0</span>] = &#123;</span><br><span class="line">        .name         = <span class="string">"24c02"</span>,</span><br><span class="line">        .driver_data  = <span class="number">0</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    [<span class="number">1</span>] = &#123;</span><br><span class="line">        .name         = <span class="string">"24c08"</span>,</span><br><span class="line">        .driver_data  = <span class="number">0</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* I2C设备驱动 */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">i2c_driver</span> <span class="title">e2prom_driver</span> = &#123;</span></span><br><span class="line">    .probe     =  e2prom_probe,</span><br><span class="line">    .<span class="built_in">remove</span>    =  e2prom_remove,</span><br><span class="line">    .id_table  =  e2prom_table,</span><br><span class="line">    .driver    = &#123;</span><br><span class="line">        .name = <span class="string">"e2prom"</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">e2prom_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> i2c_add_driver(&amp;e2prom_driver);   <span class="comment">/* 注册I2C设备驱动 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先是调用<code>i2c_add_driver</code>注册I2C设备驱动。根据上文在I2C核心层的源码分析可知，会通过在核心层中注册的<code>i2c_bus_type</code>下的<code>i2c_device_match</code>函数来匹配设备与驱动，一旦匹配上则会调用其<code>i2c_device_probe</code>函数，而<code>i2c_device_probe</code>函数又会调用i2c_driver的probe函数。注意如上文分析所知，client生成的原料为board_info，所以要使这个驱动成功匹配，需要在<code>arch/arm/mach-s5pv210/mach-x210.c</code>中使用<code>i2c_register_board_info</code>来注册board_info。接下去直奔prob函数进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">file_operations</span> <span class="title">e2prom_fops</span> = &#123;</span></span><br><span class="line">    .owner = THIS_MODULE,</span><br><span class="line">    .<span class="built_in">open</span>  = e2prom_open,</span><br><span class="line">    .<span class="built_in">write</span> = e2prom_write,</span><br><span class="line">    .<span class="built_in">read</span> =  e2prom_read,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">e2prom_probe</span><span class="params">(struct i2c_client *client, <span class="keyword">const</span> struct i2c_device_id *id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret;</span><br><span class="line"></span><br><span class="line">    printk(KERN_INFO <span class="string">"e2prom probe!\n"</span>);</span><br><span class="line">    e2prom_dev = kmalloc(<span class="keyword">sizeof</span>(struct e2prom_device), GFP_KERNEL);</span><br><span class="line">    <span class="keyword">if</span> (!e2prom_dev) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"malloc failed!\n"</span>);</span><br><span class="line">        <span class="keyword">return</span> -ENOMEM;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    e2prom_dev-&gt;at24c02_client = client;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 注册为字符设备驱动 */</span></span><br><span class="line">    ret = register_chrdev(E2PROM_MAJOR, <span class="string">"e2prom_module"</span>, &amp;e2prom_fops);</span><br><span class="line">    <span class="keyword">if</span> (ret &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"malloc failed\n"</span>);</span><br><span class="line">        ret = -ENOMEM;</span><br><span class="line">        <span class="keyword">goto</span> err0;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 创建类  */</span></span><br><span class="line">    e2prom_dev-&gt;at24c02_class = class_create(THIS_MODULE, <span class="string">"e2prom_class"</span>);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(e2prom_dev-&gt;at24c02_class)) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"class create failed!\n"</span>);</span><br><span class="line">        ret = PTR_ERR(e2prom_dev-&gt;at24c02_class);</span><br><span class="line">        <span class="keyword">goto</span> err1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 在类下创建设备 */</span></span><br><span class="line">    e2prom_dev-&gt;at24c02_device = device_create(e2prom_dev-&gt;at24c02_class, <span class="literal">NULL</span>, MKDEV(E2PROM_MAJOR, <span class="number">0</span>), <span class="literal">NULL</span>, <span class="string">"at24c08"</span>);</span><br><span class="line">    <span class="keyword">if</span> (IS_ERR(e2prom_dev-&gt;at24c02_device)) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"class create failed!\n"</span>);</span><br><span class="line">        ret = PTR_ERR(e2prom_dev-&gt;at24c02_device);</span><br><span class="line">        <span class="keyword">goto</span> err1;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">err1:</span><br><span class="line">    unregister_chrdev(E2PROM_MAJOR, <span class="string">"e2prom_module"</span>);</span><br><span class="line">err0:</span><br><span class="line">    kfree(e2prom_dev);</span><br><span class="line">    ret</span><br></pre></td></tr></table></figure><p>在probe函数中调用<code>register_chrdev</code>函数来将E2PROM驱动注册为了字符设备驱动，并绑定了fops。然后调用<code>class_create</code>和<code>device_create</code>自动生成设备节点。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">e2prom_open</span><span class="params">(struct inode *inode, struct file *file)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>open方法为空，以write方法为例讲解具体的操作，read方法类似。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">ssize_t</span> <span class="title">e2prom_write</span><span class="params">(struct file *file, <span class="keyword">const</span> <span class="keyword">char</span> __user *buf,</span></span></span><br><span class="line"><span class="function"><span class="params">        <span class="keyword">size_t</span> <span class="built_in">size</span>, <span class="keyword">loff_t</span> *offset)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">char</span> *tmp;</span><br><span class="line">    tmp = kmalloc(<span class="built_in">size</span>, GFP_KERNEL);</span><br><span class="line">    <span class="keyword">if</span> (tmp == <span class="literal">NULL</span>) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"mallo failed!\n"</span>);</span><br><span class="line">        <span class="keyword">return</span> -ENOMEM;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 将用户空间数据拷贝到内核空间 */</span></span><br><span class="line">    ret = copy_from_user(tmp, buf, <span class="built_in">size</span>);</span><br><span class="line">    <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">        printk(<span class="string">"copy data faile!\n"</span>);</span><br><span class="line">        <span class="keyword">goto</span> err0;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* I2C write */</span></span><br><span class="line">    ret = i2c_write_byte(tmp, <span class="built_in">size</span>);</span><br><span class="line">    <span class="keyword">if</span> (ret) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"wrtie byte failed!\n"</span>);</span><br><span class="line">        <span class="keyword">goto</span> err0;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    kfree(tmp);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">size</span>;</span><br><span class="line"></span><br><span class="line">err0:</span><br><span class="line">    kfree(tmp);</span><br><span class="line">    <span class="keyword">return</span> -EINVAL;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到真正的操作I2C在<code>i2c_write_byte</code>函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">i2c_write_byte</span><span class="params">(<span class="keyword">char</span> *buf, <span class="keyword">int</span> count)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> ret = <span class="number">0</span>;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">i2c_msg</span> <span class="title">msg</span>;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 封装I2C数据包 */</span></span><br><span class="line">    msg.addr   = e2prom_dev-&gt;at24c02_client-&gt;addr; <span class="comment">/* I2C从设备地址 */</span></span><br><span class="line">    msg.flags  = <span class="number">0</span>;                                <span class="comment">/* write flag */</span></span><br><span class="line">    msg.len    = count;                            <span class="comment">/* 数据长度 */</span></span><br><span class="line">    msg.buf    = buf;                              <span class="comment">/* 写入的数据 */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* 调用I2C核心层提供的传输函数，其本质还是调用的I2C总线驱动(主机控制器驱动)层下实现的algo-&gt;master_xfe方法 */</span></span><br><span class="line">    ret = i2c_transfer(e2prom_dev-&gt;at24c02_client-&gt;adapter, &amp;msg, <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (ret &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        printk(KERN_ERR <span class="string">"i2c transfer failed!\n"</span>);</span><br><span class="line">        <span class="keyword">return</span> -EINVAL;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ret;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到是调用在I2C核心层提供的传输函数，其本质还是在传输函数内部调用了跟具体SoC相关的I2C主机控制器操作方法中的传输方法。该函数接口需要提供一个<code>i2c_msg</code>，所以对其进行了创建并填充，注意<code>msg.flags  = 0;</code>中0表示写，1表示读。</p><p>终了，撒花！！！✿✿✿ ～</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;I2C是广泛应用于计算机中的串行总线，用于处理器和其外设之间的通信。&lt;/p&gt;
&lt;h2 id=&quot;I2C硬件基本概念&quot;&gt;&lt;a href=&quot;#I2C硬件基本概念&quot; class=&quot;headerlink&quot; title=&quot;I2C硬件基本概念&quot;&gt;&lt;/a&gt;I2C硬件基本概念&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I2C总线由两根传递数据的双向信号线与一根地线组成，半双工、主从方式通信。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serial Clock Line (SCL)&lt;/li&gt;
&lt;li&gt;Serial Data Address (SDA)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每个设备都有一个唯一设备地址，一次传输8bit，高位在前，低位在后。 &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;一次完整的I2C通信需要经历一个完整的时序，I2C总线通信完整时序如下图。一般在驱动中无需关心具体时序，只需操作SoC中的I2C控制器即可，只有在裸机下需要用GPIO模拟I2C通信时才需用到，所以笔者在本文不阐述I2C时序（其实就是懒 O__O “…）。&lt;/p&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="嵌入式" scheme="https://www.caiyifan.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
      <category term="linux驱动" scheme="https://www.caiyifan.cn/tags/linux%E9%A9%B1%E5%8A%A8/"/>
    
      <category term="I2C子系统" scheme="https://www.caiyifan.cn/tags/I2C%E5%AD%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>用Python写Shell脚本</title>
    <link href="https://www.caiyifan.cn/p/49b985f8.html"/>
    <id>https://www.caiyifan.cn/p/49b985f8.html</id>
    <published>2019-06-12T03:53:20.000Z</published>
    <updated>2024-05-01T10:34:38.737Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Python调用Shell命令"><a href="#Python调用Shell命令" class="headerlink" title="Python调用Shell命令"></a>Python调用Shell命令</h2><h3 id="os-system"><a href="#os-system" class="headerlink" title="os.system"></a>os.system</h3><p><code>os.system</code>函数与系统编程中的exec族函数调用一致，创建出子进程后代码段由外部程序替换，<strong>不会返回外部程序运行结果</strong>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">os.system(<span class="string">'ls -l'</span>)</span><br></pre></td></tr></table></figure><h3 id="os-popen"><a href="#os-popen" class="headerlink" title="os.popen"></a>os.popen</h3><p><code>os.popen</code>返回的是一个文件对象，它将外部程序运行结果保存在文件对象中，当调用其<code>read</code>方法时就会得到运行结果。<strong>该方法可以得到外部程序的运行结果</strong>。</p><a id="more"></a><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">os.popen(<span class="string">'ls -l'</span>).read()</span><br><span class="line"></span><br><span class="line">res = os.popen(<span class="string">'ls'</span>).read()</span><br><span class="line"><span class="keyword">if</span> <span class="string">'a.c'</span> <span class="keyword">in</span> res:</span><br><span class="line">    print(<span class="string">'a.c in this'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'a.c not in this'</span>)</span><br></pre></td></tr></table></figure><h3 id="commands-getoutput"><a href="#commands-getoutput" class="headerlink" title="commands.getoutput"></a>commands.getoutput</h3><p>commands模块只能在Python2中使用，Python3将其移除了。<code>commands.getoutput</code>方法直接<strong>将外部程序的输出结果作为字符串返回了</strong>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> commands</span><br><span class="line"></span><br><span class="line">commands.getoutput(<span class="string">'ls -l'</span>)        <span class="comment"># 返回输出结果</span></span><br><span class="line">commands.getstatusoutput(<span class="string">'ls -l'</span>)  <span class="comment"># 返回(状态码， 输出结果)</span></span><br></pre></td></tr></table></figure><h3 id="subprocess-call"><a href="#subprocess-call" class="headerlink" title="subprocess.call"></a>subprocess.call</h3><p>Python3中引入的模块，在Python3中推荐使用该模块。<code>subprocess.call</code>会<strong>将外部程序的输出结果输出并返回状态码</strong>。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> subprocess improt call</span><br><span class="line"></span><br><span class="line"><span class="comment"># 可以将命令和参数以列表的形式传入</span></span><br><span class="line">code = call([<span class="string">'ls'</span>, <span class="string">'-l'</span>, <span class="string">'-a'</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment"># 也可以用字符串作为传入的参数（通过设置参数shell=True）</span></span><br><span class="line">code = call(<span class="string">'ls -l -a'</span>, shell=<span class="literal">True</span>)</span><br></pre></td></tr></table></figure><h2 id="文件操作API"><a href="#文件操作API" class="headerlink" title="文件操作API"></a>文件操作API</h2><h3 id="创建文件"><a href="#创建文件" class="headerlink" title="创建文件"></a>创建文件</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="comment"># touch a.c</span></span><br><span class="line">file = open(<span class="string">'a.c'</span>, <span class="string">'w'</span>)</span><br><span class="line">file.close()</span><br><span class="line"></span><br><span class="line"><span class="comment"># mkdir dir</span></span><br><span class="line">os.mkdir(<span class="string">'dir'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># mkdir -p dir1/dir2</span></span><br><span class="line">os.mkdirs(<span class="string">'dir1/dir2'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ln a.c b.c</span></span><br><span class="line">os.link(<span class="string">'a.c'</span>, <span class="string">'b.c'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># ln -s a.c b.c</span></span><br><span class="line">os.symlink(<span class="string">'a.c'</span>, <span class="string">'b.c'</span>)</span><br></pre></td></tr></table></figure><h3 id="删除文件"><a href="#删除文件" class="headerlink" title="删除文件"></a>删除文件</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="comment"># rm a.c</span></span><br><span class="line">os.remove(<span class="string">'a.c'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># rm -r dir</span></span><br><span class="line">os.rmdir(<span class="string">'dir'</span>)</span><br></pre></td></tr></table></figure><h3 id="复制文件"><a href="#复制文件" class="headerlink" title="复制文件"></a>复制文件</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> shutil</span><br><span class="line"></span><br><span class="line"><span class="comment"># 参数src为源文件名字，参数dst为目标文件或目录名字</span></span><br><span class="line"><span class="comment"># shutil.copy(src, dst)</span></span><br><span class="line"><span class="comment"># shutil.copy2(src, dst)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># cp a.c b.c</span></span><br><span class="line">shutil.copy(<span class="string">'a.c'</span>, <span class="string">'b.c'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># cp a.c dir/b.c</span></span><br><span class="line">shutil.copy(<span class="string">'a.c'</span>, <span class="string">'dir/'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># cp -p a.c b.c</span></span><br><span class="line">shutil.copy2(<span class="string">'a.c'</span>, <span class="string">'b.c'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 参数src为源目录名字，dst为目标目录名字</span></span><br><span class="line"><span class="comment"># shutil.copytree(srd, dst)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># cp dir1 dir2 -r</span></span><br><span class="line">shutil.copytree(<span class="string">'dir1'</span>, <span class="string">'dir2'</span>)</span><br></pre></td></tr></table></figure><h3 id="移动文件或重命名"><a href="#移动文件或重命名" class="headerlink" title="移动文件或重命名"></a>移动文件或重命名</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> shutil</span><br><span class="line"></span><br><span class="line"><span class="comment"># shutil.move(src, dst)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># mv a.c b.c</span></span><br><span class="line">shutil.move(<span class="string">'a.c'</span>, <span class="string">'b.c'</span>)</span><br></pre></td></tr></table></figure><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line"><span class="comment"># 切换当前工作目录到/home/ifantsai/下</span></span><br><span class="line">os.chdir(<span class="string">'/home/ifantsai/'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 返回当前工作目录</span></span><br><span class="line">work_path = os.getcwd()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改变当前进程的根目录为当前目录</span></span><br><span class="line">os.chroot(<span class="string">'./'</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改变a.c的权限为777</span></span><br><span class="line">os.chmod(<span class="string">'a.c'</span>, os.S_IRWXU <span class="keyword">or</span> os.S_IRWXG <span class="keyword">or</span> os.S_IRWXO)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改变文件的属主</span></span><br><span class="line">os.chown(<span class="string">'a.c'</span>, uid, gid)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 返回b.c这个符号链接所指向的路径</span></span><br><span class="line">path = os.readlink(<span class="string">'b.c'</span>)</span><br></pre></td></tr></table></figure><blockquote><p>注: 路径相关操作在<code>os.path</code>模块中。命令行参数在<code>sys</code>模块中, <code>sys.argc</code>为参数个数，<code>sys.argv</code>为参数列表，其中<code>sys.argv[0]</code>为程序本身</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;Python调用Shell命令&quot;&gt;&lt;a href=&quot;#Python调用Shell命令&quot; class=&quot;headerlink&quot; title=&quot;Python调用Shell命令&quot;&gt;&lt;/a&gt;Python调用Shell命令&lt;/h2&gt;&lt;h3 id=&quot;os-system&quot;&gt;&lt;a href=&quot;#os-system&quot; class=&quot;headerlink&quot; title=&quot;os.system&quot;&gt;&lt;/a&gt;os.system&lt;/h3&gt;&lt;p&gt;&lt;code&gt;os.system&lt;/code&gt;函数与系统编程中的exec族函数调用一致，创建出子进程后代码段由外部程序替换，&lt;strong&gt;不会返回外部程序运行结果&lt;/strong&gt;。&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; os&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;os.system(&lt;span class=&quot;string&quot;&gt;&#39;ls -l&#39;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;h3 id=&quot;os-popen&quot;&gt;&lt;a href=&quot;#os-popen&quot; class=&quot;headerlink&quot; title=&quot;os.popen&quot;&gt;&lt;/a&gt;os.popen&lt;/h3&gt;&lt;p&gt;&lt;code&gt;os.popen&lt;/code&gt;返回的是一个文件对象，它将外部程序运行结果保存在文件对象中，当调用其&lt;code&gt;read&lt;/code&gt;方法时就会得到运行结果。&lt;strong&gt;该方法可以得到外部程序的运行结果&lt;/strong&gt;。&lt;/p&gt;
    
    </summary>
    
    
      <category term="python" scheme="https://www.caiyifan.cn/tags/python/"/>
    
      <category term="shell" scheme="https://www.caiyifan.cn/tags/shell/"/>
    
  </entry>
  
  <entry>
    <title>Linux驱动之Input子系统剖析</title>
    <link href="https://www.caiyifan.cn/p/2d34a0d6.html"/>
    <id>https://www.caiyifan.cn/p/2d34a0d6.html</id>
    <published>2019-06-06T07:58:08.000Z</published>
    <updated>2024-05-01T10:34:38.735Z</updated>
    
    <content type="html"><![CDATA[<p>为了对多种不同类型的输入设备进行统一的处理，内核在字符设备驱动上抽象出一层，即输入子系统。 </p><p>输入子系统由三部分组成：</p><ul><li>事件驱动层</li><li>输入核心层</li><li>设备驱动层</li></ul><a id="more"></a><p><img src="https://img.caiyifan.cn/Input%E5%AD%90%E7%B3%BB%E7%BB%9F.png"></p><p><strong>事件驱动层</strong>负责处理和应用程序的接口，向应用程序提供简单的、统一的事件接口。</p><p><strong>设备驱动层</strong>负责与底层输入设备的通信。</p><p><strong>输入核心层</strong>负责各个结构体的注册以及事件驱动层与设备驱动层的数据传递。</p><p>事件驱动层是内核提供的，对所有输入类设备都是通用的，内核里已经支持所有的事件驱动。而驱动开发则只需针对具体输入设备实现设备驱动。</p><h2 id="三个重要结构体"><a href="#三个重要结构体" class="headerlink" title="三个重要结构体"></a>三个重要结构体</h2><p>都定义在<code>include/linux/input.h</code>中。</p><ul><li><strong>struct input_dev</strong>: 对输入设备的抽象描述，继承自<code>struct device</code></li><li><strong>struct input_handler</strong>: 代表输入设备的处理方法</li><li><strong>struct input_handle</strong>: 用来关联某个input_dev和input_handler</li></ul><h3 id="struct-input-dev"><a href="#struct-input-dev" class="headerlink" title="struct input_dev"></a>struct input_dev</h3><p>input_dev 代表底层的输入设备，比如按键或鼠标，所有输入设备的input_dev对象保存在一个全局的input_dev链表里。</p><h3 id="struct-input-handler"><a href="#struct-input-handler" class="headerlink" title="struct input_handler"></a>struct input_handler</h3><p>input_handler 代表某个输入设备的处理方法，比如evdev就是专门处理输入设备产生的事件，所有的input_handler对象保存在一个全局的input_handler链表里。</p><h3 id="struct-input-handle"><a href="#struct-input-handle" class="headerlink" title="struct input_handle"></a>struct input_handle</h3><p>一个input_dev可以有多个input_handler，比如鼠标可以由evdev和mousedev来处理它产生的输入；同样，一个input_handler可以用于多种输入设备的事件处理。由于这种多对多关系的存在，所以需要将input_dev和input_handler关联起来，而input_handle就是用来关联两者的。每个input_handle都会产生一个设备文件节点，比如&#x2F;dev&#x2F;input 下的四个文件event0~3。通过input_handle就可以找到对应的input_dev和input_handler。</p><h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>笔者会大体上对input子系统的源码进行分析，如若分析的有出入，还望指出。在分析之前，以一张input整体架构图来呈现整个输入设备到用户空间的数据传递。</p><p><img src="https://img.caiyifan.cn/Input%E5%AD%90%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84%E4%B8%AD%E6%96%87%E5%9B%BE.png">  </p><h3 id="事件驱动层"><a href="#事件驱动层" class="headerlink" title="事件驱动层"></a>事件驱动层</h3><p>内核在事件驱动层中实现了一个输入设备通用的事件驱动，即evdev，其实现在<code>driver/input/evdev.c</code>中。无论是按键、触摸屏还是鼠标，都会通过evdev进行输入事件的处理。比如鼠标，如果用户空间读取的是evdev提供的设备节点，则上报的是一个未经处理的通用于所有输入设备的事件，而mousedev则会对输入事件进行处理从而上报的是鼠标特有的事件。笔者从evdev.c入手分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> __init <span class="title">evdev_init</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> input_register_handler(&amp;evdev_handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过调用<code>input_register_handler</code>函数进行了<code>evdev_handler</code>的注册。<code>evdev_handler</code>是<code>struct input_handler</code>的实例对象。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">input_device_id</span> <span class="title">evdev_ids</span>[] = &#123;</span></span><br><span class="line">    &#123; .driver_info = <span class="number">1</span> &#125;,   <span class="comment">/* Matches all devices */</span></span><br><span class="line">    &#123; &#125;,            <span class="comment">/* Terminating zero entry */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">input_handler</span> <span class="title">evdev_handler</span> = &#123;</span></span><br><span class="line">    .event      = evdev_event,</span><br><span class="line">    .<span class="built_in">connect</span>    = evdev_connect,</span><br><span class="line">    .<span class="built_in">disconnect</span> = evdev_disconnect,</span><br><span class="line">    .fops       = &amp;evdev_fops,</span><br><span class="line">    .minor      = EVDEV_MINOR_BASE,</span><br><span class="line">    .name       = <span class="string">"evdev"</span>,</span><br><span class="line">    .id_table   = evdev_ids,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>evdev_handler中描述了一些输入的处理函数以及与设备匹配用的<code>id_table</code>，在接下去的源码里会使用到。</p><p>现在进到<code>input_register_handler</code>函数里进行分析，以下是该函数所有源码，接下去会拆开分析。</p><blockquote><p>input_register_handler函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">input_register_handler</span><span class="params">(struct input_handler *handler)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="class"><span class="keyword">struct</span> <span class="title">input_dev</span> *<span class="title">dev</span>;</span></span><br><span class="line">   <span class="keyword">int</span> retval;</span><br><span class="line"></span><br><span class="line">   retval = mutex_lock_interruptible(&amp;input_mutex);</span><br><span class="line">   <span class="keyword">if</span> (retval)</span><br><span class="line">      <span class="keyword">return</span> retval;</span><br><span class="line"></span><br><span class="line">   INIT_LIST_HEAD(&amp;handler-&gt;h_list);</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (handler-&gt;fops != <span class="literal">NULL</span>) &#123;</span><br><span class="line">      <span class="comment">// 每个事件驱动所支持的次设备号范围是[32 * n, 32 * n + 32)</span></span><br><span class="line">      <span class="comment">// 所以需要除于32来得到在input_table中的索引</span></span><br><span class="line">      <span class="keyword">if</span> (input_table[handler-&gt;minor &gt;&gt; <span class="number">5</span>]) &#123;  </span><br><span class="line">          <span class="comment">// 重复注册，错误</span></span><br><span class="line">          retval = -EBUSY;</span><br><span class="line">          <span class="keyword">goto</span> out;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">// 将handler放入input_table</span></span><br><span class="line">      input_table[handler-&gt;minor &gt;&gt; <span class="number">5</span>] = handler;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 将handler放入input_handler_list链表中，表示注册了该handler</span></span><br><span class="line">   list_add_tail(&amp;handler-&gt;node, &amp;input_handler_list);</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 遍历已经注册的设备，匹配device和handler，</span></span><br><span class="line">   <span class="comment">// 匹配成功则调用handler-&gt;connect函数将device和handler关联成handle，</span></span><br><span class="line">   <span class="comment">// 然后进行设备的注册</span></span><br><span class="line">   list_for_each_entry(dev, &amp;input_dev_list, node)</span><br><span class="line">      input_attach_handler(dev, handler);</span><br><span class="line"></span><br><span class="line">   input_wakeup_procfs_readers();</span><br><span class="line"></span><br><span class="line">   out:</span><br><span class="line">   mutex_unlock(&amp;input_mutex);</span><br><span class="line">   <span class="keyword">return</span> retval;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></blockquote><p><code>input_register_handler</code>函数定义在input.c中，即现在进入到了输入核心层。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (handler-&gt;fops != <span class="literal">NULL</span>) &#123;</span><br><span class="line">    <span class="comment">// 每个事件驱动所支持的次设备号范围是[32 * n, 32 * n + 32)</span></span><br><span class="line">    <span class="comment">// 所以需要除于32来得到在input_table中的索引</span></span><br><span class="line">    <span class="keyword">if</span> (input_table[handler-&gt;minor &gt;&gt; <span class="number">5</span>]) &#123;  </span><br><span class="line">        <span class="comment">// 重复注册，错误</span></span><br><span class="line">        retval = -EBUSY;</span><br><span class="line">        <span class="keyword">goto</span> out;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 将handler放入input_table</span></span><br><span class="line">    input_table[handler-&gt;minor &gt;&gt; <span class="number">5</span>] = handler;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将handler放入input_handler_list链表中，表示注册了该handler</span></span><br><span class="line">list_add_tail(&amp;handler-&gt;node, &amp;input_handler_list);</span><br></pre></td></tr></table></figure><p>从<code>evdev_handler</code>的定义中可以看到<code>handler-&gt;fops</code>是有定义的，所以进入到子语句。这里解释一下<code>handler-&gt;minor &gt;&gt; 5</code>，内核中对每个事件驱动所支持的次设备号范围规定是[32 * n, 32 * n + 32)，比如mousedev的次设备号范围是[32, 64)、evdev的次设备号范围是[64, 96)等，相当于将次设备号以32个为一组对各种事件驱动进行了分类。该段代码就是找到正确的位置将handler放入input_table中，然后将handler放入input_handler_list链表中，表示注册了该handler。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">list_for_each_entry(dev, &amp;input_dev_list, node)</span><br><span class="line">    input_attach_handler(dev, handler);</span><br></pre></td></tr></table></figure><p>这段代码是在遍历已经注册的设备，在<code>input_attach_handler</code>函数里匹配device和handler，匹配成功则调用<code>handler-&gt;connect</code>函数将device和handler关联成handle，然后进行设备的注册，然后<code>input_register_handler</code>函数基本上执行完毕。</p><p>进到<code>input_attach_handler</code>函数里进行分析。</p><blockquote><p>input_attach_handler函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">input_attach_handler</span><span class="params">(struct input_dev *dev, struct input_handler *handler)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">input_device_id</span> *<span class="title">id</span>;</span></span><br><span class="line">   <span class="keyword">int</span> error;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 匹配input_handler和input_dev</span></span><br><span class="line">   id = input_match_device(handler, dev);</span><br><span class="line">   <span class="keyword">if</span> (!id)</span><br><span class="line">      <span class="keyword">return</span> -ENODEV;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 匹配成功后，调用handler-&gt;connect将input_handler和input_dev绑定成input_handle</span></span><br><span class="line">   error = handler-&gt;<span class="built_in">connect</span>(handler, dev, id);</span><br><span class="line">   <span class="keyword">if</span> (error &amp;&amp; error != -ENODEV)</span><br><span class="line">      printk(KERN_ERR</span><br><span class="line">          <span class="string">"input: failed to attach handler %s to device %s, "</span></span><br><span class="line">          <span class="string">"error: %d\n"</span>,</span><br><span class="line">          handler-&gt;name, kobject_name(&amp;dev-&gt;dev.kobj), error);</span><br><span class="line"></span><br><span class="line">   <span class="keyword">return</span> error;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></blockquote><p>从代码中可以看出先是匹配<code>input_handler</code>和<code>input_dev</code>，匹配成功后则调用connect函数进行连接。</p><blockquote><p><code>input_match_device</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">const</span> struct input_device_id *<span class="title">input_match_device</span><span class="params">(struct input_handler *handler, struct input_dev *dev)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="keyword">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">input_device_id</span> *<span class="title">id</span>;</span></span><br><span class="line">   <span class="keyword">int</span> i;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 遍历input_hanlder的id_table，匹配input_dev</span></span><br><span class="line">   <span class="keyword">for</span> (id = handler-&gt;id_table; id-&gt;flags || id-&gt;driver_info; id++) &#123;</span><br><span class="line"></span><br><span class="line">       <span class="comment">/* 严格匹配bus、vendor、product、version，前提是flag中有定义 */</span></span><br><span class="line">       <span class="keyword">if</span> (id-&gt;flags &amp; INPUT_DEVICE_ID_MATCH_BUS)</span><br><span class="line">          <span class="keyword">if</span> (id-&gt;bustype != dev-&gt;id.bustype)</span><br><span class="line">              <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> (id-&gt;flags &amp; INPUT_DEVICE_ID_MATCH_VENDOR)</span><br><span class="line">          <span class="keyword">if</span> (id-&gt;vendor != dev-&gt;id.vendor)</span><br><span class="line">              <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> (id-&gt;flags &amp; INPUT_DEVICE_ID_MATCH_PRODUCT)</span><br><span class="line">          <span class="keyword">if</span> (id-&gt;product != dev-&gt;id.product)</span><br><span class="line">              <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">       <span class="keyword">if</span> (id-&gt;flags &amp; INPUT_DEVICE_ID_MATCH_VERSION)</span><br><span class="line">          <span class="keyword">if</span> (id-&gt;version != dev-&gt;id.version)</span><br><span class="line">              <span class="keyword">continue</span>;</span><br><span class="line">          </span><br><span class="line">       <span class="comment">// 严格匹配所有事件类型</span></span><br><span class="line">       MATCH_BIT(evbit,  EV_MAX);</span><br><span class="line">       <span class="comment">/* 严格匹配所有事件的子事件 */</span></span><br><span class="line">       MATCH_BIT(keybit, KEY_MAX);</span><br><span class="line">       MATCH_BIT(relbit, REL_MAX);</span><br><span class="line">       MATCH_BIT(absbit, ABS_MAX);</span><br><span class="line">       MATCH_BIT(mscbit, MSC_MAX);</span><br><span class="line">       MATCH_BIT(ledbit, LED_MAX);</span><br><span class="line">       MATCH_BIT(sndbit, SND_MAX);</span><br><span class="line">       MATCH_BIT(ffbit,  FF_MAX);</span><br><span class="line">       MATCH_BIT(swbit,  SW_MAX);</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 如果有定义handler-&gt;match函数，再调用handler-&gt;match进行匹配</span></span><br><span class="line">       <span class="keyword">if</span> (!handler-&gt;match || handler-&gt;match(handler, dev))</span><br><span class="line">           <span class="keyword">return</span> id;</span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></blockquote><p>从<code>evdev_handler</code>中的<code>id_table</code>的定义可以知道并没有定义任何flag和bit，所以这些严格匹配在evdev中都不会进行。而且<code>handler-&gt;match</code>为NULL，所以对于evdev而言这个函数并没有做什么，而是直接将id返回了。</p><p>回到<code>input_attach_handler</code>函数，最后在匹配成功后调用了<code>handler-&gt;connect</code>函数。这个connect函数实现在事件驱动层，所以回到evdev.c。</p><blockquote><p><code>evdev_connect</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">evdev_connect</span><span class="params">(struct input_handler *handler, struct input_dev *dev,<span class="keyword">const</span> struct input_device_id *id)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="class"><span class="keyword">struct</span> <span class="title">evdev</span> *<span class="title">evdev</span>;</span></span><br><span class="line">   <span class="keyword">int</span> minor;</span><br><span class="line">   <span class="keyword">int</span> error;</span><br><span class="line"></span><br><span class="line">   <span class="comment">// 找到一个evdev_table中未使用的索引</span></span><br><span class="line">   <span class="keyword">for</span> (minor = <span class="number">0</span>; minor &lt; EVDEV_MINORS; minor++)</span><br><span class="line">      <span class="keyword">if</span> (!evdev_table[minor])</span><br><span class="line">          <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> (minor == EVDEV_MINORS) &#123;</span><br><span class="line">      printk(KERN_ERR <span class="string">"evdev: no more free evdev devices\n"</span>);</span><br><span class="line">      <span class="keyword">return</span> -ENFILE;</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   evdev = kzalloc(<span class="keyword">sizeof</span>(struct evdev), GFP_KERNEL);</span><br><span class="line">   <span class="keyword">if</span> (!evdev)</span><br><span class="line">      <span class="keyword">return</span> -ENOMEM;</span><br><span class="line"></span><br><span class="line">   INIT_LIST_HEAD(&amp;evdev-&gt;client_list);</span><br><span class="line">   spin_lock_init(&amp;evdev-&gt;client_lock);</span><br><span class="line">   mutex_init(&amp;evdev-&gt;mutex);</span><br><span class="line">   init_waitqueue_head(&amp;evdev-&gt;wait);</span><br><span class="line"></span><br><span class="line">   <span class="comment">/* 初始化struct evdev */</span></span><br><span class="line">   dev_set_name(&amp;evdev-&gt;dev, <span class="string">"event%d"</span>, minor);</span><br><span class="line">   evdev-&gt;exist = <span class="number">1</span>;</span><br><span class="line">   evdev-&gt;minor = minor;</span><br><span class="line"></span><br><span class="line">   evdev-&gt;handle.dev = input_get_device(dev);</span><br><span class="line">   evdev-&gt;handle.name = dev_name(&amp;evdev-&gt;dev);</span><br><span class="line">   evdev-&gt;handle.handler = handler;</span><br><span class="line">   evdev-&gt;handle.<span class="keyword">private</span> = evdev;</span><br><span class="line">                                         <span class="comment">// EVDEV_MINOR_BASE + minor 生成真正的次设备号</span></span><br><span class="line">   evdev-&gt;dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);</span><br><span class="line">   evdev-&gt;dev<span class="class">.<span class="keyword">class</span> = &amp;<span class="title">input_class</span>;</span></span><br><span class="line">   evdev-&gt;dev.parent = &amp;dev-&gt;dev;</span><br><span class="line">   evdev-&gt;dev.<span class="built_in">release</span> = evdev_free;</span><br><span class="line">   device_initialize(&amp;evdev-&gt;dev);</span><br><span class="line"></span><br><span class="line">   error = input_register_handle(&amp;evdev-&gt;handle);   <span class="comment">// 注册input_handle</span></span><br><span class="line">   <span class="keyword">if</span> (error)</span><br><span class="line">      <span class="keyword">goto</span> err_free_evdev;</span><br><span class="line"></span><br><span class="line">   error = evdev_install_chrdev(evdev);</span><br><span class="line">   <span class="keyword">if</span> (error)</span><br><span class="line">      <span class="keyword">goto</span> err_unregister_handle;</span><br><span class="line"></span><br><span class="line">   error = device_add(&amp;evdev-&gt;dev);    <span class="comment">// 注册设备，创建设备节点</span></span><br><span class="line">   <span class="keyword">if</span> (error)</span><br><span class="line">       <span class="keyword">goto</span> err_cleanup_evdev;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">   err_cleanup_evdev:</span><br><span class="line">   evdev_cleanup(evdev);  </span><br><span class="line">   err_unregister_handle:</span><br><span class="line">   input_unregister_handle(&amp;evdev-&gt;handle);</span><br><span class="line">   err_free_evdev:</span><br><span class="line">   put_device(&amp;evdev-&gt;dev);</span><br><span class="line">   <span class="keyword">return</span> error;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></blockquote><p>实例化了一个<code>struct evdev</code>对象，该结构体是对一个完整的evdev事件驱动的抽象描述。初始化<code>struct evdev</code>，将<code>input_handler</code>和<code>input_dev</code>关联起来形成<code>input_handle</code>，然后赋给<code>evdev-&gt;handle</code>，生成设备的设备号，向内核注册<code>input_handle</code>，最后注册设备以及创建设备节点。至此evdev的注册就结束了。</p><h3 id="设备驱动层"><a href="#设备驱动层" class="headerlink" title="设备驱动层"></a>设备驱动层</h3><p>以usbmouse.c为例分析鼠标的设备驱动，鼠标是挂载在usb总线下，笔者在这里将usb相关的代码忽略，只关心输入子系统有关的代码。根据Linux设备模型的原理，直接进入到<code>usb_mouse_probe</code>函数进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">input_dev</span> *<span class="title">input_dev</span>;</span></span><br><span class="line">input_dev = input_allocate_device();</span><br><span class="line">input_dev-&gt;name = mouse-&gt;name;</span><br><span class="line">input_dev-&gt;phys = mouse-&gt;phys;</span><br><span class="line">usb_to_input_id(dev, &amp;input_dev-&gt;id);</span><br><span class="line">input_dev-&gt;dev.parent = &amp;intf-&gt;dev;</span><br><span class="line"></span><br><span class="line">input_dev-&gt;evbit[<span class="number">0</span>] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);</span><br><span class="line">input_dev-&gt;keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |</span><br><span class="line">    BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);</span><br><span class="line">input_dev-&gt;relbit[<span class="number">0</span>] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);</span><br><span class="line">input_dev-&gt;keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |</span><br><span class="line">    BIT_MASK(BTN_EXTRA);</span><br><span class="line">input_dev-&gt;relbit[<span class="number">0</span>] |= BIT_MASK(REL_WHEEL);</span><br><span class="line"></span><br><span class="line">input_set_drvdata(input_dev, mouse);</span><br><span class="line"></span><br><span class="line">input_dev-&gt;<span class="built_in">open</span> = usb_mouse_open;</span><br><span class="line">input_dev-&gt;<span class="built_in">close</span> = usb_mouse_close;</span><br><span class="line"></span><br><span class="line">error = input_register_device(mouse-&gt;dev);</span><br></pre></td></tr></table></figure><p>先实例化一个<code>struct input_dev</code>对象，然后进行相关初始化，<code>struct input_dev</code>成员中定义了一些位图，如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> evbit[BITS_TO_LONGS(EV_CNT)];      <span class="comment">// 描述设备所支持的事件类型</span></span><br><span class="line"><span class="comment">/* 描述设备所支持的相应事件的具体编码，或者可以看作是子事件 */</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> keybit[BITS_TO_LONGS(KEY_CNT)];    <span class="comment">// 描述按键类型</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> relbit[BITS_TO_LONGS(REL_CNT)];    <span class="comment">// 描述相对坐标的类型</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> absbit[BITS_TO_LONGS(ABS_CNT)];    <span class="comment">// 描述绝对坐标的类型</span></span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> mscbit[BITS_TO_LONGS(MSC_CNT)];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> ledbit[BITS_TO_LONGS(LED_CNT)];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> sndbit[BITS_TO_LONGS(SND_CNT)];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> ffbit[BITS_TO_LONGS(FF_CNT)];</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> swbit[BITS_TO_LONGS(SW_CNT)];</span><br></pre></td></tr></table></figure><p>所以在初始化中还对<code>evbit</code>、<code>keybit</code>等成员进行了初始化，表示鼠标所支持的事件类型。最后调用<code>input_register_device</code>函数完成了鼠标设备的注册。</p><p>在<code>usb_mouse_irq</code>函数中进行事件的上报。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// data[0] &amp; 0x01 取出最后一位，1表示按下，0表示未按下</span></span><br><span class="line">input_report_key(dev, BTN_LEFT,   data[<span class="number">0</span>] &amp; <span class="number">0x01</span>);</span><br><span class="line">input_report_key(dev, BTN_RIGHT,  data[<span class="number">0</span>] &amp; <span class="number">0x02</span>);</span><br><span class="line">input_report_key(dev, BTN_MIDDLE, data[<span class="number">0</span>] &amp; <span class="number">0x04</span>);</span><br><span class="line">input_report_key(dev, BTN_SIDE,   data[<span class="number">0</span>] &amp; <span class="number">0x08</span>);</span><br><span class="line">input_report_key(dev, BTN_EXTRA,  data[<span class="number">0</span>] &amp; <span class="number">0x10</span>);</span><br><span class="line"></span><br><span class="line">input_report_rel(dev, REL_X,     data[<span class="number">1</span>]);</span><br><span class="line">input_report_rel(dev, REL_Y,     data[<span class="number">2</span>]);</span><br><span class="line">input_report_rel(dev, REL_WHEEL, data[<span class="number">3</span>]);</span><br><span class="line"></span><br><span class="line">input_sync(dev);</span><br></pre></td></tr></table></figure><p>关键部分就是调用<code>input_report_key</code>函数来上报按键信息，调用<code>input_report_rel</code>上报鼠标的相对位移，最后调用<code>input_sync</code> 来提交同步事件，告知input子系统，该设备已经提交了一个完整报告。</p><h3 id="事件传递流程"><a href="#事件传递流程" class="headerlink" title="事件传递流程"></a>事件传递流程</h3><p>设备驱动通过一系列input_report_xxx函数来上报事件，以<code>input_report_key</code>函数为例进行分析。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 提交按键事件</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">inline</span> <span class="keyword">void</span> <span class="title">input_report_key</span><span class="params">(struct input_dev *dev, <span class="keyword">unsigned</span> <span class="keyword">int</span> code, <span class="keyword">int</span> value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    input_event(dev, EV_KEY, code, !!value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>input_report_key</code>函数调用的<code>input_event</code>函数，其实一系列上报函数（包括<code>input_sync</code>函数）都是调用的<code>input_event</code>函数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">input_event</span><span class="params">(struct input_dev *dev,</span></span></span><br><span class="line"><span class="function"><span class="params">         <span class="keyword">unsigned</span> <span class="keyword">int</span> type, <span class="keyword">unsigned</span> <span class="keyword">int</span> code, <span class="keyword">int</span> value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">long</span> flags;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (is_event_supported(type, dev-&gt;evbit, EV_MAX)) &#123;</span><br><span class="line"></span><br><span class="line">        spin_lock_irqsave(&amp;dev-&gt;event_lock, flags);</span><br><span class="line">        add_input_randomness(type, code, value);</span><br><span class="line">        input_handle_event(dev, type, code, value);</span><br><span class="line">        spin_unlock_irqrestore(&amp;dev-&gt;event_lock, flags);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>关键就是调用了<code>input_handle_event</code>函数，而<code>input_handle_event</code>函数中的关键就是下面代码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (disposition &amp; INPUT_PASS_TO_HANDLERS)</span><br><span class="line">    input_pass_event(dev, type, code, value);</span><br></pre></td></tr></table></figure><p>进到<code>input_pass_event</code>函数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">input_pass_event</span><span class="params">(struct input_dev *dev,</span></span></span><br><span class="line"><span class="function"><span class="params">                 <span class="keyword">unsigned</span> <span class="keyword">int</span> type, <span class="keyword">unsigned</span> <span class="keyword">int</span> code, <span class="keyword">int</span> value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">input_handler</span> *<span class="title">handler</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">input_handle</span> *<span class="title">handle</span>;</span></span><br><span class="line"></span><br><span class="line">    rcu_read_lock();</span><br><span class="line"></span><br><span class="line">    handle = rcu_dereference(dev-&gt;grab);</span><br><span class="line">    <span class="keyword">if</span> (handle)</span><br><span class="line">        handle-&gt;handler-&gt;event(handle, type, code, value);</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">bool</span> filtered = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">        list_for_each_entry_rcu(handle, &amp;dev-&gt;h_list, d_node) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!handle-&gt;<span class="built_in">open</span>)</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            handler = handle-&gt;handler;</span><br><span class="line">            <span class="keyword">if</span> (!handler-&gt;filter) &#123;</span><br><span class="line">                <span class="keyword">if</span> (filtered)</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">                handler-&gt;event(handle, type, code, value);</span><br><span class="line"></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (handler-&gt;filter(handle, type, code, value))</span><br><span class="line">                filtered = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    rcu_read_unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到核心就是调用<code>handler-&gt;event</code>函数，以evdev为例，回到evdev.c中，进入到<code>evdev_event</code>函数中</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">client = rcu_dereference(evdev-&gt;grab);</span><br><span class="line"><span class="keyword">if</span> (client)</span><br><span class="line">    evdev_pass_event(client, &amp;event);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    list_for_each_entry_rcu(client, &amp;evdev-&gt;client_list, node)</span><br><span class="line">    evdev_pass_event(client, &amp;event);</span><br></pre></td></tr></table></figure><p>需要关心的部分是从evdev对象中取出了client对象（两者的挂接是在open时完成的），然后执行了<code>evdev_pass_event(client, &amp;event);</code>（其中<code>evdev</code>是<code>struct evdev</code>的实例对象，是对一个完整的evdev事件驱动的抽象描述，其中<code>struct evdev_client *grab</code>成员管理该事件驱动下的所有<code>client</code>；<code>client</code>是<code>struct evdev_client</code>的实例对象，对于同一个设备，每打开一次就会实例化出一个该结构体的对象）</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">evdev_pass_event</span><span class="params">(struct evdev_client *client,</span></span></span><br><span class="line"><span class="function"><span class="params">                 struct input_event *event)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">     * Interrupts are disabled, just acquire the lock</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    spin_lock(&amp;client-&gt;buffer_lock);</span><br><span class="line">    wake_lock_timeout(&amp;client-&gt;wake_lock, <span class="number">5</span> * HZ);</span><br><span class="line">    client-&gt;<span class="built_in">buffer</span>[client-&gt;head++] = *event;</span><br><span class="line">    client-&gt;head &amp;= EVDEV_BUFFER_SIZE - <span class="number">1</span>;</span><br><span class="line">    spin_unlock(&amp;client-&gt;buffer_lock);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (event-&gt;type == EV_SYN)</span><br><span class="line">        kill_fasync(&amp;client-&gt;fasync, SIGIO, POLL_IN);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上面代码可以看到将<code>struct input_event</code>的实例对象存进了client中的buffer里。<code>struct evdev_client</code>的定义如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 每打开一次设备就会实例化出该结构体</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">evdev_client</span> &#123;</span></span><br><span class="line">    <span class="comment">// buffer用来存储从设备驱动中提交上来的事件，</span></span><br><span class="line">    <span class="comment">// 当应用程序read设备文件时，事件驱动会把该buffer传递给应用层</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">input_event</span> <span class="title">buffer</span>[<span class="title">EVDEV_BUFFER_SIZE</span>];</span> </span><br><span class="line">    <span class="keyword">int</span> head;</span><br><span class="line">    <span class="keyword">int</span> tail;</span><br><span class="line">    <span class="keyword">spinlock_t</span> buffer_lock; <span class="comment">/* protects access to buffer, head and tail */</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">fasync_struct</span> *<span class="title">fasync</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">evdev</span> *<span class="title">evdev</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">list_head</span> <span class="title">node</span>;</span></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">wake_lock</span> <span class="title">wake_lock</span>;</span></span><br><span class="line">    <span class="keyword">char</span> name[<span class="number">28</span>];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>所以到此就清晰了事件从底层设备如何传递到事件驱动层的，事件驱动层的<code>cline-&gt;buffer</code>就是用来中转数据的，接下来我们关心事件是如何从事件驱动层传递给应用层。</p><p>以evdev.c为例，进入到handler中的fops中的open和read函数。</p><p><code>evdev_open</code>函数主要做的是根据次设备号减去基地址得到索引，从<code>evdev_table</code>中取出<code>evdev</code>对象，然后实例化出一个<code>client</code>对象，将<code>clinet</code>对象绑定到<code>evdev</code>对象中。</p><p><code>evdev_read</code>函数的核心部分如下</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (retval + input_event_size() &lt;= count &amp;&amp;</span><br><span class="line">       evdev_fetch_next_event(client, &amp;event)) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (input_event_to_user(<span class="built_in">buffer</span> + retval, &amp;event))</span><br><span class="line">        <span class="keyword">return</span> -EFAULT;</span><br><span class="line"></span><br><span class="line">    retval += input_event_size();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>evdev_fetch_next_event</code>函数从<code>client-&gt;buffer</code>中取出从底层设备提交上来的事件赋给<code>event</code>，然后调用<code>input_event_to_user</code>函数将这个<code>event</code>传递给从用户层传下来的<code>buffer</code>，完成从事件驱动层到用户空间的数据传递。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;为了对多种不同类型的输入设备进行统一的处理，内核在字符设备驱动上抽象出一层，即输入子系统。 &lt;/p&gt;
&lt;p&gt;输入子系统由三部分组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件驱动层&lt;/li&gt;
&lt;li&gt;输入核心层&lt;/li&gt;
&lt;li&gt;设备驱动层&lt;/li&gt;
&lt;/ul&gt;
    
    </summary>
    
    
      <category term="linux" scheme="https://www.caiyifan.cn/tags/linux/"/>
    
      <category term="嵌入式" scheme="https://www.caiyifan.cn/tags/%E5%B5%8C%E5%85%A5%E5%BC%8F/"/>
    
      <category term="linux驱动" scheme="https://www.caiyifan.cn/tags/linux%E9%A9%B1%E5%8A%A8/"/>
    
      <category term="input子系统" scheme="https://www.caiyifan.cn/tags/input%E5%AD%90%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
</feed>
