<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>菜鸟</title>
  
  <subtitle>个人记录</subtitle>
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2024-11-25T15:34:34.641Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>John Doe</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>IIC总线驱动</title>
    <link href="http://example.com/posts/IIC%E6%80%BB%E7%BA%BF%E9%A9%B1%E5%8A%A8.html"/>
    <id>http://example.com/posts/IIC%E6%80%BB%E7%BA%BF%E9%A9%B1%E5%8A%A8.html</id>
    <published>2024-11-25T15:33:19.000Z</published>
    <updated>2024-11-25T15:34:34.641Z</updated>
    
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>platform总线驱动</title>
    <link href="http://example.com/posts/platform%E6%80%BB%E7%BA%BF%E9%A9%B1%E5%8A%A8.html"/>
    <id>http://example.com/posts/platform%E6%80%BB%E7%BA%BF%E9%A9%B1%E5%8A%A8.html</id>
    <published>2024-11-25T15:33:19.000Z</published>
    <updated>2024-11-25T15:34:58.013Z</updated>
    
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>内核字符操作函数</title>
    <link href="http://example.com/posts/%E5%86%85%E6%A0%B8%E5%AD%97%E7%AC%A6%E6%93%8D%E4%BD%9C%E5%87%BD%E6%95%B0.html"/>
    <id>http://example.com/posts/%E5%86%85%E6%A0%B8%E5%AD%97%E7%AC%A6%E6%93%8D%E4%BD%9C%E5%87%BD%E6%95%B0.html</id>
    <published>2024-11-25T15:33:19.000Z</published>
    <updated>2024-11-25T15:36:10.528Z</updated>
    
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>字符设备驱动创建流程</title>
    <link href="http://example.com/posts/%E5%AD%97%E7%AC%A6%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B.html"/>
    <id>http://example.com/posts/%E5%AD%97%E7%AC%A6%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B.html</id>
    <published>2024-11-25T15:33:19.000Z</published>
    <updated>2024-11-25T15:35:34.385Z</updated>
    
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>设备驱动总线模型</title>
    <link href="http://example.com/posts/%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%80%BB%E7%BA%BF%E6%A8%A1%E5%9E%8B.html"/>
    <id>http://example.com/posts/%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%80%BB%E7%BA%BF%E6%A8%A1%E5%9E%8B.html</id>
    <published>2024-11-25T15:33:19.000Z</published>
    <updated>2024-11-25T15:33:49.858Z</updated>
    
    <content type="html"><![CDATA[<p>kobject<br>ktype<br>kset</p><p>kobject_create_and_add函数等</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;kobject&lt;br&gt;ktype&lt;br&gt;kset&lt;/p&gt;
&lt;p&gt;kobject_create_and_add函数等&lt;/p&gt;
</summary>
      
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>Linux内核内存管理</title>
    <link href="http://example.com/posts/Linux%E5%86%85%E6%A0%B8%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86.html"/>
    <id>http://example.com/posts/Linux%E5%86%85%E6%A0%B8%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86.html</id>
    <published>2024-11-25T15:30:49.000Z</published>
    <updated>2024-11-25T15:32:38.171Z</updated>
    
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>Linux内核进程管理&amp;进程调度</title>
    <link href="http://example.com/posts/Linux%E5%86%85%E6%A0%B8%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86-%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6.html"/>
    <id>http://example.com/posts/Linux%E5%86%85%E6%A0%B8%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86-%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6.html</id>
    <published>2024-11-25T15:30:49.000Z</published>
    <updated>2024-11-25T15:32:53.635Z</updated>
    
    <content type="html"><![CDATA[<p>slab MMU TLB等</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;slab MMU TLB等&lt;/p&gt;
</summary>
      
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>内核文件系统&amp;VFS</title>
    <link href="http://example.com/posts/%E5%86%85%E6%A0%B8%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F-VFS.html"/>
    <id>http://example.com/posts/%E5%86%85%E6%A0%B8%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F-VFS.html</id>
    <published>2024-11-25T15:29:53.000Z</published>
    <updated>2024-11-25T15:30:16.987Z</updated>
    
    
    
    
    <category term="待填坑" scheme="http://example.com/categories/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
    
    <category term="待填坑" scheme="http://example.com/tags/%E5%BE%85%E5%A1%AB%E5%9D%91/"/>
    
  </entry>
  
  <entry>
    <title>等待队列</title>
    <link href="http://example.com/posts/%E7%AD%89%E5%BE%85%E9%98%9F%E5%88%97.html"/>
    <id>http://example.com/posts/%E7%AD%89%E5%BE%85%E9%98%9F%E5%88%97.html</id>
    <published>2024-11-25T15:20:39.000Z</published>
    <updated>2024-11-25T15:21:11.300Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、Linux等待队列概述"><a href="#一、Linux等待队列概述" class="headerlink" title="一、Linux等待队列概述"></a>一、Linux等待队列概述</h3><p>Linux内核的等待队列（Wait Queue）是重要的数据结构，与进程调度机制紧密相关联，可以用来同步对系统资源的访问、异步事件通知、跨进程通信等。</p><p>在Linux中，等待队列以循环链表为基础结构，包括两种数据结构：等待队列头(wait queue head)和等待队列元素(wait queue)，整个等待队列由等待队列头进行管理。下文将用内核源码（基于Linux kernel 5.2）对等待队列进行介绍，详细说明采用等待队列实现进程阻塞和唤醒的方法。</p><h3 id="二、等待队列头和等待队列元素"><a href="#二、等待队列头和等待队列元素" class="headerlink" title="二、等待队列头和等待队列元素"></a>二、等待队列头和等待队列元素</h3><p>等待队列以循环链表为基础结构，链表头和链表项分别为等待队列头和等待队列元素，分别用结构体 wait_queue_head_t 和 wait_queue_entry_t 描述（定义在 linux&#x2F;wait.h ）。</p><p>2.1 基本概念</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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">wait_queue_head</span> &#123;</span><br>    <span class="hljs-type">spinlock_t</span>          lock;<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">list_head</span>    <span class="hljs-title">head</span>;</span><br>&#125;;<br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">wait_queue_head</span> <span class="hljs-title">wait_queue_head_t</span>;</span><br></code></pre></td></tr></table></figure><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><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-title function_">int</span> <span class="hljs-params">(*<span class="hljs-type">wait_queue_func_t</span>)</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry, <span class="hljs-type">unsigned</span> mode, <span class="hljs-type">int</span> flags, <span class="hljs-type">void</span> *key)</span>;<br><span class="hljs-type">int</span> <span class="hljs-title function_">default_wake_function</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry, <span class="hljs-type">unsigned</span> mode, <span class="hljs-type">int</span> flags, <span class="hljs-type">void</span> *key)</span>;<br><span class="hljs-comment">/* wait_queue_entry::flags */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> WQ_FLAG_EXCLUSIVE   0x01</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> WQ_FLAG_WOKEN       0x02</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> WQ_FLAG_BOOKMARK    0x04</span><br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * A single wait-queue entry structure:</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">wait_queue_entry</span> &#123;</span><br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span>        flags;<br>    <span class="hljs-type">void</span>                *private;<br>    <span class="hljs-type">wait_queue_func_t</span>   func;<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">list_head</span>    <span class="hljs-title">entry</span>;</span><br>&#125;;<br><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">wait_queue_entry</span> <span class="hljs-title">wait_queue_entry_t</span>;</span><br></code></pre></td></tr></table></figure><p>等待队列头结构包括一个自旋锁和一个链表头。等待队列元素除了包括链表项，还包括：</p><ul><li>flags : 标识队列元素状态和属性</li><li><code>*private</code>: 用于指向关联进程 task_struct 结构体的指针<ul><li>func : 函数指针，用于指向等待队列被唤醒时的回调的唤醒函数</li></ul></li></ul><p>以进程阻塞和唤醒的过程为例，等待队列的使用场景可以简述为：</p><p>进程 A 因等待某些资源（依赖进程 B 的某些操作）而不得不进入阻塞状态，便将当前进程加入到等待队列 Q 中。进程 B 在一系列操作后，可通知进程 A 所需资源已到位，便调用唤醒函数 wake up 来唤醒等待队列上 Q 的进程，注意此时所有等待在队列 Q 上的进程均被置为可运行状态。</p><p>借助上述描述场景，说明等待队列元素属性 flags 标志的作用，下文也将结合源码进行详细解读。</p><p>(1) WQ_FLAG_EXCLUSIVE </p><p>上述场景中看到，当某进程调用 wake up 函数唤醒等待队列时，队列上所有的进程均被唤醒，在某些场合会出现唤醒的所有进程中，只有某个进程获得了期望的资源，而其他进程由于资源被占用不得不再次进入休眠。如果等待队列中进程数量庞大时，该行为将影响系统性能。</p><p>内核增加了”独占等待”(WQ_FLAG_EXCLUSIVE)来解决此类问题。一个独占等待的行为和通常的休眠类似，但有如下两个重要的不同：</p><p>等待队列元素设置 WQ_FLAG_EXCLUSIVE 标志时，会被添加到等待队列的尾部，而非头部。</p><p>在某等待队列上调用 wake up 时，执行独占等待的进程每次只会唤醒其中第一个（所有非独占等待进程仍会被同时唤醒）。</p><p>(2) WQ_FLAG_WOKEN </p><p>暂时还未理解，TODO</p><p>(3) WQ_FLAG_BOOKMARK </p><p>用于 wake_up() 唤醒等待队列时实现分段遍历，减少单次对自旋锁的占用时间。</p><h3 id="三、-等待队列的创建和初始化"><a href="#三、-等待队列的创建和初始化" class="headerlink" title="三、 等待队列的创建和初始化"></a>三、 等待队列的创建和初始化</h3><p>等待队列头的定义和初始化有两种方式： init_waitqueue_head(&amp;wq_head) 和宏定义  DECLARE_WAIT_QUEUE_HEAD(name)   。</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> init_waitqueue_head(wq_head)                            \</span><br><span class="hljs-meta">    do &#123;                                                        \</span><br><span class="hljs-meta">        static struct lock_class_key __key;                     \</span><br><span class="hljs-meta">        __init_waitqueue_head((wq_head), #wq_head, &amp;__key);     \</span><br><span class="hljs-meta">    &#125; while (0)</span><br><br><span class="hljs-type">void</span> __init_waitqueue_head(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *name, <span class="hljs-keyword">struct</span> lock_class_key *key)<br>&#123;<br>    spin_lock_init(&amp;wq_head-&gt;lock);<br>    lockdep_set_class_and_name(&amp;wq_head-&gt;lock, key, name);<br>    INIT_LIST_HEAD(&amp;wq_head-&gt;head);<br>&#125;<br></code></pre></td></tr></table></figure><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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DECLARE_WAIT_QUEUE_HEAD(name)                       \</span><br><span class="hljs-meta">    struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __WAIT_QUEUE_HEAD_INITIALIZER(name) &#123;               \</span><br><span class="hljs-meta">    .lock       = __SPIN_LOCK_UNLOCKED(name.lock),          \</span><br><span class="hljs-meta">    .head       = &#123; &amp;(name).head, &amp;(name).head &#125; &#125;</span><br></code></pre></td></tr></table></figure><h3 id="四、等待队列元素的创建和初始化"><a href="#四、等待队列元素的创建和初始化" class="headerlink" title="四、等待队列元素的创建和初始化"></a>四、等待队列元素的创建和初始化</h3><p>创建等待队列元素较为普遍的一种方式是调用宏定义 DECLARE_WAITQUEUE(name, task) ，将定义一个名为 name 的等待队列元素， private 数据指向给定的关联进程结构体 task ，唤醒函数为 default_wake_function() 。后文介绍唤醒细节时详细介绍唤醒函数的工作。</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DECLARE_WAITQUEUE(name, tsk)                        \</span><br><span class="hljs-meta">    struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __WAITQUEUE_INITIALIZER(name, tsk) &#123;                \</span><br><span class="hljs-meta">    .private    = tsk,                                      \</span><br><span class="hljs-meta">    .func       = default_wake_function,                    \</span><br><span class="hljs-meta">    .entry      = &#123; NULL, NULL &#125; &#125;</span><br></code></pre></td></tr></table></figure><p>内核源码中还存在其他定义等待队列元素的方式，调用宏定义 DEFINE_WAIT(name) 和 init_wait(&amp;wait_queue) 。</p><p>这两种方式都将当前进程(current)关联到所定义的等待队列上，唤醒函数为 autoremove_wake_function() ，注意此函数与上述宏定义方式时不同（上述定义中使用 default_wake_function() ）。</p><p>下文也将介绍 DEFINE_WAIT() 和 DECLARE_WAITQUEUE() 在使用场合上的不同。</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DEFINE_WAIT(name)   DEFINE_WAIT_FUNC(name, autoremove_wake_function)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> DEFINE_WAIT_FUNC(name, function)                    \</span><br><span class="hljs-meta">    struct wait_queue_entry name = &#123;                        \</span><br><span class="hljs-meta">        .private    = current,                              \</span><br><span class="hljs-meta">        .func       = function,                             \</span><br><span class="hljs-meta">        .entry      = LIST_HEAD_INIT((name).entry),         \</span><br><span class="hljs-meta">    &#125;</span><br></code></pre></td></tr></table></figure><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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> init_wait(wait)                                     \</span><br><span class="hljs-meta">    do &#123;                                                    \</span><br><span class="hljs-meta">        (wait)-&gt;private = current;                          \</span><br><span class="hljs-meta">        (wait)-&gt;func = autoremove_wake_function;            \</span><br><span class="hljs-meta">        INIT_LIST_HEAD(&amp;(wait)-&gt;entry);                     \</span><br><span class="hljs-meta">        (wait)-&gt;flags = 0;                                  \</span><br><span class="hljs-meta">    &#125; while (0)</span><br></code></pre></td></tr></table></figure><h3 id="五、添加和移除等待队列"><a href="#五、添加和移除等待队列" class="headerlink" title="五、添加和移除等待队列"></a>五、添加和移除等待队列</h3><p>内核提供了两个函数（定义在 kernel&#x2F;sched&#x2F;wait.c ）用于将等待队列元素 wq_entry 添加到等待队列 wq_head 中： add_wait_queue() 和 add_wait_queue_exclusive() 。</p><p> add_wait_queue() ：在等待队列头部添加普通的等待队列元素（非独占等待，清除 WQ_FLAG_EXCLUSIVE 标志）。</p><p> add_wait_queue_exclusive() ：在等待队列尾部添加独占等待队列元素（设置了 WQ_FLAG_EXCLUSIVE 标志）。</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><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">add_wait_queue</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)</span><br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    <span class="hljs-comment">// 清除WQ_FLAG_EXCLUSIVE标志</span><br>    wq_entry-&gt;flags &amp;= ~WQ_FLAG_EXCLUSIVE;<br>    spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>    __add_wait_queue(wq_head, wq_entry);<br>    spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>&#125;   <br><br><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> __add_wait_queue(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)<br>&#123;<br>    list_add(&amp;wq_entry-&gt;entry, &amp;wq_head-&gt;head);<br>&#125;<br></code></pre></td></tr></table></figure><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><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">add_wait_queue_exclusive</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)</span><br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    <span class="hljs-comment">// 设置WQ_FLAG_EXCLUSIVE标志</span><br>    wq_entry-&gt;flags |= WQ_FLAG_EXCLUSIVE;<br>    spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>    __add_wait_queue_entry_tail(wq_head, wq_entry);<br>    spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>&#125;<br><br><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> __add_wait_queue_entry_tail(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)<br>&#123;<br>    list_add_tail(&amp;wq_entry-&gt;entry, &amp;wq_head-&gt;head);<br>&#125;<br></code></pre></td></tr></table></figure><p>remove_wait_queue() 函数用于将等待队列元素 wq_entry 从等待队列 wq_head 中移除。</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><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">remove_wait_queue</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)</span><br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>    __remove_wait_queue(wq_head, wq_entry);<br>    spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>&#125;<br><br><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span><br>__remove_wait_queue(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)<br>&#123;<br>    list_del(&amp;wq_entry-&gt;entry);<br>&#125;<br></code></pre></td></tr></table></figure><p>添加和移除等待队列的示意图如下所示：</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blogblog2f3220332bf3f6b8af6038fe1003a5c8.png"></p><h3 id="六、等待事件"><a href="#六、等待事件" class="headerlink" title="六、等待事件"></a>六、等待事件</h3><p>内核中提供了等待事件 wait_event() 宏（以及它的几个变种），可用于实现简单的进程休眠，等待直至某个条件成立，主要包括如下几个定义：</p><p>wait_event(wq_head, condition)<br>wait_event_timeout(wq_head, condition, timeout)<br>wait_event_interruptible(wq_head, condition)<br>wait_event_interruptible_timeout(wq_head, condition, timeout)<br>io_wait_event(wq_head, condition)</p><p>上述所有形式函数中， wq_head 是等待队列头（采用”值传递“的方式传输函数）， condition 是任意一个布尔表达式。使用 wait_event ，进程将被置于非中断休眠，而使用 wait_event_interruptible 时，进程可以被信号中断。</p><p>另外两个版本 wait_event_timeout 和 wait_event_interruptible_timeout 会使进程只等待限定的时间（以jiffy表示，给定时间到期时，宏均会返回0，而无论 condition 为何值）。</p><p>详细介绍 wait_event() 函数的实现原理。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> wait_event(wq_head, condition)                      \</span><br><span class="hljs-meta">    do &#123;                                                    \</span><br><span class="hljs-meta">        might_sleep();                                      \</span><br><span class="hljs-meta">        <span class="hljs-comment">// 如果condition满足，提前返回                       \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        if (condition)                                      \</span></span><br><span class="hljs-comment"><span class="hljs-meta">           break;                                           \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        __wait_event(wq_head, condition);                   \</span></span><br><span class="hljs-comment"><span class="hljs-meta">    &#125; while (0)</span></span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __wait_event(wq_head, condition)                    \</span><br><span class="hljs-meta">     (void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, schedule())</span><br><br><span class="hljs-comment">/* 定义等待队列元素，并将元素加入到等待队列中</span><br><span class="hljs-comment"> * 循环判断等待条件condition是否满足，若条件满足，或者接收到中断信号，等待结束，函数返回</span><br><span class="hljs-comment"> * 若condition满足，返回0；否则返回-ERESTARTSYS</span><br><span class="hljs-comment"> */</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> ___wait_event(wq_head, condition, state, exclusive, ret, cmd)       \</span><br><span class="hljs-meta">(&#123;                                                          \</span><br><span class="hljs-meta">     __label__ __out;                                       \</span><br><span class="hljs-meta">     struct wait_queue_entry __wq_entry;                    \</span><br><span class="hljs-meta">     long __ret = ret;          <span class="hljs-comment">/* explicit shadow */</span>       \</span><br><span class="hljs-meta">                                                            \</span><br><span class="hljs-meta">     <span class="hljs-comment">// 初始化等待队列元素__wq_entry，关联当前进程，根据exclusive参数初始化属性标志 \</span></span><br><span class="hljs-comment"><span class="hljs-meta">     // 唤醒函数为autoremove_wake_function()                                        \</span></span><br><span class="hljs-comment"><span class="hljs-meta">     init_wait_entry(&amp;__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);    \</span></span><br><span class="hljs-comment"><span class="hljs-meta">     // 等待事件循环                                        \</span></span><br><span class="hljs-comment"><span class="hljs-meta">     for (;;) &#123;                                             \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        // 如果进程可被信号中断并且刚好有信号挂起，返回-ERESTARTSYS     \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        // 否则，将等待队列元素加入等待队列，并且设置进程状态，返回0    \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        long __int = prepare_to_wait_event(&amp;wq_head, &amp;__wq_entry, state);\</span></span><br><span class="hljs-comment"><span class="hljs-meta">                                                           \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        // 当前进程让出调度器前，判断condition是否成立。若成立，提前结束，后续将返回0   \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        if (condition)                                      \</span></span><br><span class="hljs-comment"><span class="hljs-meta">            break;                                          \</span></span><br><span class="hljs-comment"><span class="hljs-meta">                                                            \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        // 当前进程让出调度器前，判断当前进程是否接收到中断信号（或KILL信号）       \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        // 如果成立，将提前返回-ERESTARTSYS                 \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        if (___wait_is_interruptible(state) &amp;&amp; __int) &#123;     \</span></span><br><span class="hljs-comment"><span class="hljs-meta">            __ret = __int;                                  \</span></span><br><span class="hljs-comment"><span class="hljs-meta">            goto __out;                                     \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        &#125;                                                   \</span></span><br><span class="hljs-comment"><span class="hljs-meta">                                                           \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        // 此处实际执行schedule()，当前进程让出调度器       \</span></span><br><span class="hljs-comment"><span class="hljs-meta">        cmd;                                                \</span></span><br><span class="hljs-comment"><span class="hljs-meta">     &#125;                                                      \</span></span><br><span class="hljs-comment"><span class="hljs-meta"></span></span><br>     <span class="hljs-comment">// 设置进程为可运行状态，并且将等待队列元素从等待队列中删除    \</span><br><span class="hljs-comment">     finish_wait(&amp;wq_head, &amp;__wq_entry);                    \</span><br><span class="hljs-comment">     __out:  __ret;                                         \</span><br><span class="hljs-comment">&#125;)  </span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">init_wait_entry</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry, <span class="hljs-type">int</span> flags)</span> <br>&#123;<br>    wq_entry-&gt;flags = flags;<br>    wq_entry-&gt;private = current;<br>    wq_entry-&gt;func = autoremove_wake_function;<br>    INIT_LIST_HEAD(&amp;wq_entry-&gt;entry);<br>&#125;<br><br><span class="hljs-type">long</span> <span class="hljs-title function_">prepare_to_wait_event</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry, <span class="hljs-type">int</span> state)</span><br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    <span class="hljs-type">long</span> ret = <span class="hljs-number">0</span>;<br>    spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>    <span class="hljs-comment">// 返回非0值条件：可被信号中断并且确实有信号挂起</span><br>    <span class="hljs-keyword">if</span> (signal_pending_state(state, current)) &#123;<br>        <span class="hljs-comment">// 将等待队列元素从等待队列中删除，返回-ERESTARTSYS</span><br>        list_del_init(&amp;wq_entry-&gt;entry);<br>        ret = -ERESTARTSYS;<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>        <span class="hljs-comment">// 判断wq_entry-&gt;entry是否为空，即等待队列元素是否已经被添加到等待队列中</span><br>        <span class="hljs-keyword">if</span> (list_empty(&amp;wq_entry-&gt;entry)) &#123;<br>            <span class="hljs-comment">// WQ_FLAG_EXCLUSIVE标志被设置时，将等待队列元素添加到等待队列尾部（独占等待）</span><br>            <span class="hljs-comment">// 否则，将等待队列元素添加到等待队列头部。同2.1中对WQ_FLAG_EXCLUSIVE标志介绍。</span><br>            <span class="hljs-keyword">if</span> (wq_entry-&gt;flags &amp; WQ_FLAG_EXCLUSIVE)<br>                __add_wait_queue_entry_tail(wq_head, wq_entry);<br>            <span class="hljs-keyword">else</span><br>                __add_wait_queue(wq_head, wq_entry);<br>        &#125;<br>        <span class="hljs-comment">// 改变当前进程的状态</span><br>        set_current_state(state);<br>    &#125;<br>    spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>    <span class="hljs-keyword">return</span> ret;<br>&#125;<br><br><span class="hljs-comment">// 用state_value改变当前的进程状态，并且执行了一次内存屏障</span><br><span class="hljs-comment">// 注意，只是改变了调度器处理该进程的方式，但尚未使该进程让出处理器</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> set_current_state(state_value)              \</span><br><span class="hljs-meta">    do &#123;                            \</span><br><span class="hljs-meta">        WARN_ON_ONCE(is_special_task_state(state_value));\</span><br><span class="hljs-meta">        current-&gt;task_state_change = _THIS_IP_;     \</span><br><span class="hljs-meta">        smp_store_mb(current-&gt;state, (state_value));    \</span><br><span class="hljs-meta">    &#125; while (0)</span><br><br><span class="hljs-comment">/*  设置进程为可运行状态，并且将等待队列元素从等待队列中删除  */</span><br><span class="hljs-type">void</span> <span class="hljs-title function_">finish_wait</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry)</span><br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    <span class="hljs-comment">// 将当前进程状态改为可运行状态(TASK_RUNNING)</span><br>    <span class="hljs-comment">// 类似set_current_state()，差别在于未进行内存屏障</span><br>    __set_current_state(TASK_RUNNING);<br>    <span class="hljs-comment">// 等待队列元素若仍在等待队列中，则将其删除</span><br>    <span class="hljs-keyword">if</span> (!list_empty_careful(&amp;wq_entry-&gt;entry)) &#123;<br>        spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>        list_del_init(&amp;wq_entry-&gt;entry);<br>        spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>经过源码分析可以看到， wait_event 使进程进入非中断休眠状态，循环等待直至特定条件满足，否则进程继续保持休眠状态。</p><p>可以简单总结出使用等待队列使进程休眠的一般步骤：</p><ul><li>将当前进程关联的等待队列元素加入到等待队列中。 __add_wait_queue()&#x2F;__add_wait_queue_entry_tail()</li><li>设置当前进程状态（可中断 TASK_INTERRUPTIBLE 或不可中断 TASK_UNINTERRUPTIBLE）。 set_current_state()</li><li>判断资源是否得到，或是否捕获中断信号。</li><li>进程让出调度器，进入休眠状态。 schedule()</li><li>资源得到满足时，将等待队列元素从等待队列中移除。</li></ul><h3 id="七、等待队列唤醒"><a href="#七、等待队列唤醒" class="headerlink" title="七、等待队列唤醒"></a>七、等待队列唤醒</h3><p>前文已经简单提到， wake_up 函数可用于将等待队列上的所有进程唤醒，和 wait_event 相对应， wake_up 函数也包括多个变体。主要包括：</p><p>wake_up(&amp;wq_head)<br>wake_up_interruptible(&amp;wq_head)<br>wake_up_nr(&amp;wq_head, nr)<br>wake_up_interruptible_nr(&amp;wq_head, nr)<br>wake_up_interruptible_all(&amp;wq_head)</p><h4 id="wake-up"><a href="#wake-up" class="headerlink" title="wake_up()"></a>wake_up()</h4><p> wake_up() 可以用来唤醒等待队列上的所有进程，而 wake_up_interruptible() 只会唤醒那些执行可中断休眠的进程。因此约定， wait_event() 和 wake_up() 搭配使用，而 wait_event_interruptible() 和 wake_up_interruptible() 搭配使用。</p><p>前文提到，对于独占等待的进程， wake_up() 只会唤醒第一个独占等待进程。 wake_up_nr() 函数提供功能，它能唤醒给定数目nr个独占等待进程，而不是只有一个。</p><p> wake_up() 函数的实现如下：</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> TASK_NORMAL         (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)</span><br><span class="hljs-comment">// 可以看出wake_up函数将唤醒TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的所有进程</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> wake_up(x)          __wake_up(x, TASK_NORMAL, 1, NULL)</span><br><span class="hljs-type">void</span> __wake_up(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> mode, <span class="hljs-type">int</span> nr_exclusive, <span class="hljs-type">void</span> *key)<br>&#123;<br>    __wake_up_common_lock(wq_head, mode, nr_exclusive, <span class="hljs-number">0</span>, key);<br>&#125;<br><br><span class="hljs-type">static</span> <span class="hljs-type">void</span> __wake_up_common_lock(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> mode,<br>        <span class="hljs-type">int</span> nr_exclusive, <span class="hljs-type">int</span> wake_flags, <span class="hljs-type">void</span> *key)<br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    <span class="hljs-type">wait_queue_entry_t</span> bookmark;<br>    bookmark.flags = <span class="hljs-number">0</span>;<br>    bookmark.private = <span class="hljs-literal">NULL</span>;<br>    bookmark.func = <span class="hljs-literal">NULL</span>;<br>    INIT_LIST_HEAD(&amp;bookmark.entry);<br>    <span class="hljs-comment">// 第一次尝试调用__wake_up_common()，如果需要进行BOOKMARK过程，bookmark.flags会被置为WQ_FLAG_BOOKMARK</span><br>    spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>    nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &amp;bookmark);<br>    spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>    <span class="hljs-comment">// 如果还有需要处理的元素，那么bookmark.flags肯定置上WQ_FLAG_BOOKMARK；否则，在一个loop内便处理完成</span><br>    <span class="hljs-keyword">while</span> (bookmark.flags &amp; WQ_FLAG_BOOKMARK) &#123;<br>        spin_lock_irqsave(&amp;wq_head-&gt;lock, flags);<br>        nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &amp;bookmark);<br>        spin_unlock_irqrestore(&amp;wq_head-&gt;lock, flags);<br>    &#125;<br>&#125;<br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> WAITQUEUE_WALK_BREAK_CNT 64</span><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> __wake_up_common(<span class="hljs-keyword">struct</span> wait_queue_head *wq_head, <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> mode,<br>        <span class="hljs-type">int</span> nr_exclusive, <span class="hljs-type">int</span> wake_flags, <span class="hljs-type">void</span> *key, <span class="hljs-type">wait_queue_entry_t</span> *bookmark)<br>&#123;<br>    <span class="hljs-type">wait_queue_entry_t</span> *curr, *next;<br>    <span class="hljs-type">int</span> cnt = <span class="hljs-number">0</span>;<br>    <span class="hljs-comment">// 判断自旋锁已经被持有</span><br>    lockdep_assert_held(&amp;wq_head-&gt;lock);<br>    <span class="hljs-comment">// 如果bookmark元素中标志`WQ_FLAG_BOOKMARK`已被设置，则curr被设置为bookmark下一个元素</span><br>    <span class="hljs-comment">// 同时将bookmark从等待队列中删除，bookmark-&gt;flags清零</span><br>    <span class="hljs-comment">// 否则，curr设置为等待队列wq_head的第一个元素（实际上为第一次调用__wake_up_common）</span><br>    <span class="hljs-keyword">if</span> (bookmark &amp;&amp; (bookmark-&gt;flags &amp; WQ_FLAG_BOOKMARK)) &#123;<br>        curr = list_next_entry(bookmark, entry);<br>        list_del(&amp;bookmark-&gt;entry);<br>        bookmark-&gt;flags = <span class="hljs-number">0</span>;<br>    &#125; <span class="hljs-keyword">else</span><br>        curr = list_first_entry(&amp;wq_head-&gt;head, <span class="hljs-type">wait_queue_entry_t</span>, entry);<br>    <span class="hljs-keyword">if</span> (&amp;curr-&gt;entry == &amp;wq_head-&gt;head)<br>        <span class="hljs-keyword">return</span> nr_exclusive;<br>    <span class="hljs-comment">// 在等待队列头指向的链表上，从curr指向的元素开始依次遍历元素</span><br>    list_for_each_entry_safe_from(curr, next, &amp;wq_head-&gt;head, entry) &#123;<br>        <span class="hljs-type">unsigned</span> flags = curr-&gt;flags;<br>        <span class="hljs-type">int</span> ret;<br>        <span class="hljs-comment">// 跳过标记为WQ_FLAG_BOOKMARK的元素，等待队列元素被置上WQ_FLAG_BOOKMARK？</span><br>        <span class="hljs-keyword">if</span> (flags &amp; WQ_FLAG_BOOKMARK)<br>            <span class="hljs-keyword">continue</span>;<br>        <span class="hljs-comment">// 调用等待队列元素绑定的唤醒回调函数</span><br>        <span class="hljs-comment">// 注意，具体唤醒何种进程(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)，作为参数传递给唤醒函数处理</span><br>        <span class="hljs-comment">// 当进程不符合唤醒条件时，ret为0，详见try_to_wake_up()</span><br>        ret = curr-&gt;func(curr, mode, wake_flags, key);<br>        <span class="hljs-keyword">if</span> (ret &lt; <span class="hljs-number">0</span>)<br>            <span class="hljs-keyword">break</span>;<br>        <span class="hljs-comment">// 如果当前等待队列元素为独占等待，并且独占等待个数已经等于nr_exclusive，提前退出循环</span><br>        <span class="hljs-comment">// 如2.1所述，独占等待进程被加入到等待队列的尾部，因此此时表明所有唤醒工作已经完成</span><br>        <span class="hljs-keyword">if</span> (ret &amp;&amp; (flags &amp; WQ_FLAG_EXCLUSIVE) &amp;&amp; !--nr_exclusive)<br>            <span class="hljs-keyword">break</span>;<br>        <span class="hljs-comment">// 连续唤醒的进程数目达到指定数目WAITQUEUE_WALK_BREAK_CNT（仍有进程元素需要处理），</span><br>        <span class="hljs-comment">// 标记bookmark-&gt;flags为WQ_FLAG_BOOKMARK，同时将下一个要处理的元素添加到bookmark作为头节点的链表尾部，并退出遍历循环</span><br>        <span class="hljs-comment">// 通过这种机制，实现了进程分批次唤醒，避免了等待队列中自旋锁被持有时间过长</span><br>        <span class="hljs-keyword">if</span> (bookmark &amp;&amp; (++cnt &gt; WAITQUEUE_WALK_BREAK_CNT) &amp;&amp;<br>                (&amp;next-&gt;entry != &amp;wq_head-&gt;head)) &#123;<br>            bookmark-&gt;flags = WQ_FLAG_BOOKMARK;<br>            list_add_tail(&amp;bookmark-&gt;entry, &amp;next-&gt;entry);<br>            <span class="hljs-keyword">break</span>;<br>        &#125;<br>    &#125;<br>    <span class="hljs-keyword">return</span> nr_exclusive;<br>&#125;<br></code></pre></td></tr></table></figure><p>wake_up() 函数会遍历等待队列上的所有元素（包括TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE)），根据 nr_exclusive 参数的要求唤醒进程，同时实现了分批次唤醒工作。最终会回调等待队列元素所绑定的唤醒函数。</p><p>前文已经提到，定义等待队列元素时主要涉及到两种唤醒回调函数：</p><ul><li><p>default_wake_function() ：宏定义 DECLARE_WAITQUEUE(name, tsk) 使用的唤醒函数。</p></li><li><p>autoremove_wake_function() ： DEFINE_WAIT(name) ， init_wait(wait) 和 wait_event() 中调用的 init_wait_entry() 使用此唤醒函数。</p></li></ul><h4 id="default-wake-function"><a href="#default-wake-function" class="headerlink" title="default_wake_function()"></a>default_wake_function()</h4><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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">default_wake_function</span><span class="hljs-params">(<span class="hljs-type">wait_queue_entry_t</span> *curr, <span class="hljs-type">unsigned</span> mode, <span class="hljs-type">int</span> wake_flags, <span class="hljs-type">void</span> *key)</span><br>&#123;<br>    <span class="hljs-keyword">return</span> try_to_wake_up(curr-&gt;private, mode, wake_flags);<br>&#125;<br><br><span class="hljs-type">static</span> <span class="hljs-type">int</span><br><span class="hljs-title function_">try_to_wake_up</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> task_struct *p, <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> state, <span class="hljs-type">int</span> wake_flags)</span><br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br>    <span class="hljs-type">int</span> cpu, success = <span class="hljs-number">0</span>;<br>    raw_spin_lock_irqsave(&amp;p-&gt;pi_lock, flags);<br>    smp_mb__after_spinlock();<br>    <span class="hljs-comment">// 此处对进程的状态进行筛选，跳过不符合状态的进程（TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE）</span><br>    <span class="hljs-keyword">if</span> (!(p-&gt;state &amp; state))<br>        <span class="hljs-keyword">goto</span> out;<br>    trace_sched_waking(p);<br>    <span class="hljs-comment">/* We&#x27;re going to change -&gt;state: */</span><br>    success = <span class="hljs-number">1</span>;<br>    cpu = task_cpu(p);<br>    smp_rmb();<br>    <span class="hljs-keyword">if</span> (p-&gt;on_rq &amp;&amp; ttwu_remote(p, wake_flags))<br>        <span class="hljs-keyword">goto</span> stat;<br>    ... ...<br>    <span class="hljs-comment">// Try-To-Wake-Up</span><br>    ttwu_queue(p, cpu, wake_flags);<br>stat:<br>    ttwu_stat(p, cpu, wake_flags);<br>out:<br>    raw_spin_unlock_irqrestore(&amp;p-&gt;pi_lock, flags);<br>    <span class="hljs-keyword">return</span> success;<br>&#125;<br><br><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title function_">ttwu_queue</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> task_struct *p, <span class="hljs-type">int</span> cpu, <span class="hljs-type">int</span> wake_flags)</span><br>&#123;<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rq</span> *<span class="hljs-title">rq</span> =</span> cpu_rq(cpu);<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">rq_flags</span> <span class="hljs-title">rf</span>;</span><br>    ... ...<br>    rq_lock(rq, &amp;rf);<br>    update_rq_clock(rq);<br>    ttwu_do_activate(rq, p, wake_flags, &amp;rf);<br>    rq_unlock(rq, &amp;rf);<br>&#125;<br><br><span class="hljs-type">static</span> <span class="hljs-type">void</span><br><span class="hljs-title function_">ttwu_do_activate</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rq *rq, <span class="hljs-keyword">struct</span> task_struct *p, <span class="hljs-type">int</span> wake_flags,</span><br><span class="hljs-params">        <span class="hljs-keyword">struct</span> rq_flags *rf)</span><br>&#123;<br>    <span class="hljs-type">int</span> en_flags = ENQUEUE_WAKEUP | ENQUEUE_NOCLOCK;<br>    lockdep_assert_held(&amp;rq-&gt;lock);<br>    ... ...<br>    activate_task(rq, p, en_flags);<br>    ttwu_do_wakeup(rq, p, wake_flags, rf);<br>&#125;<br><br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Mark the task runnable and perform wakeup-preemption.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title function_">ttwu_do_wakeup</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> rq *rq, <span class="hljs-keyword">struct</span> task_struct *p, <span class="hljs-type">int</span> wake_flags,</span><br><span class="hljs-params">        <span class="hljs-keyword">struct</span> rq_flags *rf)</span><br>&#123;<br>    check_preempt_curr(rq, p, wake_flags);<br>    p-&gt;state = TASK_RUNNING;<br>    trace_sched_wakeup(p);<br>    ... ...<br>&#125;<br></code></pre></td></tr></table></figure><p>从函数调用过程中可以看到， default_wake_function() 实现唤醒进程的过程为：</p><p>default_wake_function() –&gt; try_to_wake_up() –&gt; ttwu_queue() –&gt; ttwu_do_activate() –&gt; ttwu_do_wakeup()</p><p>值得一提的是， default_wake_function() 的实现中并未将等待队列元素从等待队列中删除。因此，编写程序时不能忘记添加步骤将等待队列元素从等待队列元素中删除。</p><h4 id="autoremove-wake-function"><a href="#autoremove-wake-function" class="headerlink" title="autoremove_wake_function()"></a>autoremove_wake_function()</h4><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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">autoremove_wake_function</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> wait_queue_entry *wq_entry, <span class="hljs-type">unsigned</span> mode, <span class="hljs-type">int</span> sync, <span class="hljs-type">void</span> *key)</span><br>&#123;<br>    <span class="hljs-type">int</span> ret = default_wake_function(wq_entry, mode, sync, key);<br>    <span class="hljs-keyword">if</span> (ret)<br>        list_del_init(&amp;wq_entry-&gt;entry);<br>    <span class="hljs-keyword">return</span> ret;<br>&#125;<br></code></pre></td></tr></table></figure><p>autoremove_wake_function() 相比于 default_wake_function() ，在成功执行进程唤醒工作后，会自动将等待队列元素从等待队列中移除。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 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;/h3&gt;&lt;p&gt;Linux内核的等待队列（Wait Queue）是重</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>module_init宏详解&amp;insmod流程&amp;THIS_MODULE</title>
    <link href="http://example.com/posts/module-init%E5%AE%8F%E8%AF%A6%E8%A7%A3-insmod%E6%B5%81%E7%A8%8B-THIS-MODULE.html"/>
    <id>http://example.com/posts/module-init%E5%AE%8F%E8%AF%A6%E8%A7%A3-insmod%E6%B5%81%E7%A8%8B-THIS-MODULE.html</id>
    <published>2024-11-25T15:19:20.000Z</published>
    <updated>2024-11-25T15:19:39.596Z</updated>
    
    <content type="html"><![CDATA[<p>一个模块可以编译进内核，和内核一起打包，也可以作为一个单独模块，单独加载，如ko，本节以ko为例</p><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><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><code class="hljs c"><span class="hljs-comment">// //helloworld.c</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/kernel.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/module.h&gt;</span></span><br><br><span class="hljs-comment">//内核模块初始化函数</span><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> __init <span class="hljs-title function_">hello_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>  printk(<span class="hljs-string">&quot;Hello World\n&quot;</span>);<br>  <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>&#125;<br><br><span class="hljs-comment">//内核模块退出函数</span><br><span class="hljs-type">static</span> <span class="hljs-type">void</span> __exit <span class="hljs-title function_">hello_exit</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>  printk(<span class="hljs-string">&quot;exit\n&quot;</span>);<br>&#125;<br><br>module_init(hello_init);<br>module_exit(hello_exit);<br><br>MODULE_LICENSE(<span class="hljs-string">&quot;GPL&quot;</span>);<br></code></pre></td></tr></table></figure><h2 id="一、一个特殊的段-gnu-linkonce-this-module-section-THIS-MODULE定义"><a href="#一、一个特殊的段-gnu-linkonce-this-module-section-THIS-MODULE定义" class="headerlink" title="一、一个特殊的段.gnu.linkonce.this_module section(THIS_MODULE定义)"></a>一、一个特殊的段.gnu.linkonce.this_module section(THIS_MODULE定义)</h2><p>当我们编译生成.ko模块时，会在该目录下产生这样一个附加文件helloworld.mod.c，这个文件内容如下：<br>其实THIS_MODULE在内核的定义就是__this_module，通过这个宏就能拿到本模块的一些信息</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><code class="hljs c"><span class="hljs-comment">// helloworld.mod.c</span><br><br>__visible <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">module</span> __<span class="hljs-title">this_module</span></span><br><span class="hljs-class">__<span class="hljs-title">section</span>(&quot;.<span class="hljs-title">gnu</span>.<span class="hljs-title">linkonce</span>.<span class="hljs-title">this_module</span>&quot;) =</span> &#123;<br>  .name = KBUILD_MODNAME,<br>  .init = init_module,<br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> CONFIG_MODULE_UNLOAD</span><br>  .<span class="hljs-built_in">exit</span> = cleanup_module,<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>  .arch = MODULE_ARCH_INIT,<br>&#125;;<br></code></pre></td></tr></table></figure><p>这个后缀为.mod.c的文件会初始化模块 struct module 的这几个成员：</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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">module</span> &#123;</span><br>  ......<br>  <span class="hljs-comment">/* Unique handle for this module */</span><br>  <span class="hljs-type">char</span> name[MODULE_NAME_LEN];<br><br>  <span class="hljs-comment">/* Startup function. */</span><br>  <span class="hljs-type">int</span> (*init)(<span class="hljs-type">void</span>);<br><br>  <span class="hljs-comment">/* Destruction function. */</span><br>  <span class="hljs-type">void</span> (*<span class="hljs-built_in">exit</span>)(<span class="hljs-type">void</span>);<br><br>  <span class="hljs-comment">/* Arch-specific module values */</span><br>  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">mod_arch_specific</span> <span class="hljs-title">arch</span>;</span><br>  ......<br>&#125;<br></code></pre></td></tr></table></figure><p>通过__section(“.gnu.linkonce.this_module”)我们可以知道将该struct module实例添加到内核模块ELF文件中“.gnu.linkonce.this_module”section中。</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sh">readelf -S helloworld.ko<br></code></pre></td></tr></table></figure><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog1ab338456ff54936a8c8e4851bd837f8.png"></p><h2 id="二、初始化和清理函数"><a href="#二、初始化和清理函数" class="headerlink" title="二、初始化和清理函数"></a>二、初始化和清理函数</h2><h3 id="初始化和清理函数"><a href="#初始化和清理函数" class="headerlink" title="初始化和清理函数"></a>初始化和清理函数</h3><p>在这里我们发现.mod.c后缀文件的模块初始化函数和清理函数名字是init_module和cleanup_module，而我们编写的内核模块程序中是module_init和module_exit：</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><code class="hljs c">.init = init_module,<br>.<span class="hljs-built_in">exit</span> = cleanup_module,<br></code></pre></td></tr></table></figure><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><code class="hljs c">module_init(hello_init);<br>module_exit(hello_exit);<br></code></pre></td></tr></table></figure><p>接下来咱们一起看看module_init源码实现细节：</p><h2 id="三、module-init的定义"><a href="#三、module-init的定义" class="headerlink" title="三、module_init的定义"></a>三、module_init的定义</h2><p>其实module_init在内核中&lt;linux&#x2F;module.h&gt;中有两个定义，通过MODULE宏来区分，在咱们编译ko时，使用的命令通常如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">make -C $(KDIR) M=$(PWD) modules<br></code></pre></td></tr></table></figure><p>这时会进入内核源码树的顶层Makefile下查找modules目标协助编译，MODULE宏的定义是由内核顶层的Makefile和script&#x2F;Makefile.lib配合实现的</p><p>Makefile</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><code class="hljs c">KBUILD_AFLAGS_MODULE  := -DMODULE<br>KBUILD_CFLAGS_MODULE  := -DMODULE<br></code></pre></td></tr></table></figure><p>scripts&#x2F;Makefile.lib</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><code class="hljs c">part-of-module = $(<span class="hljs-keyword">if</span> $(filter $(basename $@).o, $(real-obj-m)),y)<br>quiet_modtag = $(<span class="hljs-keyword">if</span> $(part-of-module),[M],   )<br>modkern_cflags =                                          \<br>       $(<span class="hljs-keyword">if</span> $(part-of-module),                           \<br>                $(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \<br>                $(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags))<br></code></pre></td></tr></table></figure><p>$@ 表示正在编译的目标，如果是 module 的一部分，则使用 KBUILD_CFLAGS_MODULE 作为 cflags ，即 -DMODULE 被引入 gcc 命令行</p><p>此时module_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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// linux-4.10.1/include/linux/module.h</span><br><br><span class="hljs-comment">/* Each module must use one module_init(). */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> module_init(initfn)          \</span><br><span class="hljs-meta">  static inline initcall_t __inittest(void)\</span><br><span class="hljs-meta">  &#123; return initfn; &#125;\</span><br><span class="hljs-meta">  int init_module(void) __attribute__((alias(#initfn)));</span><br><br><span class="hljs-comment">/* This is only required if you want to be unloadable. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> module_exit(exitfn)          \</span><br><span class="hljs-meta">  static inline exitcall_t __exittest(void)\</span><br><span class="hljs-meta">  &#123; return exitfn; &#125;\</span><br><span class="hljs-meta">  void cleanup_module(void) __attribute__((alias(#exitfn)));</span><br><br></code></pre></td></tr></table></figure><p>这使用了gcc提供的别名属性（<strong>attribute</strong>(alias)）：<br />将init_module函数的别名设置为initfn，而initfn就是我们模块程序中的初始化函数。<br />将cleanup_module的别名设置为exitfn，而exitfn就是我们模块程序的清理函数。</p><h2 id="四、-init函数和-exit函数"><a href="#四、-init函数和-exit函数" class="headerlink" title="四、__init函数和__exit函数"></a>四、__init函数和__exit函数</h2><p>定义如下：</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> __init    __section(.init.text) __cold notrace __latent_entropy</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __exit      __section(.exit.text) __exitused __cold notrace</span><br></code></pre></td></tr></table></figure><p>这两个前缀是为了将初始化函数和清理函数放置在ko二进制文件正确的section中。</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog92d67e5ad6f04ae9a35d6b000d7762db.png"></p><h2 id="五、insmod流程"><a href="#五、insmod流程" class="headerlink" title="五、insmod流程"></a>五、insmod流程</h2><p>insmod对应的是busybox的一个命令，可以看busybox中的insmod_main函数中的具体实现，这里简单描述一下：</p><p>insmod将ko文件使用mmap映射，获取到地址，然后通过系统调用SYSCALL_DEFINE3进入内核</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><code class="hljs c">SYSCALL_DEFINE3(init_module)<br>  --&gt;load_module()<br>    --&gt;layout_and_allocate()<br>      --&gt;setup_load_info()<br></code></pre></td></tr></table></figure><p>load_module中会把.gnu.linkonce.this_module section中的内容初始化给 struct module，然后通过一些列操作，重定位等，获取到.init_text中的位置，最后调用进去，也就是调用到了module_init中的函数，也就是本例中的hello_init。至此ko加载成功，hello_init函数执行</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;一个模块可以编译进内核，和内核一起打包，也可以作为一个单独模块，单独加载，如ko，本节以ko为例&lt;/p&gt;
&lt;p&gt;以最简单的例子讲解：&lt;/p&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;sp</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>Linux内核的initcall机制</title>
    <link href="http://example.com/posts/Linux%E5%86%85%E6%A0%B8%E7%9A%84initcall%E6%9C%BA%E5%88%B6.html"/>
    <id>http://example.com/posts/Linux%E5%86%85%E6%A0%B8%E7%9A%84initcall%E6%9C%BA%E5%88%B6.html</id>
    <published>2024-11-25T15:17:08.000Z</published>
    <updated>2024-11-25T15:17:37.868Z</updated>
    
    <content type="html"><![CDATA[<p>从根文件系统的挂载说起吧，使能了initd小文件系统的OS，最终会调用到populate_rootfs执行解压挂载的操作，搜索populate最终看到是rootfs_initcall被执行到的，那就从此处开始说起吧，看看内核是如何执行到这个函数的</p><h2 id="一、initcall函数调用流程"><a href="#一、initcall函数调用流程" class="headerlink" title="一、initcall函数调用流程"></a>一、initcall函数调用流程</h2><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></pre></td><td class="code"><pre><code class="hljs c">init/main.c<br>调用链如下：<br> <br>start_kernel()<br>    ----&gt;arch_call_rest_init()<br>        ----&gt;rest_init()<br>            ----&gt;kernel_init()<br>                ----&gt;kernel_init_freeable()<br>                    ----&gt;do_basic_setup()<br>                        ----&gt;do_initcalls()<br></code></pre></td></tr></table></figure><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><code class="hljs c"> <br><span class="hljs-type">static</span> <span class="hljs-type">initcall_entry_t</span> *initcall_levels[] __initdata = &#123;<br>  __initcall0_start,<br>  __initcall1_start,<br>  __initcall2_start,<br>  __initcall3_start,<br>  __initcall4_start,<br>  __initcall5_start,<br>  __initcall6_start,<br>  __initcall7_start,<br>  __initcall_end,<br>&#125;;<br> <br><span class="hljs-type">static</span> <span class="hljs-type">void</span> __init <span class="hljs-title function_">do_initcalls</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>  <span class="hljs-type">int</span> level;<br> <br>  <span class="hljs-keyword">for</span> (level = <span class="hljs-number">0</span>; level &lt; ARRAY_SIZE(initcall_levels) - <span class="hljs-number">1</span>; level++)<br>    do_initcall_level(level);<br>&#125;<br></code></pre></td></tr></table></figure><p>for 循环针对一个指针数组轮询，该数组是一个静态的 initcall_entry_t 类型，这些数据都存放在 __initdata 段。</p><p>指针数组的类型为 initcall_entry_t，是 initcall_t 的另一种叫法</p><p>继续来看下这个指针数组中的元素：__initcall0_start ~ __initcall_end，而这些元素的值在本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></pre></td><td class="code"><pre><code class="hljs c">init/main.c<br> <br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall0_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall1_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall2_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall3_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall4_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall5_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall6_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall7_start[];<br><span class="hljs-keyword">extern</span> <span class="hljs-type">initcall_entry_t</span> __initcall_end[];<br></code></pre></td></tr></table></figure><p>不难看出，数组 initcall_levels 中元素存放的是这些函数指针数组的首地址。</p><p>那么这些实际的指针数组是在哪里呢？从上文得知，initcall 函数都会被定义成 static initcall_t 类型，并且保存在 .initcall##level##.init 段中，那么 initcall_levels 与其是怎么关联的呢？</p><p>答案在 vmlinux.lds.h 中。</p><h2 id="二、vmlinux-lds-h"><a href="#二、vmlinux-lds-h" class="headerlink" title="二、vmlinux.lds.h"></a>二、vmlinux.lds.h</h2><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><code class="hljs c">include/<span class="hljs-keyword">asm</span>-generic/vmlinux.lds.h<br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> INIT_CALLS_LEVEL(level)            \</span><br><span class="hljs-meta">    __initcall##level##_start = .;\</span><br><span class="hljs-meta">    KEEP(*(.initcall##level##.init))\</span><br><span class="hljs-meta">    KEEP(*(.initcall##level##s.init))\</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> INIT_CALLS              \</span><br><span class="hljs-meta">    __initcall_start = .;\</span><br><span class="hljs-meta">    KEEP(*(.initcallearly.init))\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(0)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(1)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(2)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(3)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(4)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(5)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(rootfs)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(6)\</span><br><span class="hljs-meta">    INIT_CALLS_LEVEL(7)\</span><br><span class="hljs-meta">    __initcall_end = .;</span><br></code></pre></td></tr></table></figure><p>在这里首先定义了__initcall_start，将其关联到”.initcallearly.init”段。</p><p>然后对每个level定义了INIT_CALLS_LEVEL(level)，将INIT_CALLS_LEVEL(level)展开之后的结果是定义__initcall##level##_start，并将__initcall##level##_start关联到”.initcall##level##.init”段和”.initcall##level##s.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></pre></td><td class="code"><pre><code class="hljs c">__initcall_start = .;           \<br>*(.initcallearly.init)          \<br>__initcall0_start = .;          \<br>*(.initcall0.init)              \<br>*(.initcall0s.init)             \<br><span class="hljs-comment">// 省略1、2、3、4、5</span><br>__initcallrootfs_start = .;     \<br>*(.initcallrootfs.init)         \<br>*(.initcallrootfss.init)            \<br>__initcall6_start = .;          \<br>*(.initcall6.init)              \<br>*(.initcall6s.init)             \<br>__initcall7_start = .;          \<br>*(.initcall7.init)              \<br>*(.initcall7s.init)             \<br>__initcall_end = .;<br></code></pre></td></tr></table></figure><p>上面这些代码段最终在kernel.img中按先后顺序组织，也就决定了位于其中的一些函数的执行先后顺序。</p><p>在 kernel_init() 初始化完成后，会调用 free_initmem() 对内核 init 段的内存进行释放处理。即 .init 或者 .initcalls 段在内核启动完毕后，这个段中的内存会被释放掉。</p><h2 id="三、do-initcall-level"><a href="#三、do-initcall-level" class="headerlink" title="三、do_initcall_level"></a>三、do_initcall_level</h2><p>do_initcall_level函数定义如下：</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><code class="hljs c">init/main.c<br> <br><span class="hljs-type">static</span> <span class="hljs-type">void</span> __init <span class="hljs-title function_">do_initcall_level</span><span class="hljs-params">(<span class="hljs-type">int</span> level)</span><br>&#123;<br>  <span class="hljs-type">initcall_entry_t</span> *fn;<br> <br>  <span class="hljs-built_in">strcpy</span>(initcall_command_line, saved_command_line);<br>  parse_args(initcall_level_names[level],<br>       initcall_command_line, __start___param,<br>       __stop___param - __start___param,<br>       level, level,<br>       <span class="hljs-literal">NULL</span>, &amp;repair_env_string);<br> <br>  trace_initcall_level(initcall_level_names[level]);<br>  <span class="hljs-keyword">for</span> (fn = initcall_levels[level]; fn &lt; initcall_levels[level+<span class="hljs-number">1</span>]; fn++)<br>    do_one_initcall(initcall_from_entry(fn));<br>&#125;<br></code></pre></td></tr></table></figure><p>do_initcall_level() 函数的参数 level 是之前是initcall_levels 数组的索引，从第0个开始。</p><p>这里再用一个 for 循环，跳到 initcall_levels 内部元素 (函数指针数组)进行轮询，fn 初始值为函数指针数组的起始地址，后面 fn++ 相当于函数指针 +1，即跳到下一个函数指针。</p><p>即for 循环中，根据传入的 level，确定需要轮询的 .initcall##level##.init 段的所有函数指针，一直到下一个 .intcall##(level+1)##.init 段。</p><p>另外，需要注意 do_one_initcall() 的参数就是获取函数指针的内容，而这个内容就是注册进来的 initcall 的实际初始化函数。</p><p>如上面的举例：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">rootfs_initcall(populate_rootfs);<br></code></pre></td></tr></table></figure><p>这里最终就看成调用 do_one_initcall(populate_rootfs)；</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><code class="hljs c">init/main.c<br> <br><span class="hljs-type">int</span> __init_or_module <span class="hljs-title function_">do_one_initcall</span><span class="hljs-params">(<span class="hljs-type">initcall_t</span> fn)</span><br>&#123;<br>  ...<br><br>  ret = fn();<br>  ...<br>  <span class="hljs-keyword">return</span> ret;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="四、initcall源码"><a href="#四、initcall源码" class="headerlink" title="四、initcall源码"></a>四、initcall源码</h2><p>上文提到过 Linux 对驱动程序提供静态编译进内核和动态加载两种方式，Linux 的 initcall 机制也是根据静态编译和动态加载的两种方式选择不同的编译、运行流程。</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><code class="hljs c">include/linux/init.h<br> <br><span class="hljs-meta">#<span class="hljs-keyword">ifndef</span> MODULE</span><br> <br>... <span class="hljs-comment">//静态加载</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br> <br>... <span class="hljs-comment">//动态加载</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br></code></pre></td></tr></table></figure><p>MODULE 是在编译的时候，通过编译器参数来传入。例如，在编译 ko 时会使用如下两个编译选项，如果是链接到内核，则不会使用：</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><code class="hljs c"><span class="hljs-comment">//Makefile</span><br> <br>KBUILD_AFLAGS_MODULE  := -DMODULE<br>KBUILD_CFLAGS_MODULE  := -DMODULE<br></code></pre></td></tr></table></figure><p>通过 MODULE 的配置，选择静态编译还是动态加载。</p><p>本文将只分析静态编译流程</p><p>内核中定义的initcall如下：</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></pre></td><td class="code"><pre><code class="hljs c">include/linux/init.h<br> <br> <br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * Early initcalls run before initializing SMP.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * Only for built-in code, not modules.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> early_initcall(fn)    __define_initcall(fn, early)</span><br> <br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * A &quot;pure&quot; initcall has no dependencies on anything else, and purely</span><br><span class="hljs-comment"> * initializes variables that couldn&#x27;t be statically initialized.</span><br><span class="hljs-comment"> *</span><br><span class="hljs-comment"> * This only exists for built-in code, not for modules.</span><br><span class="hljs-comment"> * Keep main.c:initcall_level_names[] in sync.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> pure_initcall(fn)    __define_initcall(fn, 0)</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> core_initcall(fn)    __define_initcall(fn, 1)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> core_initcall_sync(fn)    __define_initcall(fn, 1s)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> postcore_initcall(fn)    __define_initcall(fn, 2)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> postcore_initcall_sync(fn)  __define_initcall(fn, 2s)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> arch_initcall(fn)    __define_initcall(fn, 3)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> arch_initcall_sync(fn)    __define_initcall(fn, 3s)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> subsys_initcall(fn)    __define_initcall(fn, 4)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> subsys_initcall_sync(fn)  __define_initcall(fn, 4s)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> fs_initcall(fn)      __define_initcall(fn, 5)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> fs_initcall_sync(fn)    __define_initcall(fn, 5s)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> rootfs_initcall(fn)    __define_initcall(fn, rootfs)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> device_initcall(fn)    __define_initcall(fn, 6)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> device_initcall_sync(fn)  __define_initcall(fn, 6s)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> late_initcall(fn)    __define_initcall(fn, 7)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> late_initcall_sync(fn)    __define_initcall(fn, 7s)</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __initcall(fn) device_initcall(fn)</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __exitcall(fn)            \</span><br><span class="hljs-meta">  static exitcall_t __exitcall_##fn __exit_call = fn</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> console_initcall(fn)  ___define_initcall(fn, con, .con_initcall)</span><br></code></pre></td></tr></table></figure><p>对于静态编译 initcall 接口如上，其中 <strong>pure_initcall()</strong>  只能在静态编译中存在。</p><p>当然，对于静态编译的驱动也可以调佣 <strong>module_init()</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></pre></td><td class="code"><pre><code class="hljs c">include/linux/module.h<br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> module_init(x)  __initcall(x);</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> module_exit(x)  __exitcall(x);</span><br></code></pre></td></tr></table></figure><blockquote><p><strong>module_init在内核中是有两种定义的，module_init定义在&lt;linux&#x2F;module.h&gt;中，当包含此头文件的代码中，没有定义MODULE宏时，module_init定义为initcall形式</strong></p></blockquote><h2 id="五、initcall级别"><a href="#五、initcall级别" class="headerlink" title="五、initcall级别"></a>五、initcall级别</h2><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><code class="hljs c">early--&gt;<br><span class="hljs-number">0</span>--&gt;<span class="hljs-number">0</span>s--&gt;<br><span class="hljs-number">1</span>--&gt;<span class="hljs-number">1</span>s--&gt;<br><span class="hljs-number">2</span>--&gt;<span class="hljs-number">2</span>s--&gt;<br><span class="hljs-number">3</span>--&gt;<span class="hljs-number">3</span>s--&gt;<br><span class="hljs-number">4</span>--&gt;<span class="hljs-number">4</span>s--&gt;<br><span class="hljs-number">5</span>--&gt;<span class="hljs-number">5</span>s--&gt;<br>rootfs--&gt;<br><span class="hljs-number">6</span>--&gt;<span class="hljs-number">6</span>s--&gt;<br><span class="hljs-number">7</span>--&gt;<span class="hljs-number">7</span>s--&gt;<br>console<br></code></pre></td></tr></table></figure><p>__define_initcall实现：</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><code class="hljs c">include/linux/init.h<br> <br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> CONFIG_LTO_CLANG</span><br>  <span class="hljs-comment">/*</span><br><span class="hljs-comment">   * With LTO, the compiler doesn&#x27;t necessarily obey link order for</span><br><span class="hljs-comment">   * initcalls, and the initcall variable needs to be globally unique</span><br><span class="hljs-comment">   * to avoid naming collisions.  In order to preserve the correct</span><br><span class="hljs-comment">   * order, we add each variable into its own section and generate a</span><br><span class="hljs-comment">   * linker script (in scripts/link-vmlinux.sh) to ensure the order</span><br><span class="hljs-comment">   * remains correct.  We also add a __COUNTER__ prefix to the name,</span><br><span class="hljs-comment">   * so we can retain the order of initcalls within each compilation</span><br><span class="hljs-comment">   * unit, and __LINE__ to make the names more unique.</span><br><span class="hljs-comment">   */</span><br>  <span class="hljs-meta">#<span class="hljs-keyword">define</span> ___lto_initcall(c, l, fn, id, __sec) \</span><br><span class="hljs-meta">  static initcall_t __initcall_##c##_##l##_##fn##id __used \</span><br><span class="hljs-meta">    __attribute__((__section__( #__sec \</span><br><span class="hljs-meta">      __stringify(.init..##c##_##l##_##fn)))) = fn;</span><br>  <span class="hljs-meta">#<span class="hljs-keyword">define</span> __lto_initcall(c, l, fn, id, __sec) \</span><br><span class="hljs-meta">  ___lto_initcall(c, l, fn, id, __sec)</span><br> <br>  <span class="hljs-meta">#<span class="hljs-keyword">define</span> ___define_initcall(fn, id, __sec) \</span><br><span class="hljs-meta">  __lto_initcall(__COUNTER__, __LINE__, fn, id, __sec)</span><br><span class="hljs-meta">#<span class="hljs-keyword">else</span></span><br>  <span class="hljs-meta">#<span class="hljs-keyword">define</span> ___define_initcall(fn, id, __sec) \</span><br><span class="hljs-meta">  static initcall_t __initcall_##fn##id __used \</span><br><span class="hljs-meta">    __attribute__((__section__(#__sec <span class="hljs-string">&quot;.init&quot;</span>))) = fn;</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)</span><br></code></pre></td></tr></table></figure><p>下文会继续细化分析，这里提前提示：</p><p>__define_initcall() 其实就是定义了一个 static initcall_t 的函数指针</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><code class="hljs c">include/linux/init.h<br> <br><span class="hljs-keyword">typedef</span> <span class="hljs-title function_">int</span> <span class="hljs-params">(*<span class="hljs-type">initcall_t</span>)</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br><span class="hljs-keyword">typedef</span> <span class="hljs-title function_">void</span> <span class="hljs-params">(*<span class="hljs-type">exitcall_t</span>)</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br></code></pre></td></tr></table></figure><h2 id="六、attribute详解"><a href="#六、attribute详解" class="headerlink" title="六、attribute详解"></a>六、attribute详解</h2><p>(1)、__used</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><code class="hljs c">include/linux/compiler_attributes.h<br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __used                          __attribute__((__used__))</span><br></code></pre></td></tr></table></figure><p>这是一种 attribute 修饰属性的一种，意思是告诉编译器：这个静态符号在编译的时候，即使没有使用也要保留，不能优化掉。</p><p>(2)、__attribute__ ((<strong>section</strong>(…)))<br><strong>attribute</strong> 是 GNU C 的一大特色，可以用来修饰对象、函数、结构体类型等等。</p><p>这里用来修改 section，意思是将作用的函数放入指定的 section name 对应的段中。</p><p>(3)、 __stringify()</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><code class="hljs c">include/linux/stringify.h<br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __stringify_1(x...)  #x</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __stringify(x...)  __stringify_1(x)</span><br></code></pre></td></tr></table></figure><p>将 __stringify() 中内容字符串化。</p><h2 id="七、例子"><a href="#七、例子" class="headerlink" title="七、例子"></a>七、例子</h2><p>以静态的module_init定义为例：</p><p>假如，我们在驱动使用如下接口：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">module_init(hello_init);<br></code></pre></td></tr></table></figure><p>那么，在编译的时候编译器会通过 initcall 接口产生：</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">initcall_t</span> __initcall_1_23_hello_init6 __attribute__(__used) \<br>    __attribute__((__section__(<span class="hljs-string">&quot;.initcall6.init..1_23_hello_init&quot;</span>))) = hello_init;<br></code></pre></td></tr></table></figure><h3 id="linux-编译后的initcall-函数"><a href="#linux-编译后的initcall-函数" class="headerlink" title="linux 编译后的initcall 函数"></a>linux 编译后的initcall 函数</h3><p>查看编译好的 <strong>System.map：</strong></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><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><code class="hljs bash">...<br>ffffffc012032ee0 d __initcall_223_42_trace_init_flags_sys_enterearly<br>ffffffc012032ee0 D __initcall_start<br>ffffffc012032ee0 D __setup_end<br>ffffffc012032ee8 d __initcall_224_66_trace_init_flags_sys_exitearly<br>ffffffc012032ef0 d __initcall_163_146_cpu_suspend_initearly<br>ffffffc012032ef8 d __initcall_151_267_asids_initearly<br>ffffffc012032f00 d __initcall_167_688_spawn_ksoftirqdearly<br>ffffffc012032f08 d __initcall_343_6656_migration_initearly<br>...<br>ffffffc012032f90 d __initcall_312_768_initialize_ptr_randomearly<br>ffffffc012032f98 D __initcall0_start<br>ffffffc012032f98 d __initcall_241_771_bpf_jit_charge_init0<br>ffffffc012032fa0 d __initcall_141_53_init_mmap_min_addr0<br>ffffffc012032fa8 d __initcall_209_6528_pci_realloc_setup_params0<br>ffffffc012032fb0 d __initcall_339_1143_net_ns_init0<br>ffffffc012032fb8 D __initcall1_start<br>ffffffc012032fb8 d __initcall_160_1437_fpsimd_init1<br>ffffffc012032fc0 d __initcall_181_669_tagged_addr_init1<br>...<br>ffffffc012033178 d __initcall_347_1788_init_default_flow_dissectors1<br>ffffffc012033180 d __initcall_360_2821_netlink_proto_init1<br>ffffffc012033188 D __initcall2_start<br>ffffffc012033188 d __initcall_165_139_debug_monitors_init2<br>ffffffc012033190 d __initcall_141_333_irq_sysfs_init2<br>...<br>ffffffc0120332b8 d __initcall_304_814_kobject_uevent_init2<br>ffffffc0120332c0 d __initcall_184_1686_msm_rpm_driver_init2s<br>ffffffc0120332c8 D __initcall3_start<br>ffffffc0120332c8 d __initcall_173_390_debug_traps_init3<br>ffffffc0120332d0 d __initcall_161_275_reserve_memblock_reserved_regions3<br>...<br>ffffffc012033370 d __initcall_132_5273_gsi_init3<br>ffffffc012033378 d __initcall_149_547_of_platform_default_populate_init3s<br>ffffffc012033380 D __initcall4_start<br>...<br>ffffffc012033878 D __initcall5_start<br>...<br>ffffffc0120339d8 d __initcall_317_1188_xsk_init5<br>ffffffc0120339e0 d __initcall_211_194_pci_apply_final_quirks5s<br>ffffffc0120339e8 d __initcall_168_680_populate_rootfsrootfs<br>ffffffc0120339e8 D __initcallrootfs_start<br>ffffffc0120339f0 D __initcall6_start<br>...<br>ffffffc012034b30 D __initcall7_start<br>...<br>ffffffc012034c88 d __initcall_150_554_of_platform_sync_state_init7s<br>ffffffc012034c90 d __initcall_123_29_alsa_sound_last_init7s<br>ffffffc012034c98 D __con_initcall_start<br>ffffffc012034c98 d __initcall_151_246_hvc_console_initcon<br>ffffffc012034c98 D __initcall_end<br>ffffffc012034ca0 D __con_initcall_end<br></code></pre></td></tr></table></figure><p>从 System.map 得知：</p><ul><li>从 __initcall_start ~ __initcall_end 所有函数指针都是连续的，相差8 个字节；</li><li>__initcall_start 就是第一个 early 级别的 initcall 函数指针，同理 __initcall0_start 就是第一个 level 0 级别的initcall 函数指针，以此类推；</li><li>rootfs 级别的 initcall 函数是插在 level 5s 之后，level 6 级别之前；</li><li>console 级别的函数在 level 7s 之后，__initcall_end 之前；<br>当然通过命令 readelf 或者 objdump (objdump -h vmlinux.o)都能看到各个字段</li></ul><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><code class="hljs c">Sections:<br>Idx Name          Size      VMA               LMA               File off  Algn<br>  <span class="hljs-number">0</span> .initcall0.init <span class="hljs-number">00000020</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000040</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">1</span> .initcall1.init <span class="hljs-number">000001</span>d0  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000060</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">2</span> .initcall2.init <span class="hljs-number">00000138</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000230</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">3</span> .initcall2s.init <span class="hljs-number">00000008</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000368</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">4</span> .initcall3.init <span class="hljs-number">000000b</span>0  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000370</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">5</span> .initcall3s.init <span class="hljs-number">00000008</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000420</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">6</span> .initcall4.init <span class="hljs-number">000004f</span>0  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000428</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">7</span> .initcall4s.init <span class="hljs-number">00000008</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000918</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">8</span> .initcall5.init <span class="hljs-number">00000168</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000920</span>  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br>  <span class="hljs-number">9</span> .initcall5s.init <span class="hljs-number">00000008</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000</span>a88  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br> <span class="hljs-number">10</span> .initcall6.init <span class="hljs-number">00001140</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00000</span>a90  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br> <span class="hljs-number">11</span> .initcall7.init <span class="hljs-number">00000140</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00001b</span>d0  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br> <span class="hljs-number">12</span> .initcall7s.init <span class="hljs-number">00000028</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00001</span>d10  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br> <span class="hljs-number">13</span> .con_initcall.init <span class="hljs-number">00000008</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00001</span>d38  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br> <span class="hljs-number">14</span> .initcallearly.init <span class="hljs-number">000000b</span>8  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00001</span>d40  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br> <span class="hljs-number">15</span> .initcallrootfs.init <span class="hljs-number">00000008</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">0000000000000000</span>  <span class="hljs-number">00001</span>df8  <span class="hljs-number">2</span>**<span class="hljs-number">3</span><br>                  CONTENTS, ALLOC, LOAD, RELOC, DATA<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;从根文件系统的挂载说起吧，使能了initd小文件系统的OS，最终会调用到populate_rootfs执行解压挂载的操作，搜索populate最终看到是rootfs_initcall被执行到的，那就从此处开始说起吧，看看内核是如何执行到这个函数的&lt;/p&gt;
&lt;h2 id=&quot;一</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>内核调试</title>
    <link href="http://example.com/posts/%E5%86%85%E6%A0%B8%E8%B0%83%E8%AF%95.html"/>
    <id>http://example.com/posts/%E5%86%85%E6%A0%B8%E8%B0%83%E8%AF%95.html</id>
    <published>2024-11-25T15:16:17.000Z</published>
    <updated>2024-11-25T15:16:32.766Z</updated>
    
    <content type="html"><![CDATA[<p>echo 7 4 1 7 &gt; &#x2F;proc&#x2F;kernel&#x2F;printk</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;echo 7 4 1 7 &amp;gt; &amp;#x2F;proc&amp;#x2F;kernel&amp;#x2F;printk&lt;/p&gt;
</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>使用proc和sysfs文件系统</title>
    <link href="http://example.com/posts/%E4%BD%BF%E7%94%A8proc%E5%92%8Csysfs%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.html"/>
    <id>http://example.com/posts/%E4%BD%BF%E7%94%A8proc%E5%92%8Csysfs%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.html</id>
    <published>2024-11-25T15:15:30.000Z</published>
    <updated>2024-11-25T15:15:51.953Z</updated>
    
    <content type="html"><![CDATA[<p>proc目录详解：<a href="https://blog.csdn.net/qq_39974998/article/details/130536491">https://blog.csdn.net/qq_39974998/article/details/130536491</a></p><h3 id="一、DEVICE-ATTR"><a href="#一、DEVICE-ATTR" class="headerlink" title="一、DEVICE_ATTR"></a>一、DEVICE_ATTR</h3><p>在调试驱动的时候我们一般会对于驱动中某一个属性或者变量进行操作，或者是控制gpio口，这个时候我们可以在驱动中创建对应的属性，从而在应用程序或者控制台对驱动的属性进行设置,sysfs可以通过sysfs_create_files和sysfs_create_group()来创建，其中使用DEVICE_ATTR来创建属性文件</p><p>这个宏定义在kernel&#x2F;include&#x2F;linux&#x2F;device.h中，函数定义为：</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DEVICE_ATTR(_name, _mode, _show, _store) \</span><br><span class="hljs-meta">  struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> __ATTR(_name, _mode, _show, _store) &#123;        \</span><br><span class="hljs-meta">  .attr = &#123;.name = __stringify(_name),\</span><br><span class="hljs-meta">  .mode = VERIFY_OCTAL_PERMISSIONS(_mode) &#125;,\</span><br><span class="hljs-meta">  .show= _show,\</span><br><span class="hljs-meta">  .store= _store,\</span><br><span class="hljs-meta">&#125;</span><br></code></pre></td></tr></table></figure><p>mode为文件的权限，定义在kernel&#x2F;include&#x2F;uapi&#x2F;linux&#x2F;stat.h</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IRWXU 00700 <span class="hljs-comment">//用户可读写和执行</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IRUSR 00400<span class="hljs-comment">//用户可读</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IWUSR 00200<span class="hljs-comment">//用户可写</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IXUSR 00100<span class="hljs-comment">//用户可执行</span></span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IRWXG 00070<span class="hljs-comment">//用户组可读写和执行</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IRGRP 00040<span class="hljs-comment">//用户组可读</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IWGRP 00020<span class="hljs-comment">//用户组可写</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IXGRP 00010<span class="hljs-comment">//用户组可执行</span></span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IRWXO 00007<span class="hljs-comment">//其他可读写和执行</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IROTH 00004<span class="hljs-comment">//其他可读</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IWOTH 00002<span class="hljs-comment">//其他可写</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> S_IXOTH 00001<span class="hljs-comment">//其他可执行</span></span><br></code></pre></td></tr></table></figure><h3 id="二、device-attribute"><a href="#二、device-attribute" class="headerlink" title="二、device_attribute"></a>二、device_attribute</h3><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><code class="hljs c"><span class="hljs-comment">/* interface for exporting device attributes */</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">device_attribute</span> &#123;</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">attribute</span>    <span class="hljs-title">attr</span>;</span><br>    <span class="hljs-type">ssize_t</span> (*show)(<span class="hljs-keyword">struct</span> device *dev, <span class="hljs-keyword">struct</span> device_attribute *attr,<br>            <span class="hljs-type">char</span> *buf);<br>    <span class="hljs-type">ssize_t</span> (*store)(<span class="hljs-keyword">struct</span> device *dev, <span class="hljs-keyword">struct</span> device_attribute *attr,<br>             <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">size_t</span> count);<br>&#125;;<br></code></pre></td></tr></table></figure><h3 id="三、创建sysfs文件"><a href="#三、创建sysfs文件" class="headerlink" title="三、创建sysfs文件"></a>三、创建sysfs文件</h3><h4 id="法1：sysfs-create-files"><a href="#法1：sysfs-create-files" class="headerlink" title="法1：sysfs_create_files"></a>法1：sysfs_create_files</h4><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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">ssize_t</span> <span class="hljs-title function_">led_store</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device *dev,<span class="hljs-keyword">struct</span> device_attribute *attr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">size_t</span> len)</span><br>&#123;<br>    printk(<span class="hljs-string">&quot;led_store()\n&quot;</span>);<br>    <span class="hljs-keyword">return</span> len;<span class="hljs-comment">//必须返回传入的长度</span><br>&#125;<br><br> <span class="hljs-comment">//下面的show和store只是简单举例</span><br><span class="hljs-type">static</span> <span class="hljs-type">ssize_t</span> <span class="hljs-title function_">led_show</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device *dev, <span class="hljs-keyword">struct</span> device_attribute*attr, <span class="hljs-type">char</span> *buf)</span><br>&#123;<br>         printk(<span class="hljs-string">&quot;led_show()\n&quot;</span>);<br>         returnpr_info(<span class="hljs-string">&quot;store\n&quot;</span>);<br> &#125;<br><br><span class="hljs-comment">//用DEVICE_ATTR宏创建属性led_sysfs文件，如果show()或是store()没有功能，就以NULL代替</span><br><span class="hljs-type">static</span> <span class="hljs-title function_">DEVICE_ATTR</span><span class="hljs-params">(led_sysfs, S_IWUSR, led_show,led_store)</span>;<br><br><span class="hljs-comment">//最后一项必须以NUll结尾</span><br><span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">attribute</span> *<span class="hljs-title">atk_imx6ul_led_sysfs_attrs</span>[] =</span> &#123;<br>  &amp;dev_attr_led_sysfs.attr,<br>  <span class="hljs-literal">NULL</span>,<br>&#125;;<br><br>sysfs_create_files(&amp;led_device.device-&gt;kobj,atk_imx6ul_led_sysfs_attrs);<br>sysfs_remove_file(&amp;led_device.device-&gt;kobj, atk_imx6ul_led_sysfs_attrs);<span class="hljs-comment">//驱动退出时释放结点</span><br></code></pre></td></tr></table></figure><p>其中kobj的定义为</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><code class="hljs c">kobject在设备结构体中的描述：<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">platform_device</span></span><br><span class="hljs-class">     —&gt;</span><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">device</span> <span class="hljs-title">dev</span></span><br><span class="hljs-class">        —&gt;</span><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">kobject</span> <span class="hljs-title">kobj</span></span><br></code></pre></td></tr></table></figure><h4 id="法2-推荐-：sysfs-create-group"><a href="#法2-推荐-：sysfs-create-group" class="headerlink" title="法2(推荐)：sysfs_create_group()"></a>法2(推荐)：sysfs_create_group()</h4><p>使用：将上面的attribute结构体再进行封装</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-type">ssize_t</span> <span class="hljs-title function_">led_store</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device *dev,<span class="hljs-keyword">struct</span> device_attribute *attr, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *buf, <span class="hljs-type">size_t</span> len)</span><br>&#123;<br>    printk(<span class="hljs-string">&quot;led_store()\n&quot;</span>);<br>    <span class="hljs-keyword">return</span> len;<span class="hljs-comment">//必须返回传入的长度</span><br>&#125;<br><br> <span class="hljs-comment">//下面的show和store只是简单举例</span><br><span class="hljs-type">static</span> <span class="hljs-type">ssize_t</span> <span class="hljs-title function_">led_show</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device *dev, <span class="hljs-keyword">struct</span> device_attribute*attr, <span class="hljs-type">char</span> *buf)</span><br>&#123;<br>         printk(<span class="hljs-string">&quot;led_show()\n&quot;</span>);<br>         returnpr_info(<span class="hljs-string">&quot;store\n&quot;</span>);<br> &#125;<br><br><span class="hljs-comment">//用DEVICE_ATTR宏创建属性led_sysfs文件，如果show()或是store()没有功能，就以NULL代替</span><br><span class="hljs-type">static</span> <span class="hljs-title function_">DEVICE_ATTR</span><span class="hljs-params">(led_sysfs, S_IWUSR, led_show,led_store)</span>;<br><br><span class="hljs-comment">//最后一项必须以NUll结尾</span><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">attribute</span> *<span class="hljs-title">atk_imx6ul_led_sysfs_attrs</span>[] =</span> &#123;<br>  &amp;dev_attr_led_sysfs.attr,<br>  <span class="hljs-literal">NULL</span>,<br>&#125;;<br><br><span class="hljs-type">static</span> <span class="hljs-type">const</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">attribute_group</span> <span class="hljs-title">dev_attr_grp</span> =</span> &#123;<br>       .attrs = atk_imx6ul_led_sysfs_attrs,<br>&#125;;<br>sysfs_create_group(&amp;led_device.device.kobj, &amp;dev_attr_grp) <span class="hljs-comment">//创建接口</span><br>sysfs_remove_group(&amp;client-&gt;dev.kobj, &amp;dev_attr_group); <span class="hljs-comment">//接口移除，在调用remove函数时调用</span><br></code></pre></td></tr></table></figure><h3 id="四、验证"><a href="#四、验证" class="headerlink" title="四、验证"></a>四、验证</h3><p>直接在终端下通过find命令查找结点，这里只是一个示例，并不是上面举的例子</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog3281136fb4b8bf1fee3e436e03651d2a.png"></p><p>实验echo命令调用store函数</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog903614bed87ac3a3d4925475013fb2ea.png"></p><p>这里cat没有写出，实际上直接将上述的echo 1或者echo 0 写成cat就okay了</p><h3 id="五、device-create-file"><a href="#五、device-create-file" class="headerlink" title="五、device_create_file"></a>五、device_create_file</h3><p>可以在创建驱动时，在创建出来的设备目录下&#x2F;sys&#x2F;class&#x2F;xxx&#x2F;xxx，新建sysfs文件</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><code class="hljs c"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> \* device_create_file - create sysfs attribute file for device.</span><br><span class="hljs-comment"> \* @dev: device.</span><br><span class="hljs-comment"> \* @attr: device attribute descriptor.</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">device_create_file</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device *dev, <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_attribute *attr)</span><br>&#123;<br> <span class="hljs-type">int</span> error = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">if</span> (dev)<br> error = sysfs_create_file(&amp;dev-&gt;kobj, &amp;attr-&gt;attr);<br> <span class="hljs-keyword">return</span> erro<br><br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;proc目录详解：&lt;a href=&quot;https://blog.csdn.net/qq_39974998/article/details/130536491&quot;&gt;https://blog.csdn.net/qq_39974998/article/details/13053649</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>内核定时器&amp;hrtime高精度定时器</title>
    <link href="http://example.com/posts/%E5%86%85%E6%A0%B8%E5%AE%9A%E6%97%B6%E5%99%A8-hrtime%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%AE%9A%E6%97%B6%E5%99%A8.html"/>
    <id>http://example.com/posts/%E5%86%85%E6%A0%B8%E5%AE%9A%E6%97%B6%E5%99%A8-hrtime%E9%AB%98%E7%B2%BE%E5%BA%A6%E5%AE%9A%E6%97%B6%E5%99%A8.html</id>
    <published>2024-11-25T15:14:19.000Z</published>
    <updated>2024-11-25T15:14:37.003Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、定时器初始化"><a href="#一、定时器初始化" class="headerlink" title="一、定时器初始化"></a>一、定时器初始化</h3><p><code>void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode)；</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><code class="hljs c">参数timer是hrtimer指针，<br>参数clock_id有如下常用几种选项：<br>       CLOCK_REALTIME  <span class="hljs-comment">//实时时间，如果系统时间变了，定时器也会变</span><br>       CLOCK_MONOTONIC  <span class="hljs-comment">//递增时间，不受系统影响</span><br>参数mode有如下几种选项：<br>  HRTIMER_MODE_ABS = <span class="hljs-number">0x0</span>,<span class="hljs-comment">/* 绝对模式 */</span><br>HRTIMER_MODE_REL = <span class="hljs-number">0x1</span>,    <span class="hljs-comment">/* 相对模式 */</span><br>HRTIMER_MODE_PINNED = <span class="hljs-number">0x02</span>,  <span class="hljs-comment">/* 和CPU绑定 */</span><br>HRTIMER_MODE_ABS_PINNED = <span class="hljs-number">0x02</span>, <span class="hljs-comment">/* 第一种和第三种的结合 */</span><br>HRTIMER_MODE_REL_PINNED = <span class="hljs-number">0x03</span>, <span class="hljs-comment">/* 第二种和第三种的结合 */</span><br></code></pre></td></tr></table></figure><h3 id="二、启动定时器"><a href="#二、启动定时器" class="headerlink" title="二、启动定时器"></a>二、启动定时器</h3><p><code>hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)；</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><code class="hljs c">参数timer是hrtimer指针<br>参数tim是时间，可以使用ktime_set()函数设置时间，<br>参数mode和初始化的mode参数一致<br></code></pre></td></tr></table></figure><h3 id="三、设置超时时间"><a href="#三、设置超时时间" class="headerlink" title="三、设置超时时间"></a>三、设置超时时间</h3><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><code class="hljs c"><span class="hljs-comment">/*</span><br><span class="hljs-comment"> * 单位为秒和纳秒组合</span><br><span class="hljs-comment"> */</span><br><span class="hljs-type">ktime_t</span> <span class="hljs-title function_">ktime_set</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">long</span> secs, <span class="hljs-type">const</span> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> nsecs)</span>；<br> <br><span class="hljs-comment">/* 设置超时时间，当定时器超时后可以用该函数设置下一次超时时间 */</span><br><span class="hljs-title function_">hrtimer_forward_now</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> hrtimer *timer, <span class="hljs-type">ktime_t</span> interval)</span><br></code></pre></td></tr></table></figure><h3 id="四、设置回调函数"><a href="#四、设置回调函数" class="headerlink" title="四、设置回调函数"></a>四、设置回调函数</h3><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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">hrtimer</span> <span class="hljs-title">timer</span>;</span><br>timer.function = hr_callback;<br></code></pre></td></tr></table></figure><h3 id="五、关闭定时器"><a href="#五、关闭定时器" class="headerlink" title="五、关闭定时器"></a>五、关闭定时器</h3><p><code>int hrtimer_cancel(struct hrtimer *timer)；</code></p><p>使用例子：hrtimer.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><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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/module.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/kernel.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/hrtimer.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/jiffies.h&gt;</span></span><br> <br><span class="hljs-comment">//定义一个hrtimer</span><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">hrtimer</span> <span class="hljs-title">timer</span>;</span><br><span class="hljs-type">ktime_t</span> kt;<br> <br><span class="hljs-comment">//定时器回调函数</span><br><span class="hljs-type">static</span> <span class="hljs-keyword">enum</span> hrtimer_restart <span class="hljs-title function_">hrtimer_hander</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> hrtimer *timer)</span><br>&#123;<br>    printk(<span class="hljs-string">&quot;I am in hrtimer hander\r\n&quot;</span>);<br>    hrtimer_forward(timer,timer-&gt;base-&gt;get_time(),kt);<span class="hljs-comment">//hrtimer_forward(timer, now, tick_period);</span><br>    <span class="hljs-keyword">return</span> HRTIMER_RESTART;  <span class="hljs-comment">//重启定时器</span><br>&#125;<br> <br><span class="hljs-type">static</span> <span class="hljs-type">int</span> __init <span class="hljs-title function_">test_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>    printk(<span class="hljs-string">&quot;---------%s-----------\r\n&quot;</span>,__func__);<br> <br>    kt = ktime_set(<span class="hljs-number">0</span>,<span class="hljs-number">1000000</span>);<span class="hljs-comment">// 0s  1000000ns  = 1ms 定时</span><br>    hrtimer_init(&amp;timer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);<br>    hrtimer_start(&amp;timer,kt,HRTIMER_MODE_REL);<br>    timer.function = hrtimer_hander; <span class="hljs-comment">//此处设置了定时器到时间后的回调函数</span><br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>&#125;<br> <br><span class="hljs-type">static</span> <span class="hljs-type">void</span> __exit <span class="hljs-title function_">test_exit</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>    hrtimer_cancel(&amp;timer);<br>    printk(<span class="hljs-string">&quot;------------test over---------------\r\n&quot;</span>);<br>&#125;<br> <br>module_init(test_init);<br>module_exit(test_exit);<br>MODULE_LICENSE(<span class="hljs-string">&quot;GPL&quot;</span>);<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、定时器初始化&quot;&gt;&lt;a href=&quot;#一、定时器初始化&quot; class=&quot;headerlink&quot; title=&quot;一、定时器初始化&quot;&gt;&lt;/a&gt;一、定时器初始化&lt;/h3&gt;&lt;p&gt;&lt;code&gt;void hrtimer_init(struct hrtimer *timer,</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>系统调用流程</title>
    <link href="http://example.com/posts/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B.html"/>
    <id>http://example.com/posts/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B.html</id>
    <published>2024-11-25T15:13:20.000Z</published>
    <updated>2024-11-25T15:13:44.426Z</updated>
    
    <content type="html"><![CDATA[<p>用户态代码通过系统调用可以进入内核态，通过系统调用进入内核态，同时会切换CPU运行模式，保证了用户空间和内核空间不影响。</p><p>程序可以请求操作系统提供服务，‌如设备IO操作、‌进程间通信等。‌系统调用运行在内核空间，‌需要更高的执行权限，‌而普通的库函数调用则运行在用户态。‌</p><p>Linux系统调用的实现机制涉及多个层面，‌包括用户空间和内核空间的划分、‌系统调用号、‌中断处理、‌以及内核函数的具体实现等。‌在x86架构上，‌系统调用通常通过<code>int 80h</code>指令实现，‌利用系统调用号来区分不同的入口函数。‌在Intel CPU中，‌这个指令由中断0x80实现，‌而在ARM架构中，‌则是通过SWI指令实现。‌</p><p>系统调用的基本流程包括：‌</p><ol><li>用户空间的程序通过库函数或者直接的系统调用请求服务。‌</li><li>这些请求通过软件中断（‌在x86上是int $0x80，‌在ARM上是SWI指令）‌陷入内核空间。‌</li><li>内核接收到请求后，‌根据系统调用号查找系统调用表（‌sys_call_table）‌，‌找到对应的内核函数入口地址。‌</li><li>执行内核函数，‌完成请求的服务后，‌返回结果到用户空间。‌</li></ol><p>系统调用的优点包括：‌</p><ul><li>提供了一种标准化的接口，‌使得用户空间的程序可以以一致的方式请求操作系统服务。‌</li><li>由于系统调用在内核空间执行，‌因此可以访问硬件设备等低级资源，‌提供了对硬件的直接控制。‌</li><li>通过内核的权限检查，‌提高了系统的安全性。‌</li></ul><p>总的来说，‌Linux系统调用是操作系统内核与用户空间程序交互的重要机制，‌它允许用户空间的程序请求操作系统提供各种服务，‌如文件操作、‌进程管理、‌网络通信等，‌从而实现了操作系统的基本功能‌</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;用户态代码通过系统调用可以进入内核态，通过系统调用进入内核态，同时会切换CPU运行模式，保证了用户空间和内核空间不影响。&lt;/p&gt;
&lt;p&gt;程序可以请求操作系统提供服务，‌如设备IO操作、‌进程间通信等。‌系统调用运行在内核空间，‌需要更高的执行权限，‌而普通的库函数调用则运行</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>互斥锁、信号量、原子操作、自旋锁</title>
    <link href="http://example.com/posts/%E4%BA%92%E6%96%A5%E9%94%81%E3%80%81%E4%BF%A1%E5%8F%B7%E9%87%8F%E3%80%81%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C%E3%80%81%E8%87%AA%E6%97%8B%E9%94%81.html"/>
    <id>http://example.com/posts/%E4%BA%92%E6%96%A5%E9%94%81%E3%80%81%E4%BF%A1%E5%8F%B7%E9%87%8F%E3%80%81%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C%E3%80%81%E8%87%AA%E6%97%8B%E9%94%81.html</id>
    <published>2024-11-25T15:12:37.000Z</published>
    <updated>2024-11-25T15:12:55.639Z</updated>
    
    <content type="html"><![CDATA[<p>linux并发产生的原因<br>（1）多线程并发访问，<br>（2）抢占式并发访问，进程调度可以在任意时刻抢占正在运行的线程，从而运行其他的线程<br>（3）终端程序并发访问<br>（4）SMP(多核)间并发访问</p><p>并发访问的后果就是竞争，一般像全局变量，设备结构体这些肯定是要保护的，至于其他的数据就要根据实际的驱动程序而定了。</p><h3 id="一、原子操作"><a href="#一、原子操作" class="headerlink" title="一、原子操作"></a>一、原子操作</h3><p>（1）原子操作的原因</p><p>对于c语言来说，a&#x3D;3，编译为汇编语言时可能被翻译成如下汇编（仅作为理解）</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><code class="hljs c">ldr r0, =<span class="hljs-number">0X30000000</span> <span class="hljs-comment">/* 变量 a 地址 */</span><br>ldr r1, = <span class="hljs-number">3</span> <span class="hljs-comment">/* 要写入的值 */</span><br>str r1, [r0] <span class="hljs-comment">/* 将 3 写入到 a 变量中 */</span><br></code></pre></td></tr></table></figure><p>当并发出现时可能出现这种情况：<br><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog82a1f37e4d64afd81e730e81473c81b5.png"></p><p>(2)原子操作api</p><p>为避免上述情况，Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作，在使用中用原子变量来代替整形变量，此结构体定义在 include&#x2F;linux&#x2F;types.h 文件中，</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><code class="hljs c"> <span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> &#123;</span><br>   <span class="hljs-type">int</span> counter;<br> &#125; <span class="hljs-type">atomic_t</span>;<br> <br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> &#123;</span><br>  <span class="hljs-type">long</span> <span class="hljs-type">long</span> counter;<br>&#125; <span class="hljs-type">atomic64_t</span>;<span class="hljs-comment">//64操作系统</span><br></code></pre></td></tr></table></figure><p>声明变量可以这样，atomic_t  a;<br>linux提供了多个可以对原子变量进行操作的函数，<br><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog20241125231145.png"></p><h3 id="二、自旋锁"><a href="#二、自旋锁" class="headerlink" title="二、自旋锁"></a>二、自旋锁</h3><p>原子操作只能对整形变量或者位进行保护，但是，在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区。举个最简单的例子，设备结构体变量就不是整型变量，我们对于结构体中成员变量的操作也要保证原子性，在线程 A 对结构体变量使用期间，应该禁止其他的线程来访问此结构体变量，这些工作原子操作都不能胜任，需要锁机制，在 Linux内核中就是自旋锁。</p><p>当一个线程要访问某个共享资源的时候首先要先获取相应的锁，锁只能被一个线程持有，只要此线程不释放持有的锁，那么其他的线程就不能获取此锁。对于自旋锁而言，如果自旋锁正在被线程 A 持有，线程 B 想要获取自旋锁，那么线程 B 就会处于忙循环-旋转-等待状态，线程 B 不会进入休眠状态或者说去做其他的处理，而是会一直傻傻的在那里“转圈圈”的等待锁可用。</p><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">spinlock</span> &#123;</span><br>  <span class="hljs-class"><span class="hljs-keyword">union</span> &#123;</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">raw_spinlock</span> <span class="hljs-title">rlock</span>;</span><br> <br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> CONFIG_DEBUG_LOCK_ALLOC</span><br><span class="hljs-meta"># <span class="hljs-keyword">define</span> LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> &#123;</span><br>      u8 __padding[LOCK_PADSIZE];<br>      <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">lockdep_map</span> <span class="hljs-title">dep_map</span>;</span><br>    &#125;;<br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>  &#125;;<br>&#125; <span class="hljs-type">spinlock_t</span>;<br></code></pre></td></tr></table></figure><p>自旋锁接口函数<br><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blogf5ff77cc22a0891d07a00467b061a698.png"></p><p>自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数，否则的话会可能会导致死锁现象的发生，经典死锁的发生例子：</p><p>线程a在持有锁期间进入了休眠，被调度出去，此时线程b想要获取锁，但是锁被线程a持有，并且内核抢占被禁止了，线程b无法调度出去一直等待锁被释放，而线程a此时也无法再次被调度运行，死锁就发生了。</p><p>还有就是中断里使用锁的时候也一定注意死锁的发生，例如<br><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blogedb90740c3e5348b42f9bd45cd80fe18.png"></p><p>线程 A 先运行，并且获取到了 lock 这个锁，当线程 A 运行 functionA 函数的时候中断发生了，中断抢走了 CPU 使用权。右边的中断服务函数也要获取 lock 这个锁，但是这个锁被线程 A 占有着，中断就会一直自旋，等待锁有效。</p><p>最好的解决方法就是获取锁之前关闭本地中断<br><img src="https://raw.githubusercontent.com/mengchao666/picture/main/bloga295bf9aa6e378115555fcfc664da424.png"></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><code class="hljs c">DEFINE_SPINLOCK(lock) <span class="hljs-comment">/* 定义并初始化一个锁 */</span><br> <br> <span class="hljs-comment">/* 线程 A */</span><br> <span class="hljs-type">void</span> <span class="hljs-title function_">functionA</span> <span class="hljs-params">()</span>&#123;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags; <span class="hljs-comment">/* 中断状态 */</span><br> spin_lock_irqsave(&amp;lock, flags) <span class="hljs-comment">/* 是在这里禁止本地中断，并获取锁 */</span><br> <span class="hljs-comment">/* 临界区 */</span><br> spin_unlock_irqrestore(&amp;lock, flags)  <span class="hljs-comment">/* 释放锁 */</span><br> &#125;<br> <br> <span class="hljs-comment">/* 中断服务函数 */</span><br> <span class="hljs-type">void</span> <span class="hljs-title function_">irq</span><span class="hljs-params">()</span> &#123;<br> spin_lock(&amp;lock) <span class="hljs-comment">/* 获取锁 */</span><br> <span class="hljs-comment">/* 临界区 */</span><br> spin_unlock(&amp;lock) <span class="hljs-comment">/* 释放锁 */</span><br> &#125;<br></code></pre></td></tr></table></figure><p>自旋锁的使用注意事项；</p><p>（1）因为在等待自旋锁的时候处于“自旋”状态，因此锁的持有时间不能太长，一定要<br>短，否则的话会降低系统性能。如果临界区比较大，运行时间比较长的话要选择其他的并发处<br>理方式，比如信号量和互斥体。</p><p>（2）自旋锁保护的临界区内不能调用任何可能导致线程休眠的 API 函数，否则的话可能<br>导致死锁.</p><p>（3）不能递归申请自旋锁，因为一旦通过递归的方式申请一个你正在持有的锁，那么你就<br>必须“自旋”</p><h3 id="三、信号量"><a href="#三、信号量" class="headerlink" title="三、信号量"></a>三、信号量</h3><p>相比较自旋锁，信号量可以使线程进入休眠状态，比如 A 与 B、C 合租了一套房子，这个房子只有一个厕所，一次只能一个人使用。某一天早上 A 去上厕所了，过了一会 B 也想用厕所，因为 A 在厕所里面，所以 B 只能等到 A 用来了才能进去。B 要么就一直在厕所门口等着，等 A 出来，这个时候就相当于自旋锁。B 也可以告诉 A，让 A 出来以后通知他一下，然后 B 继续回房间睡觉，这个时候相当于信号量。使用信号量会提高处理器的使用效率，但是，信号量的开销要比自旋锁大，因为信号量使线程进入休眠状态以后会切换线程，切换线程就会有开销。</p><p>信号量的特点：</p><p>（1）因为信号量可以使等待资源线程进入休眠状态，因此适用于那些占用资源比较久的场<br>合。<br>（2）信号量不能用于中断中，因为信号量会引起休眠，中断不能休眠。<br>（3）如果共享资源的持有时间比较短，那就不适合使用信号量了，因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。</p><p>信号量一般有一个信号量值，可通过信号量值控制方位共享资源的访问数量。例如一个房间有10吧钥匙，相当于信号量值为10，如果想要进入房间，那就需要先获取一把钥匙，信号量值减1，直到10把钥匙都被拿走，信号量为0，此时就不允许人进入房间了，如果有人出来，那么就可以在允许一个人进入。</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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">semaphore</span> &#123;</span><br>  <span class="hljs-type">raw_spinlock_t</span> lock;<br>  <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> count;<br>  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">list_head</span> <span class="hljs-title">wait_list</span>;</span><br>&#125;;<br></code></pre></td></tr></table></figure><p>信号量的 API 函数：</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/bloga175ba65633e9d8c9cacc53190261b40.png"></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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">semaphore</span> <span class="hljs-title">sem</span>;</span> <span class="hljs-comment">/* 定义信号量 */</span><br>sema_init(&amp;sem, <span class="hljs-number">1</span>)； <span class="hljs-comment">/* 初始化信号量 */</span><br>down(&amp;sem); <span class="hljs-comment">/* 申请信号量 */</span><br><span class="hljs-comment">/* 临界区 */</span><br>up(&amp;sem); <span class="hljs-comment">/* 释放信号量 */</span><br></code></pre></td></tr></table></figure><h3 id="四、互斥体"><a href="#四、互斥体" class="headerlink" title="四、互斥体"></a>四、互斥体</h3><p>将信号量的值设置为 1 就可以使用信号量进行互斥访问，但是互斥体有专门的结构体，</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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">mutex</span> &#123;</span><br>  <span class="hljs-comment">/* 1: unlocked, 0: locked, negative: locked, possible waiters */</span><br>  <span class="hljs-type">atomic_t</span> count;<br>  <span class="hljs-type">spinlock_t</span> wait_lock;<br>&#125;;<br></code></pre></td></tr></table></figure><p>特点：<br>（1）mutex 可以导致休眠，因此不能在中断中使用 mutex，中断中只能使用自旋锁。<br>（2）和信号量一样，mutex 保护的临界区可以调用引起阻塞的 API 函数。<br>（3）因为一次只有一个线程可以持有 mutex，因此，必须由 mutex 的持有者释放 mutex。并<br>且 mutex 不能递归上锁和解锁.</p><p>互斥体的接口函数：<br><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog17b4bf19b2811abeee9604ff99eafab5.png"></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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">mutex</span> <span class="hljs-title">lock</span>;</span> <span class="hljs-comment">/* 定义一个互斥体 */</span><br>mutex_init(&amp;lock); <span class="hljs-comment">/* 初始化互斥体 */</span><br><br>mutex_lock(&amp;lock); <span class="hljs-comment">/* 上锁 */</span><br><span class="hljs-comment">/* 临界区 */</span><br>mutex_unlock(&amp;lock); <span class="hljs-comment">/* 解锁 */</span><br></code></pre></td></tr></table></figure><h3 id="五、RCU"><a href="#五、RCU" class="headerlink" title="五、RCU"></a>五、RCU</h3><p>RCU(Read-Copy Update)，是 Linux 中比较重要的一种同步机制。顾名思义就是“读，拷贝更新”，再直白点是“随意读，但更新数据的时候，需要先复制一份副本，在副本上完成修改，再一次性地替换旧数据”。这是 Linux 内核实现的一种针对“读多写少”的共享数据的同步机制。</p><p>适用的场景：<br>我们前面说过，每种锁都有自己的适用的场景：spin lock不区分reader和writer，对于那些读写强度不对称的是不适合的，RW spin lcok和seq lock解决了这个问题，不过seq lock倾向writer，而RW spin lock更照顾reader。看起来一切都已经很完美了，但是，随着计算机硬件技术的发展，CPU的运算速度越来越快，相比之下，存储器件的速度发展较为滞后。在这种背景下，获取基于counter（需要访问存储器件）的锁（例如spin lock，rwlock）的机制开销比较大。而且，目前的趋势是：CPU和存储器件之间的速度差别在逐渐扩大。因此，那些基于一个multi-processor之间的共享的counter的锁机制已经不能满足性能的需求，在这种情况下，RCU机制应运而生（当然，更准确的说RCU一种内核同步机制，但不是一种lock，本质上它是lock-free的），它克服了其他锁机制的缺点，但是，甘蔗没有两头甜，RCU的使用场景比较受限，主要适用于下面的场景：</p><p>（1）RCU只能保护动态分配的数据结构，并且必须是通过指针访问该数据结构</p><p>（2）受RCU保护的临界区内不能sleep（SRCU不是本文的内容）</p><p>（3）读写不对称，对writer的性能没有特别要求，但是reader性能要求极高。</p><p>（4）reader端对新旧数据不敏感。</p><p>常用函数为：</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><code class="hljs c">rcu_read_lock();<br>rcu_read_unlock();<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;linux并发产生的原因&lt;br&gt;（1）多线程并发访问，&lt;br&gt;（2）抢占式并发访问，进程调度可以在任意时刻抢占正在运行的线程，从而运行其他的线程&lt;br&gt;（3）终端程序并发访问&lt;br&gt;（4）SMP(多核)间并发访问&lt;/p&gt;
&lt;p&gt;并发访问的后果就是竞争，一般像全局变量，设备结</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>DTS设备树</title>
    <link href="http://example.com/posts/DTS%E8%AE%BE%E5%A4%87%E6%A0%91.html"/>
    <id>http://example.com/posts/DTS%E8%AE%BE%E5%A4%87%E6%A0%91.html</id>
    <published>2024-11-25T15:09:20.000Z</published>
    <updated>2024-11-25T15:09:44.811Z</updated>
    
    <content type="html"><![CDATA[<p>设备树(Device Tree)，将这个词分开就是“设备”和“树”，描述设备树的文件叫做 DTS(Device Tree Source)，这个 DTS 文件采用树形结构描述板级设备，也就是开发板上的设备信息，比如 CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等</p><p>树的主干就是系统总线，IIC 控制器、GPIO 控制器、SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种，其中 IIC1 上接了 FT5206 和 AT24C02 这两个 IIC 设备，IIC2 上只接了 MPU6050 这个设备。DTS 文件的主要功能就是按照图所示的结构来描述板子上的设备信息，DTS 文件描述设备信息是有相应的语法规则要求的，稍后我们会详细的讲解 DTS 语法规则。</p><p>设备树由一系列的节点和属性组成，节点可包含子节点。在设备树中，可描述的信息包括：</p><ul><li>CPU数量和类型</li><li>内存基地址和大小</li><li>总线和桥</li><li>外设连接</li><li>中断控制器和中断使用情况</li><li>GPIO控制器和GPIO使用情况</li><li>时钟控制器和时钟使用情况</li></ul><p>bootload 会将这些信息传递给内核，内核开始识别这些树，并解析成 Linux 内核中 platform_device, i2c_client, spi_device等设备，而这些设备使用的内存资源，中断等信息也传递给内核。内核会将这些资源绑定给相应的设备。</p><h3 id="一、设备树例子"><a href="#一、设备树例子" class="headerlink" title="一、设备树例子"></a>一、设备树例子</h3><p>设备树相关的包含 3 部分：DTS、DTC、DTB</p><p>DTS 是设备树源码文件， DTB 是将 DTS 编译以后得到的二进制文件。那么将 .dts 编译为 .dtbn 需要什么工具呢？需要用到 DTC 工具</p><p>dts的一个例子如下：<br>比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;dt-bindings/gpio/gpio.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;dt-bindings/interrupt-controller/arm-gic.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&quot;imx6ull-pinfunc.h&quot;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&quot;imx6ull-pinfunc-snvs.h&quot;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&quot;skeleton.dtsi&quot;</span></span><br> <br>/ &#123;<br>  aliases &#123;<br>    can0 = &amp;flexcan1;<br>    ...<br>  &#125;;<br>  cpus &#123;<br>    <span class="hljs-meta">#address-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>    <span class="hljs-meta">#size-cells = <span class="hljs-string">&lt;0&gt;</span>;</span><br> <br>    cpu0: cpu@<span class="hljs-number">0</span> &#123;<br>      compatible = <span class="hljs-string">&quot;arm,cortex-a7&quot;</span>;<br>      device_type = <span class="hljs-string">&quot;cpu&quot;</span>;<br>      reg = &lt;<span class="hljs-number">0</span>&gt;;<br>      clock-latency = &lt;<span class="hljs-number">61036</span>&gt;; <span class="hljs-comment">/* two CLK32 periods */</span><br>      operating-points = &lt;<br>        <span class="hljs-comment">/* kHzuV */</span><br>        <span class="hljs-number">996000</span><span class="hljs-number">1275000</span><br>        <span class="hljs-number">792000</span><span class="hljs-number">1225000</span><br>        <span class="hljs-number">528000</span><span class="hljs-number">1175000</span><br>        <span class="hljs-number">396000</span><span class="hljs-number">1025000</span><br>        <span class="hljs-number">198000</span><span class="hljs-number">950000</span><br>      &gt;;<br>              <span class="hljs-comment">/* kHzuV */</span><br>        <span class="hljs-number">996000</span><span class="hljs-number">1275000</span><br>        <span class="hljs-number">792000</span><span class="hljs-number">1225000</span><br>        <span class="hljs-number">528000</span><span class="hljs-number">1175000</span><br>        <span class="hljs-number">396000</span><span class="hljs-number">1025000</span><br>      ...<br>  &#125;;<br> <br>  intc: interrupt-controller@<span class="hljs-number">00</span>a01000 &#123;<br>    compatible = <span class="hljs-string">&quot;arm,cortex-a7-gic&quot;</span>;<br>    <span class="hljs-meta">#interrupt-cells = <span class="hljs-string">&lt;3&gt;</span>;</span><br>    interrupt-controller;<br>    reg = &lt;<span class="hljs-number">0x00a01000</span> <span class="hljs-number">0x1000</span>&gt;,<br>    &lt;<span class="hljs-number">0x00a02000</span> <span class="hljs-number">0x100</span>&gt;;<br>  &#125;;<br>    ...<br></code></pre></td></tr></table></figure><p>文件描述了 CPU arm,cortex-a7 ，支持 996MHz、 792MHz等频率， 时钟一些信息。</p><p>“&#x2F;”是根节点，每个设备树文件只有一个根节点</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">node-name@unit-address<br></code></pre></td></tr></table></figure><p>其中 “node-name” 是节点名字，为 ASCII 字符串，节点名字应该能够清晰的描述出节点的功能，比如 “uart1” 就表示这个节点是 UART1 外设。“unit-address” 一般表示设备的地址或寄存器首地址，如果某个节点没有地址或者寄存器的话 “unit-address” 可以不要，比如 “cpu@0”、“interrupt-controller@00a01000”。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">label: node-name@unit-address<br></code></pre></td></tr></table></figure><p>引入 label 的目的就是为了方便访问节点，可以直接通过 &amp;label 来访问这个节点，比如通过&amp;cpu0 就可以访问 “cpu@0” 这个节点。很明显通过 &amp;intc 来访问 “interrupt-controller@00a01000” 这个节点要方便很多！</p><p>每个节点都有不同属性，不同的属性又有不同的内容，属性都是键值对，值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示：</p><p>1、字符串</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">compatible = <span class="hljs-string">&quot;fairchild,74hc595&quot;</span>;<br></code></pre></td></tr></table></figure><p>2、32 位无符号整数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">reg = &lt;<span class="hljs-number">0</span> <span class="hljs-number">0x123456</span> <span class="hljs-number">100</span>&gt;;<br></code></pre></td></tr></table></figure><p>3、字符串列表</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">compatible = <span class="hljs-string">&quot;fsl,imx6ul-pxp-v4l2&quot;</span>, <span class="hljs-string">&quot;fsl,imx6sx-pxp-v4l2&quot;</span>, <span class="hljs-string">&quot;fsl,imx6sl-pxp-v4l2&quot;</span>;<br></code></pre></td></tr></table></figure><h3 id="二、设备树详解"><a href="#二、设备树详解" class="headerlink" title="二、设备树详解"></a>二、设备树详解</h3><h5 id="1-、标准属性"><a href="#1-、标准属性" class="headerlink" title="(1)、标准属性"></a>(1)、标准属性</h5><p>1、compatible 属性</p><p>compatible 属性也叫做“兼容性”属性，这是非常重要的一个属性！ compatible 属性的值是一个字符串列表， compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序，compatible 属性的值格式如下所示：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-string">&quot;manufacturer,model&quot;</span><br></code></pre></td></tr></table></figure><p>其中 manufacturer 表示厂商，model 一般是模块对应的驱动名字。比如 imx6ull-alientek-emmc.dts 中 sound 节点是 I.MX6U-ALPHA 开发板的音频设备节点，I.MX6U-ALPHA 开发板上的音频芯片采用的欧胜(WOLFSON)出品的 WM8960，sound 节点的 compatible 属性值如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">compatible = <span class="hljs-string">&quot;fsl,imx6ul-evk-wm8960&quot;</span>,<span class="hljs-string">&quot;fsl,imx-audio-wm8960&quot;</span>;<br></code></pre></td></tr></table></figure><p>其中 “fsl” 表示厂商是飞思卡尔，“imx6ul-evk-wm8960” 和 “imx-audio-wm8960” 表示驱动模块名字。设备首先使用第一个兼容值在 Linux 内核里面查找，如果没有找到的话就使用第二个兼容值查。</p><p>2、 model 属性</p><p>model 属性值也是一个字符串，一般 model 属性描述设备模块信息，比如名字什么的，比如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c">model = <span class="hljs-string">&quot;wm8960-audio&quot;</span>;<br></code></pre></td></tr></table></figure><p>3、status 属性</p><p>status 属性看名字就知道是和设备状态有关的，status 属性值也是字符串，字符串是设备的状态信息，可选的状态如下表所示：</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog803172a7f4daa540ee467df243b092a8.png"></p><p>4、★#address-cells 和#size-cells 属性</p><p>#address-cells# 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位)<br>#size-cells# 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)<br>#address-cells# 和 #size-cells# 表明了子节点应该如何编写 reg 属性值，一般 reg 属性都是和地址有关的内容，和地址相关的信息有两种：起始地址和地址长度， reg 属性的格式一为：</p><blockquote><p>reg &#x3D; &lt;address1 length1 address2 length2 address3 length3……&gt;</p></blockquote><p>每个 “address length” 组合表示一个地址范围，其中 address 是起始地址， length 是地址长度.<br>#address-cells# 表明 address 这个数据所占用的字长， #size-cells# 表明 length 这个数据所占用的字长</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><code class="hljs c">aips3: aips-bus@<span class="hljs-number">02200000</span> &#123;<br>  compatible = <span class="hljs-string">&quot;fsl,aips-bus&quot;</span>, <span class="hljs-string">&quot;simple-bus&quot;</span>;<br>  <span class="hljs-meta">#address-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>  <span class="hljs-meta">#size-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br><br>  dcp: dcp@<span class="hljs-number">02280000</span> &#123;<br>    compatible = <span class="hljs-string">&quot;fsl,imx6sl-dcp&quot;</span>;<br>    reg = &lt;<span class="hljs-number">0x02280000</span> <span class="hljs-number">0x4000</span>&gt;;<br>  &#125;;<br>&#125;;<br></code></pre></td></tr></table></figure><p>说明 aips3: aips-bus@02200000 节点起始地址长度所占用的字长为 1，地址长度所占用的字长也为 1<br>子节点 dcp: dcp@02280000 的 reg 属性值为&lt;0x02280000 0x4000&gt;相当于设置了起始地址为 0x02280000，地址长度为 0x40000，但是 dcp的地址长度(范围)并没有 0x4000 这么多</p><p>5、 ★reg 属性</p><p>reg 属性一般用于描述设备地址空间资源信息，一般都是某个外设的寄存器地址范围信息,一般是(address， length)组成，详情如上所述！</p><p>6、ranges 属性</p><p>ranges 属性值可以为空或者按照 (child-bus-address,parent-bus-address,length) 格式编写的数字矩阵， ranges 是一个地址映射&#x2F;转换表， ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成：</p><p>child-bus-address：子总线地址空间的物理地址，由父节点的 #address-cells# 确定此物理地址所占用的字长。<br>parent-bus-address：父总线地址空间的物理地址，同样由父节点的 #address-cells# 确定此物理地址所占用的字长。<br>length：子地址空间的长度，由父节点的 #size-cells# 确定此地址长度所占用的字长。</p><p>如果 ranges 属性值为空值，说明子地址空间和父地址空间完全相同，不需要进行地址转换，例程如下：</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><code class="hljs c">soc &#123;<br>  <span class="hljs-meta">#address-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>  <span class="hljs-meta">#size-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>  compatible = <span class="hljs-string">&quot;simple-bus&quot;</span>;<br>  interrupt-parent = &lt;&amp;gpc&gt;;<br>  ranges;<span class="hljs-comment">//为空</span><br></code></pre></td></tr></table></figure><p>ranges 属性不为空的示例代码如下所示：</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><code class="hljs c">soc &#123;<br>  compatible = <span class="hljs-string">&quot;simple-bus&quot;</span>;<br>  <span class="hljs-meta">#address-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>  <span class="hljs-meta">#size-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>  ranges = &lt;<span class="hljs-number">0x0</span> <span class="hljs-number">0xe0000000</span> <span class="hljs-number">0x00100000</span>&gt;;<br><br>  serial &#123;<br>    device_type = <span class="hljs-string">&quot;serial&quot;</span>;<br>    compatible = <span class="hljs-string">&quot;ns16550&quot;</span>;<br>    reg = &lt;<span class="hljs-number">0x4600</span> <span class="hljs-number">0x100</span>&gt;;<br>    clock-frequency = &lt;<span class="hljs-number">0</span>&gt;;<br>    interrupts = &lt;<span class="hljs-number">0xA</span> <span class="hljs-number">0x8</span>&gt;;<br>    interrupt-parent = &lt;&amp;ipic&gt;;<br>  &#125;;<br>&#125;;<br></code></pre></td></tr></table></figure><p>节点 soc 定义的 ranges 属性，值为 &lt;0x0 0xe0000000 0x00100000&gt;，此属性值指定了一个 1024KB(0x00100000)的地址范围，子地址空间的物理起始地址为 0x0，父地址空间的物理起始地址为 0xe0000000。</p><p>serial 是串口设备节点，reg 属性定义了 serial 设备寄存器的起始地址为 0x4600，寄存器长度为 0x100。经过地址转换，serial 设备可以从 0xe0004600 开始进行读写操作，0xe0004600&#x3D;0x4600+0xe0000000。</p><h3 id="三、向节点追加或修改内容"><a href="#三、向节点追加或修改内容" class="headerlink" title="三、向节点追加或修改内容"></a>三、向节点追加或修改内容</h3><p>imx6ull.dtsi 有以下内容，表示 I2C 节点。不同的 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></pre></td><td class="code"><pre><code class="hljs c">i2c1: i2c@<span class="hljs-number">021</span>a0000 &#123;<br>  <span class="hljs-meta">#address-cells = <span class="hljs-string">&lt;1&gt;</span>;</span><br>  <span class="hljs-meta">#size-cells = <span class="hljs-string">&lt;0&gt;</span>;</span><br>  compatible = <span class="hljs-string">&quot;fsl,imx6ul-i2c&quot;</span>, <span class="hljs-string">&quot;fsl,imx21-i2c&quot;</span>;<br>  reg = &lt;<span class="hljs-number">0x021a0000</span> <span class="hljs-number">0x4000</span>&gt;;<br>  interrupts = &lt;GIC_SPI <span class="hljs-number">36</span> IRQ_TYPE_LEVEL_HIGH&gt;;<br>  clocks = &lt;&amp;clks IMX6UL_CLK_I2C1&gt;;<br>  status = <span class="hljs-string">&quot;disabled&quot;</span>;<br>&#125;;<br></code></pre></td></tr></table></figure><p>现在要在 i2c1 节点下创建一个子节点，这个子节点就是 fxls8471，最简单的方法就是在 i2c1 下直接添加一个名为 fxls8471 的子节点，如下所示：</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><code class="hljs c">&amp;i2c1 &#123;<br>  clock-frequency = &lt;<span class="hljs-number">100000</span>&gt;;<br>  pinctrl-names = <span class="hljs-string">&quot;default&quot;</span>;<br>  pinctrl<span class="hljs-number">-0</span> = &lt;&amp;pinctrl_i2c1&gt;;<br>  status = <span class="hljs-string">&quot;okay&quot;</span>;<br><br>  fxls8471@<span class="hljs-number">1</span>e &#123;<br>    compatible = <span class="hljs-string">&quot;fsl,fxls8471&quot;</span>;<br>    reg = &lt;<span class="hljs-number">0x1e</span>&gt;;<br>    position = &lt;<span class="hljs-number">0</span>&gt;;<br>    interrupt-parent = &lt;&amp;gpio5&gt;;<br>    interrupts = &lt;<span class="hljs-number">0</span> <span class="hljs-number">8</span>&gt;;<br>&#125;;<br></code></pre></td></tr></table></figure><p>子节点可以修改增加一些属性；<br>比如子节点中 clock-frequency 新增加的属性。<br>status 状态由disabled变成 okay</p><h3 id="四、设备树在目录中的体现"><a href="#四、设备树在目录中的体现" class="headerlink" title="四、设备树在目录中的体现"></a>四、设备树在目录中的体现</h3><p>运行 cd &#x2F;proc&#x2F;device-tree 后，ls -a 查询当前目录下的文本情况</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog7fa958da33cda8112329b01e3bd409d8.png"></p><p>1、在当前目录下执行 cat model</p><blockquote><p>model 的内容是 “Freescale i.MX6 ULL 14x14 EVK Board”<br>compatible 的内容为 “fsl,imx6ull-14x14-evkfsl,imx6ull”</p></blockquote><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blogeb77e936e496f83c48bbafe2a30b0af0.png"></p><p>打开文件 imx6ull-alientek-emmc.dts 查看一下，这正是根节点 “&#x2F;” 的 model 和 compatible 属性值</p><p>2、soc子节点</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog20241125230745.png"></p><p>3、aliases 子节点</p><p><img src="/assets/image-20241125230849-nfh9y2i.png" alt="image"></p><p>与imx6ull.dtsi中的 aliases一致</p><p>4、chosen 子节点</p><p>chosen 并不是一个真实的设备， chosen 节点主要是为了 uboot 向 Linux 内核传递数据，重点是 bootargs 参数，一般.dts 文件中 chosen 节点通常为空或者内容很少， imx6ull-alientekemmc.dts 中 chosen 节点内容如下所示：</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><code class="hljs c">chosen &#123;<br>  <span class="hljs-built_in">stdout</span>-path = &amp;uart1;<br>&#125;<br></code></pre></td></tr></table></figure><p>chosen 节点仅仅设置了属性 “stdout-path”，表示标准输出使用 uart1。</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><code class="hljs c">root@ATK-IMX6U:/proc/device-tree/chosen<span class="hljs-meta"># ls</span><br>bootargs  name  <span class="hljs-built_in">stdout</span>-path<br></code></pre></td></tr></table></figure><p>我们可以发现 chosen 内存在 boot 的启动参数 bootargs！</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blog14c3de917c139c77d2b33b3b125e7b5e.png"></p><p>cat 查看确实是启动信息<br>1，我们并没有在设备树中设置 chosen 节点的 bootargs 属性，那么 bootargs这个属性是怎么产生的如何关联起来的呢？<br>2，为什么和 uboot 中的参数不一致？</p><p>chosen 节点的 bootargs 属性不是我们在设备树里面设置的，那么只有一种可能，那就是 uboot 自己在 chosen 节点里面添加了 bootargs 属性，并且设置 bootargs 属性的值为 bootargs环境变量的值。</p><p>uboot 源码中搜索 “chosen”，在文件 common&#x2F;fdt_support.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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">fdt_chosen</span><span class="hljs-params">(<span class="hljs-type">void</span> *fdt)</span><br>&#123;<br>   <span class="hljs-comment">//寻找chosen节点</span><br>  nodeoffset = fdt_find_or_add_subnode(fdt, <span class="hljs-number">0</span>, <span class="hljs-string">&quot;chosen&quot;</span>);<br><br>  <span class="hljs-keyword">if</span> (nodeoffset &lt; <span class="hljs-number">0</span>)<br>    <span class="hljs-keyword">return</span> nodeoffset;<br>  <span class="hljs-comment">//读取bootargs环境</span><br>  str = getenv(<span class="hljs-string">&quot;bootargs&quot;</span>);<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="五、Linux-内核解析-DTB-文件"><a href="#五、Linux-内核解析-DTB-文件" class="headerlink" title="五、Linux 内核解析 DTB 文件"></a>五、Linux 内核解析 DTB 文件</h3><p>启动内核流程函数</p><p><img src="https://raw.githubusercontent.com/mengchao666/picture/main/blogcf9237850e99e117ecac39bdde6c4911.png"></p><p>start_kernel 函数中最终调用了函数为 unflatten_dt_node（很多初始化操作都在start_kernel ）！</p><h3 id="六、设备树节点的操作函数"><a href="#六、设备树节点的操作函数" class="headerlink" title="六、设备树节点的操作函数"></a>六、设备树节点的操作函数</h3><p>Linux 驱动程序往往需要去读取到 Linux 内核中附带的 dts 文件，并操作设备树 DTS 的相关节点！接下来我们来学习一下，如何进行设备树节点操作！</p><h4 id="1、查找节点的-of-函数"><a href="#1、查找节点的-of-函数" class="headerlink" title="1、查找节点的 of 函数"></a>1、查找节点的 of 函数</h4><p>Linux 内核使用 device_node 结构体来描述一个节点</p><p>1、 of_find_node_by_name 函数</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><code class="hljs c"><span class="hljs-comment">//通过节点名字查找指定的节点</span><br><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_find_node_by_name</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device_node *from, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *name)</span>;<br><span class="hljs-comment">//from：开始查找的节点，如果为 NULL 表示从根节点开始查找整个设备树。</span><br><span class="hljs-comment">//name：要查找的节点名字</span><br></code></pre></td></tr></table></figure><p>2、of_find_node_by_type 函数</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><code class="hljs c"><span class="hljs-comment">//通过device_type查找指定的节点</span><br><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_find_node_by_type</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device_node *from, <span class="hljs-type">const</span> <span class="hljs-type">char</span> *type)</span><br></code></pre></td></tr></table></figure><p>3、 of_find_compatible_node 函数</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><code class="hljs c">device_type 和 compatible 这两个属性查找指定的节点<br><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_find_compatible_node</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device_node *from,</span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">char</span> *type,</span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">char</span> *compatible)</span><br></code></pre></td></tr></table></figure><p>4、of_find_matching_node_and_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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">//通过 of_device_id 匹配表来查找指定的节点</span><br><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_find_matching_node_and_match</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device_node *from,</span><br><span class="hljs-params">            <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> of_device_id *matches,</span><br><span class="hljs-params">            <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> of_device_id **match)</span><br></code></pre></td></tr></table></figure><p>5、of_find_node_by_path 函数</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><code class="hljs c"><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_get_parent</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_node *node)</span><br><span class="hljs-comment">//node 要查找的父节点的节点</span><br></code></pre></td></tr></table></figure><h3 id="七、查找父-子节点的-OF-函数"><a href="#七、查找父-子节点的-OF-函数" class="headerlink" title="七、查找父&#x2F;子节点的 OF 函数"></a>七、查找父&#x2F;子节点的 OF 函数</h3><p>1、of_get_parent 函数<br>用于获取指定节点的父节点</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><code class="hljs c"><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_get_parent</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_node *node)</span><br><span class="hljs-comment">//node 要查找的父节点的节点</span><br></code></pre></td></tr></table></figure><p>2、of_get_next_child 函数</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><code class="hljs c"><span class="hljs-comment">//用迭代的查找子节点</span><br><span class="hljs-keyword">struct</span> device_node *<span class="hljs-title function_">of_get_next_child</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_node *node,</span><br><span class="hljs-params">                          <span class="hljs-keyword">struct</span> device_node *prev)</span><br><span class="hljs-comment">//node：父节点。</span><br><span class="hljs-comment">//prev：前一个子节点，也就是从哪一个子节点开始迭代的查找下一个子//节点。可以设置为NULL，表示从第一个子节点开始。</span><br><span class="hljs-comment">//返回 找到的下一个子节点。</span><br></code></pre></td></tr></table></figure><h3 id="八、提取属性值的-OF-函数"><a href="#八、提取属性值的-OF-函数" class="headerlink" title="八、提取属性值的 OF 函数"></a>八、提取属性值的 OF 函数</h3><p>property 结构体，此结构体定义在文件 include&#x2F;linux&#x2F;of.h 中</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">property</span> &#123;</span><br>  <span class="hljs-type">char</span> *name; <span class="hljs-comment">/* 属性名字 */</span><br>  <span class="hljs-type">int</span> length; <span class="hljs-comment">/* 属性长度 */</span><br>  <span class="hljs-type">void</span> *value; <span class="hljs-comment">/* 属性值 */</span><br>  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">property</span> *<span class="hljs-title">next</span>;</span> <span class="hljs-comment">/* 下一个属性 */</span><br>  <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> _flags;<br>  <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> unique_id;<br>  <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">bin_attribute</span> <span class="hljs-title">attr</span>;</span><br>&#125;;<br></code></pre></td></tr></table></figure><p>1、 of_find_property 函数</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><code class="hljs c">property *<span class="hljs-title function_">of_find_property</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_node *np,</span><br><span class="hljs-params">               <span class="hljs-type">const</span> <span class="hljs-type">char</span> *name,</span><br><span class="hljs-params">               <span class="hljs-type">int</span> *lenp)</span><br><span class="hljs-comment">//np：设备节点。</span><br><span class="hljs-comment">//name： 属性名字。</span><br><span class="hljs-comment">//lenp：属性值的字节数</span><br><span class="hljs-comment">//返回找到的属性</span><br></code></pre></td></tr></table></figure><p>2、 of_property_count_elems_of_size 函数<br>用于获取属性中元素的数量，比如 reg 属性值是一个数组，那么使用此函数可以获取到这个数组的大小</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">of_property_count_elems_of_size</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_node *np,</span><br><span class="hljs-params">                      <span class="hljs-type">const</span> <span class="hljs-type">char</span> *propname,</span><br><span class="hljs-params">                      <span class="hljs-type">int</span> elem_size)</span><br><span class="hljs-comment">//np：设备节点。</span><br><span class="hljs-comment">//proname： 需要统计元素数量的属性名字。</span><br><span class="hljs-comment">//elem_size：元素长度。</span><br><span class="hljs-comment">//返回 得到的属性元素数量</span><br></code></pre></td></tr></table></figure><p>3、 of_property_read_u32_index 函数<br>从属性中获取指定标号的 u32 类型数据值(无符号 32 位)，比如某个属性有多个 u32 类型的值，那么就可以使用此函数来获取指定标号的数据值</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">of_property_read_u32_index</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> device_node *np,</span><br><span class="hljs-params">                     <span class="hljs-type">const</span> <span class="hljs-type">char</span> *propname,</span><br><span class="hljs-params">                     u32 index,</span><br><span class="hljs-params">                     u32 *out_value)</span><br></code></pre></td></tr></table></figure><p>4、of_property_read_u8_array 函数<br>of_property_read_u16_array 函数<br>of_property_read_u32_array 函数<br>of_property_read_u64_array 函数</p><p>分别是读取属性中 u8、 u16、 u32 和 u64 类型的数组数据，比如大多数的 reg 属性都是数组数据，可以使用这 4 个函数一次读取出 reg 属性中的所有数据</p><p>5、 of_property_read_string 函数</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><code class="hljs c"><span class="hljs-comment">//用于读取属性中字符串值</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">of_property_read_string</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> device_node *np,</span><br><span class="hljs-params">                            <span class="hljs-type">const</span> <span class="hljs-type">char</span> *propname,</span><br><span class="hljs-params">                            <span class="hljs-type">const</span> <span class="hljs-type">char</span> **out_string)</span><br></code></pre></td></tr></table></figure><p>6、 of_n_addr_cells 函数<br>用于获取#address-cells 属性值</p><p>7、 of_n_size_cells 函数<br>of_size_cells 函数用于获取#size-cells 属性值</p><p>8、of_iomap 函数<br>采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址，不需要使用 ioremap 函数了</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;设备树(Device Tree)，将这个词分开就是“设备”和“树”，描述设备树的文件叫做 DTS(Device Tree Source)，这个 DTS 文件采用树形结构描述板级设备，也就是开发板上的设备信息，比如 CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SP</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>内核线程</title>
    <link href="http://example.com/posts/%E5%86%85%E6%A0%B8%E7%BA%BF%E7%A8%8B.html"/>
    <id>http://example.com/posts/%E5%86%85%E6%A0%B8%E7%BA%BF%E7%A8%8B.html</id>
    <published>2024-11-25T15:04:27.000Z</published>
    <updated>2024-11-25T15:04:48.199Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、kthread-create-kthread-run"><a href="#一、kthread-create-kthread-run" class="headerlink" title="一、kthread_create &amp; kthread_run"></a>一、kthread_create &amp; kthread_run</h3><p>内核线程是工作在内核空间的，不属于任何一个进程，可以发生睡眠。可以用内核线程来进行一些循环的动作，比如通过循环拉高拉低gpio设置成方波输出的信号来模拟pwm信号，比如循环控制led的闪灯效果等等都可以使用到内核线程kthread_create接口函数。</p><p>内核线程的相关代码目录：</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><code class="hljs c">include/linux/kthread.h <br>kernel/kthread.c<br></code></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><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><code class="hljs c"><span class="hljs-keyword">struct</span> task_struct *<span class="hljs-title function_">kthread_create</span><span class="hljs-params">(<span class="hljs-type">int</span> (*threadfn)(<span class="hljs-type">void</span> *data),</span><br><span class="hljs-params">                               <span class="hljs-type">void</span> *data,</span><br><span class="hljs-params">                               <span class="hljs-type">const</span> <span class="hljs-type">char</span> namefmt[], ...)</span>;<br><br>_<span class="hljs-comment">/**_</span><br><span class="hljs-comment"> _* kthread_run - create and wake a thread._</span><br><span class="hljs-comment"> _* @threadfn: the function to run until signal_pending(current)._</span><br><span class="hljs-comment"> _* @data: data ptr for @threadfn._</span><br><span class="hljs-comment"> _* @namefmt: printf-style name for the thread._</span><br><span class="hljs-comment"> _*_</span><br><span class="hljs-comment"> _* Description: Convenient wrapper for kthread_create() followed by_</span><br><span class="hljs-comment"> _* wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM)._</span><br><span class="hljs-comment"> _*/</span>_<br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> kthread_run(threadfn, data, namefmt, ...)                        \</span><br><span class="hljs-meta">(&#123;                                                               \</span><br><span class="hljs-meta">       struct task_struct *__k                                        \</span><br><span class="hljs-meta">              = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \</span><br><span class="hljs-meta">       <span class="hljs-keyword">if</span> (!IS_ERR(__k))                                        \</span><br><span class="hljs-meta">              wake_up_process(__k);                                \</span><br><span class="hljs-meta">       __k;                                                     \</span><br><span class="hljs-meta">&#125;)</span><br></code></pre></td></tr></table></figure><p>其中kthread_create()只是创建一个内核线程，但并没有启动，需要调用wake_up_process()来启动线程，所以内核又帮我们定义了一个宏kthread_run来帮我们搞定。内核线程创建成功后，会返回一个struct task_struct对象指针，方便我们的后续操作。</p><h3 id="二、关闭一个内核线程"><a href="#二、关闭一个内核线程" class="headerlink" title="二、关闭一个内核线程"></a>二、关闭一个内核线程</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">kthread_stop</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> task_struct *k)</span>;<br></code></pre></td></tr></table></figure><p>这个调用是会阻塞等待，直到内核线程k退出为止。原因为此函数内部会调用wait_for_completion(）的方法（通过等待队列来实现），阻塞等待内核线程自身的退出。</p><h3 id="三、内核线程函数，如何判断自身需要退出"><a href="#三、内核线程函数，如何判断自身需要退出" class="headerlink" title="三、内核线程函数，如何判断自身需要退出"></a>三、内核线程函数，如何判断自身需要退出</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">kthread_should_stop</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>;<br></code></pre></td></tr></table></figure><p>如果该内核线程已经被设置stop标志了，则会返回1，否则返回0。</p><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><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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/module.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/kthread.h&gt;</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/delay.h&gt;</span></span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> ENTER() printk(KERN_DEBUG <span class="hljs-string">&quot;%s() Enter&quot;</span>, __func__)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> EXIT() printk(KERN_DEBUG <span class="hljs-string">&quot;%s() Exit&quot;</span>, __func__)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> ERR(fmt, args...) printk(KERN_ERR <span class="hljs-string">&quot;%s()-%d: &quot;</span> fmt <span class="hljs-string">&quot;\n&quot;</span>, __func__, __LINE__, ##args)</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> DBG(fmt, args...) printk(KERN_DEBUG <span class="hljs-string">&quot;%s()-%d: &quot;</span> fmt <span class="hljs-string">&quot;\n&quot;</span>, __func__, __LINE__, ##args)</span><br><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">task_struct</span> *<span class="hljs-title">test_kthread</span> =</span> <span class="hljs-literal">NULL</span>;  <span class="hljs-comment">//_定义一个_task_struct_结构体指针，赋值为_NULL</span><br><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">kthread_test_func</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span>   <span class="hljs-comment">//定义一个内核线程要执行的函数</span><br>&#123;<br>    ENTER();<br>    <span class="hljs-keyword">while</span> (!kthread_should_stop()) &#123;<br>        DBG(<span class="hljs-string">&quot;kthread is running&quot;</span>);<br>        msleep(<span class="hljs-number">5000</span>);<br>    &#125;<br><br>    EXIT();<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br><br>&#125;<br><br><span class="hljs-type">static</span> __init <span class="hljs-type">int</span> <span class="hljs-title function_">kthread_test_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>    ENTER();<br>    test_kthread = kthread_run(kthread_test_func, <span class="hljs-literal">NULL</span>, <span class="hljs-string">&quot;kthread-test&quot;</span>);  _<span class="hljs-comment">//_创建线程_kthread-test_，并且运行</span><br>    <span class="hljs-keyword">if</span> (!test_kthread) &#123;<br>        ERR(<span class="hljs-string">&quot;kthread_run fail&quot;</span>);<br>        <span class="hljs-keyword">return</span> -ECHILD;<br>    &#125;<br>    EXIT();<br>    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>&#125;<br><br><span class="hljs-type">static</span> __exit <span class="hljs-type">void</span> <span class="hljs-title function_">kthread_test_exit</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>    ENTER();<br>    <span class="hljs-keyword">if</span> (test_kthread) &#123;<br>        DBG(<span class="hljs-string">&quot;kthread_stop&quot;</span>);<br>        kthread_stop(test_kthread); _<span class="hljs-comment">//_停止内核线程</span><br>        test_kthread = <span class="hljs-literal">NULL</span>;<br>    &#125;<br>    EXIT();<br>&#125;<br><br>module_init(kthread_test_init);<br>module_exit(kthread_test_exit);<br>MODULE_AUTHOR(<span class="hljs-string">&quot;czd,214241976@qq.com&quot;</span>);<br>MODULE_DESCRIPTION(<span class="hljs-string">&quot;Device_create Driver&quot;</span>);<br>MODULE_LICENSE(<span class="hljs-string">&quot;GPL&quot;</span>);<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、kthread-create-kthread-run&quot;&gt;&lt;a href=&quot;#一、kthread-create-kthread-run&quot; class=&quot;headerlink&quot; title=&quot;一、kthread_create &amp;amp; kthread_run&quot;&gt;</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>中断下半部-workqueue</title>
    <link href="http://example.com/posts/%E4%B8%AD%E6%96%AD%E4%B8%8B%E5%8D%8A%E9%83%A8-workqueue.html"/>
    <id>http://example.com/posts/%E4%B8%AD%E6%96%AD%E4%B8%8B%E5%8D%8A%E9%83%A8-workqueue.html</id>
    <published>2024-11-25T15:03:33.000Z</published>
    <updated>2024-11-25T15:03:51.061Z</updated>
    
    <content type="html"><![CDATA[<p>工作队列（work queue）是中断下半部的一种实现机制，主要用于耗时任务处理，由内核线程代表进程执行。工作队列运行于进程上下文，因此允许阻塞。</p><p>运行工作队列的内核线程，称为工作者线程（worker thread），可以使用系统默认的，也可以自行创建（通常无必要理由不推荐）。</p><p>使用工作队列方式：1）初始化工作队列；2）将“工作”（work）放入“工作队列中”。这样，对应的内核线程就会取出“工作”，执行其中的函数。</p><p>工作队列缺点：多个工作挤在某个内核线程中依次序执行，前面的函数如果执行得很慢，就会影响到后面的函数。</p><h3 id="一、内核数据结构与函数"><a href="#一、内核数据结构与函数" class="headerlink" title="一、内核数据结构与函数"></a>一、内核数据结构与函数</h3><p>work queue有关数据结构和函数，都位于&lt;linux&#x2F;workqueue.h&gt;。</p><h3 id="二、work-struct结构体"><a href="#二、work-struct结构体" class="headerlink" title="二、work_struct结构体"></a>二、work_struct结构体</h3><p>一个work_struct实例代表一个“工作”，工作包含了用户想要要执行的任务。</p><p>work_struct结构体定义：</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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">work_struct</span> &#123;</span><br>    <span class="hljs-type">atomic_long_t</span> data;<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">list_head</span> <span class="hljs-title">entry</span>;</span><br>    <span class="hljs-type">work_func_t</span> func;          <span class="hljs-comment">// 处理函数</span><br><span class="hljs-meta">#<span class="hljs-keyword">ifdef</span> CONFIG_LOCKDEP</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">lockdep_map</span> <span class="hljs-title">lockdep_map</span>;</span><br><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span><br>&#125;;<br><br><span class="hljs-keyword">typedef</span> <span class="hljs-title function_">void</span> <span class="hljs-params">(*<span class="hljs-type">work_func_t</span>)</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> work_struct *work)</span>;<br></code></pre></td></tr></table></figure><p>使用work queue时，步骤如下：<br />1）构造一个work_struct实例，设置处理函数。<br />2）把work_struct放入工作队列，内核线程会运行work中的函数（func）。</p><h3 id="三、使用work-queue"><a href="#三、使用work-queue" class="headerlink" title="三、使用work queue"></a>三、使用work queue</h3><h3 id="创建work"><a href="#创建work" class="headerlink" title="创建work"></a>创建work</h3><h5 id="静态创建"><a href="#静态创建" class="headerlink" title="静态创建"></a>静态创建</h5><p>宏DECLARE_WORK用来定义一个work_struct结构体，需要指定它的处理函数。<br />宏DECLARE_DELAYED_WORK用来定义一个delayed_work结构体，也需要指定它的处理函数。“delayed”指延时，意思是要让该“工作”运行时，可以通过该宏指定延时的时间。</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DECLARE_WORK(n, f)                        \</span><br><span class="hljs-meta">    struct work_struct n = __WORK_INITIALIZER(n, f)</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> DECLARE_DELAYED_WORK(n, f)                    \</span><br><span class="hljs-meta">    struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)</span><br></code></pre></td></tr></table></figure><p>delayed_work结构体，其实是一个work_struct和一个timer_list等成员的复合结构。</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><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">delayed_work</span> &#123;</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">work_struct</span> <span class="hljs-title">work</span>;</span> <span class="hljs-comment">// 工作队列的工作</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">timer_list</span> <span class="hljs-title">timer</span>;</span> <span class="hljs-comment">// 超时时间</span><br><br>    <span class="hljs-comment">/* target workqueue and CPU -&gt;timer uses to queue -&gt;work */</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">wq</span>;</span><br>    <span class="hljs-type">int</span> cpu;<br>&#125;;<br></code></pre></td></tr></table></figure><h5 id="动态创建"><a href="#动态创建" class="headerlink" title="动态创建"></a>动态创建</h5><p>宏INIT_WORK用来初始化work_struct结构体：</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> INIT_WORK(_work, _func)                        \</span><br><span class="hljs-meta">    __INIT_WORK((_work), (_func), 0)</span><br></code></pre></td></tr></table></figure><h3 id="四、创建工作队列"><a href="#四、创建工作队列" class="headerlink" title="四、创建工作队列"></a>四、创建工作队列</h3><p>Linux系统中已有现成的system_wq等工作队列，使用工作队列时，通常推荐用现成的。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// Linux中现成的工作队列</span><br><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_wq</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_highpri_wq</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_long_wq</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_unbound_wq</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_freezable_wq</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_power_efficient_wq</span>;</span><br><span class="hljs-keyword">extern</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">workqueue_struct</span> *<span class="hljs-title">system_freezable_power_efficient_wq</span>;</span><br></code></pre></td></tr></table></figure><p>如果需要自行创建，也有办法，可以使用create_workqueue或create_singlethread_workqueue。<br />create_workqueue会在SMP系统中，针对每个CPU，都创建一个内核线程和创建的工作队列对应。<br />create_singlethread_workqueue 只会有一个内核线程与工作队列对应。</p><h3 id="五、销毁工作队列"><a href="#五、销毁工作队列" class="headerlink" title="五、销毁工作队列"></a>五、销毁工作队列</h3><p>与创建工作队列相对的，是销毁工作队列，可以调用destroy_workqueue来执行该操作。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">destroy_workqueue</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> workqueue_struct *wq)</span>;<br></code></pre></td></tr></table></figure><h3 id="六、调度执行work"><a href="#六、调度执行work" class="headerlink" title="六、调度执行work"></a>六、调度执行work</h3><p>schedule_work调度执行一个具体的work，执行的work将会被挂入Linux提供的（默认system_wq）工作队列。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">bool</span> <span class="hljs-title function_">schedule_work</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> work_struct *work)</span>;<br></code></pre></td></tr></table></figure><p>如果想延迟执行work，可以调用schedule_delayed_work ，其功能类似于schedule_work，不过多了一个延迟。</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">bool</span> <span class="hljs-title function_">schedule_delayed_work</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> delayed_work *dwork,</span><br><span class="hljs-params">                     <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> delay)</span>;<br></code></pre></td></tr></table></figure><p>queue_work 跟schedule_work类似，区别在于schedule_work是在系统默认的工作队列上执行一个work，而queue_work 需要自行指定工作队列。</p><p>其实，schedule_work是利用queue_work实现的，例如系统默认的工作队列system_wq：</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">bool</span> <span class="hljs-title function_">schedule_work</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> work_struct *work)</span><br>&#123;<br>    <span class="hljs-keyword">return</span> queue_work(system_wq, work);<br>&#125;<br></code></pre></td></tr></table></figure><p>queue_delayed_work 跟schedule_delayed_work 类似，区别在于schedule_delayed_work 是在系统默认的工作队列上执行一个work，queue_delayed_work需要自行指定工作队列。类似地，schedule_delayed_work也是依赖于queue_delayed_work实现的。</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">bool</span> <span class="hljs-title function_">schedule_delayed_work</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> delayed_work *dwork,</span><br><span class="hljs-params">                     <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> delay)</span><br>&#123;<br>    <span class="hljs-keyword">return</span> queue_delayed_work(system_wq, dwork, delay);<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="七、等待work"><a href="#七、等待work" class="headerlink" title="七、等待work"></a>七、等待work</h3><p>flush_work 等待一个work执行完毕。如果该work已经被放入队列，那么本函数等它执行完毕，并且返回true；如果该work已经执行完毕才调用本函数，那么直接返回false。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">bool</span> <span class="hljs-title function_">flush_work</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> work_struct *work)</span>;<br></code></pre></td></tr></table></figure><p>flush_delayed_work 等待一个delayed_work执行完毕。如果这个delayed_work已经被放入队列，那么本函数等它执行完毕，并且返回true；如果这个delayed_work已经执行完毕才调用本函数，那么直接返回false。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">bool</span> <span class="hljs-title function_">flush_delayed_work</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> delayed_work *dwork)</span>;<br></code></pre></td></tr></table></figure><p>TIPS：前面提到过，delayed_work是一个复合了work_struct，timer_list等成员的结构体。</p><h3 id="八、等待work-queue"><a href="#八、等待work-queue" class="headerlink" title="八、等待work queue"></a>八、等待work queue</h3><p>flush_work是等待一个work执行完毕，而flush_workqueue是等待一个工作队列上所有work执行完毕。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">flush_workqueue</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> workqueue_struct *wq)</span><br></code></pre></td></tr></table></figure><hr><h3 id="九、work-queue的内部机制"><a href="#九、work-queue的内部机制" class="headerlink" title="九、work queue的内部机制"></a>九、work queue的内部机制</h3><p>Linux内核2.x 版本中，创建workqueue时会同步创建内核线程；<br />Linux内核4.x 版本中，内核线程和workqueue分开创建，较为复杂。</p><h3 id="Linux-2-x的工作队列创建过程"><a href="#Linux-2-x的工作队列创建过程" class="headerlink" title="Linux 2.x的工作队列创建过程"></a>Linux 2.x的工作队列创建过程</h3><p>kernel&#x2F;workqueue：</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><code class="hljs c">init_workqueues<br>keventd_wq = create_workqueue(<span class="hljs-string">&quot;events&quot;</span>); <span class="hljs-comment">// 创建名为&quot;events&quot;的工作队列</span><br>    __create_workqueue((name), <span class="hljs-number">0</span>, <span class="hljs-number">0</span>)<br>        for_each_possible_cpu(cpu) &#123;<br>            err = create_workqueue_thread(cwq, cpu); <span class="hljs-comment">// 创建用于工作队列的内核线程</span><br>                    p = kthread_create(worker_thread, cwq, fmt, wq-&gt;name, cpu); <span class="hljs-comment">// 创建内核线程</span><br></code></pre></td></tr></table></figure><p>对于每个CPU，都创建一个名为“events&#x2F;n”的内核线程，n是处理器编号，从0开始。</p><p>创建workqueue的同时，创建内核线程。</p><p>每个CPU上都有一个cpu_workqueue_struct，而每个cpu_workqueue_struct下只有1个线程用于work queue执行work。所有内核线程可以从同一个work queue取work。</p><h3 id="Linux-4-x的工作队列创建过程"><a href="#Linux-4-x的工作队列创建过程" class="headerlink" title="Linux 4.x的工作队列创建过程"></a>Linux 4.x的工作队列创建过程</h3><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><code class="hljs c">init_workqueues<br><span class="hljs-comment">/* initialize CPU pools */</span><br>for_each_possible_cpu(cpu) &#123;<br>    for_each_cpu_worker_pool(pool, cpu) &#123;<br>         <span class="hljs-comment">/* 对每一个CPU都创建2个worker_pool结构体，它是含有ID的 */</span><br>         <span class="hljs-comment">/*  一个worker_pool对应普通优先级的work，第2个对应高优先级的work */</span><br>&#125;<br><br><span class="hljs-comment">/* create the initial worker */</span><br>for_each_online_cpu(cpu) &#123;<br>    for_each_cpu_worker_pool(pool, cpu) &#123;<br>        <span class="hljs-comment">/* 对每一个CPU的每一个worker_pool，创建一个worker */</span><br><span class="hljs-comment">/* 每一个worker对应一个内核线程 */</span><br>        BUG_ON(!create_worker(pool));<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>create_worker：</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">struct</span> worker *<span class="hljs-title function_">create_worker</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> worker_pool *pool)</span><br>&#123;<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">worker</span> *<span class="hljs-title">worker</span> =</span> <span class="hljs-literal">NULL</span>;<br>    <span class="hljs-type">int</span> id = <span class="hljs-number">-1</span>;<br>    <span class="hljs-type">char</span> id_buf[<span class="hljs-number">16</span>];<br><br>    <span class="hljs-comment">/* ID is needed to determine kthread name */</span><br>    id = ida_simple_get(&amp;pool-&gt;worker_ida, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, GFP_KERNEL);<br>    <span class="hljs-keyword">if</span> (id &lt; <span class="hljs-number">0</span>)<br>        <span class="hljs-keyword">goto</span> fail;<br><br>    worker = alloc_worker(pool-&gt;node);<br>    <span class="hljs-keyword">if</span> (!worker)<br>        <span class="hljs-keyword">goto</span> fail;<br><br>    worker-&gt;pool = pool;<br>    worker-&gt;id = id;<br>  <br>    <span class="hljs-keyword">if</span> (pool-&gt;cpu &gt;= <span class="hljs-number">0</span>)<br>        <span class="hljs-built_in">snprintf</span>(id_buf, <span class="hljs-keyword">sizeof</span>(id_buf), <span class="hljs-string">&quot;%d:%d%s&quot;</span>, pool-&gt;cpu, <span class="hljs-comment">// 在哪个CPU上运行</span><br>             id, <span class="hljs-comment">// poll中第几个线程</span><br>             pool-&gt;attrs-&gt;nice &lt; <span class="hljs-number">0</span>  ? <span class="hljs-string">&quot;H&quot;</span> : <span class="hljs-string">&quot;&quot;</span>);  <span class="hljs-comment">// H: 高优先级</span><br>    <span class="hljs-keyword">else</span><br>        <span class="hljs-built_in">snprintf</span>(id_buf, <span class="hljs-keyword">sizeof</span>(id_buf), <span class="hljs-string">&quot;u%d:%d&quot;</span>, pool-&gt;id, id);<br><br>    worker-&gt;task = kthread_create_on_node(worker_thread, worker, pool-&gt;node,<br>                          <span class="hljs-string">&quot;kworker/%s&quot;</span>, id_buf); <span class="hljs-comment">// 内核线程的名字</span><br>    ...<br>&#125;<br></code></pre></td></tr></table></figure><p>创建号内核线程（”kworker&#x2F;n:id”）后，再创建workqueue</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><code class="hljs c">init_workqueues<br>    system_wq = alloc_workqueue(<span class="hljs-string">&quot;events&quot;</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>);<br>        __alloc_workqueue_key<br>            wq = kzalloc(<span class="hljs-keyword">sizeof</span>(*wq) + tbl_size, GFP_KERNEL); <span class="hljs-comment">// 分配workqueue_struct</span><br>            alloc_and_link_pwqs(wq)  <span class="hljs-comment">// 跟worker_poll建立联系</span><br></code></pre></td></tr></table></figure><p>每个CPU对应2个woker_pool：一个普通的worker_pool，一个高优先级的worker_pool。每个线程池包含多个内核线程，用于执行同一个worker queue的work。work_pool的线程名，形如”kworker&#x2F;n:idH”，n代表CPU编号，id是子线程编号，H代表高优先级，如果普通优先级则为空。</p><p>对于CPU 0：<br />普通worker_pool线程名，形如”kworker&#x2F;0:0”，”kworker&#x2F;0:1”，”kworker&#x2F;0:2”。:<br />高优先级的worker_pool线程名，形如”kworker&#x2F;0:0H”，”kworker&#x2F;0:1H”，”kworker&#x2F;0:2H”。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;工作队列（work queue）是中断下半部的一种实现机制，主要用于耗时任务处理，由内核线程代表进程执行。工作队列运行于进程上下文，因此允许阻塞。&lt;/p&gt;
&lt;p&gt;运行工作队列的内核线程，称为工作者线程（worker thread），可以使用系统默认的，也可以自行创建（通常无</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>中断下半部-tasklet</title>
    <link href="http://example.com/posts/%E4%B8%AD%E6%96%AD%E4%B8%8B%E5%8D%8A%E9%83%A8-tasklet.html"/>
    <id>http://example.com/posts/%E4%B8%AD%E6%96%AD%E4%B8%8B%E5%8D%8A%E9%83%A8-tasklet.html</id>
    <published>2024-11-25T15:02:32.000Z</published>
    <updated>2024-11-25T15:03:00.857Z</updated>
    
    <content type="html"><![CDATA[<p>tasklet 是中断下半部的一种实现机制，主要用于小任务处理，耗时较短、不能阻塞的任务，用tasklet处理较合适。对于耗时较长，可以用work queue（工作队列）来处理。</p><p>tasklet和内核定时器timer_list都是通过软中断方式来实现的。</p><h3 id="一、tasklet结构体"><a href="#一、tasklet结构体" class="headerlink" title="一、tasklet结构体"></a>一、tasklet结构体</h3><p>中断下半部用结构体tasklet_struct来表示</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;linux/interrupt.h&gt;</span></span><br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tasklet_struct</span></span><br><span class="hljs-class">&#123;</span><br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tasklet_struct</span> *<span class="hljs-title">next</span>;</span><br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> state;<br>    <span class="hljs-type">atomic_t</span> count;<br>    <span class="hljs-type">void</span> (*func)(<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>);<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> data;<br>&#125;;<br></code></pre></td></tr></table></figure><p>其中，state有2位：<br />1）bit0：表示TASKLET_STATE_SCHED<br />等于1，表示已经执行了tasklet_schedule，该把tasklet放入队列了。tasklet_schedule会判断该位，如果已经等于1，那么它就不会再次把tasklet放入队列。</p><p>2）bit1：表示TASKLET_STATE_RUN<br />等于1，表示正在运行tasklet中的func函数。函数执行完毕后，内核会把该位清0。</p><p>count表示该tasklet是否使能：值0表示使能了，非0表示被禁止了。对于count非0的tasklet，func()不会被执行。</p><p>data 是传递给func()的参数。</p><h3 id="二、初始化tasklet-strcut"><a href="#二、初始化tasklet-strcut" class="headerlink" title="二、初始化tasklet_strcut"></a>二、初始化tasklet_strcut</h3><h4 id="静态初始化："><a href="#静态初始化：" class="headerlink" title="静态初始化："></a>静态初始化：</h4><p>宏初始化tasklet_struct</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><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> DECLARE_TASKLET(name, func, data) \</span><br><span class="hljs-meta">struct tasklet_struct name = &#123; NULL, 0, ATOMIC_INIT(0), func, data &#125;;</span><br><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> DECLARE_TASKLET_DISABLED(name, func, data) \</span><br><span class="hljs-meta">struct tasklet_struct name = &#123; NULL, 0, ATOMIC_INIT(1), func, data &#125;;</span><br></code></pre></td></tr></table></figure><p>使用DECLARE_TASKLET定义的tasklet结构体，它是使能的。<br />使用DECLARE_TASKLET_DISABLED定义的tasklet结构体，它是禁止的。使用之前要先调用tasklet_enable使能之。</p><h5 id="动态初始化："><a href="#动态初始化：" class="headerlink" title="动态初始化："></a>动态初始化：</h5><p>也可用函数tasklet_init()初始化tasklet结构体：<br>data是func的参数</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><code class="hljs c">tasklet_init(<span class="hljs-keyword">struct</span> tasklet_struct *t,<br>             <span class="hljs-type">void</span> (*func)(<span class="hljs-type">unsigned</span> <span class="hljs-type">long</span>), <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> data);<br></code></pre></td></tr></table></figure><h3 id="三、使能-禁止tasklet"><a href="#三、使能-禁止tasklet" class="headerlink" title="三、使能&#x2F;禁止tasklet"></a>三、使能&#x2F;禁止tasklet</h3><p>tasklet_enable将count加1；tasklet_disable将count减1。</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_enable</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> tasklet_struct *t)</span>;  <span class="hljs-comment">// 使能</span><br><br><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_disable</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> tasklet_struct *t)</span>; <span class="hljs-comment">// 禁止</span><br></code></pre></td></tr></table></figure><h3 id="四、调度tasklet"><a href="#四、调度tasklet" class="headerlink" title="四、调度tasklet"></a>四、调度tasklet</h3><p>将tasklet放入链表，并设置它的TASKLET_STATE_SCHED状态为1。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_schedule</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> tasklet_struct *t)</span>;<br></code></pre></td></tr></table></figure><h3 id="五、kill-tasklet"><a href="#五、kill-tasklet" class="headerlink" title="五、kill tasklet"></a>五、kill tasklet</h3><p>从链表中删除tasklet。如果一个tasklet未被调度，tasklet_kill会将它的TASKLET_STATE_SCHED状态清0；如果一个tasklet已被调度，tasklet_kill会等待它执行完毕，再把它的TASKLET_STATE_SCHED状态清0。<br />通常，在卸载驱动程序（module_exit）时，调用task_kill。</p><p>tasklet_kill_immediate 与tasklet_kill区别是，前者会立即移除tasklet，二不论tasklet是否处于TASKLET_STATE_SCHED状态。</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><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_kill</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> tasklet_struct *t)</span>; <span class="hljs-comment">// 移除tasklet</span><br><br><span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_kill_immediate</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> tasklet_struct *t, <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> cpu)</span>; <span class="hljs-comment">// 立即移除tasklet</span><br></code></pre></td></tr></table></figure><h3 id="六、tasklet使用方法"><a href="#六、tasklet使用方法" class="headerlink" title="六、tasklet使用方法"></a>六、tasklet使用方法</h3><p>先定义tasklet实例，需要使用时调用tasklet_schedule，驱动卸载前调用tasklet_kill。tasklet_schedule只是将tasklet放入内核队列，其func函数会在软中断执行过程中被调用。</p><h3 id="七、tasklet内部实现机制"><a href="#七、tasklet内部实现机制" class="headerlink" title="七、tasklet内部实现机制"></a>七、tasklet内部实现机制</h3><p>前面讲过，tasklet是通过软中断实现，属于TASKLET_SOFTIRQ类型的软中断。入口函数tasklet_action。</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><code class="hljs c"><span class="hljs-comment">// kernel/softirq.c</span><br><br><span class="hljs-type">void</span> __init <span class="hljs-title function_">softirq_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>&#123;<br>    <span class="hljs-type">int</span> cpu;<br><br>    for_each_possible_cpu(cpu) &#123;<br>        per_cpu(tasklet_vec, cpu).tail =<br>            &amp;per_cpu(tasklet_vec, cpu).head;<br>        per_cpu(tasklet_hi_vec, cpu).tail =<br>            &amp;per_cpu(tasklet_hi_vec, cpu).head;<br>    &#125;<br><br>    open_softirq(TASKLET_SOFTIRQ, tasklet_action); <span class="hljs-comment">// 注册TASKLET_SOFTIRQ类型软中断(普通软中断)及其处理函数</span><br>    open_softirq(HI_SOFTIRQ, tasklet_hi_action);   <span class="hljs-comment">// 高优先级软中断</span><br>&#125;<br></code></pre></td></tr></table></figure><p>在软中断的初始化（softirq_init）末尾，通过open_softirq注册TASKLET_SOFTIRQ类型的软中断（即tasklet）及其处理函数（即tasklet_action）。</p><p>驱动程序调用tasklet_schedule时，会设置tasket的state为TASKLET_STATE_SCHED，并把它放入某个链表：</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><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_schedule</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> tasklet_struct *t)</span><br>&#123;<br>    <span class="hljs-comment">// 1. 如果未设置为SCHED，则设置为SCHED并放入队列</span><br>    <span class="hljs-keyword">if</span> (!test_and_set_bit(TASKLET_STATE_SCHED, &amp;t-&gt;state)) <span class="hljs-comment">// 设置tasklet.state为TASKLET_STATE_SCHED</span><br>        __tasklet_schedule(t);<br>&#125;<br><br><span class="hljs-type">void</span> __tasklet_schedule(<span class="hljs-keyword">struct</span> tasklet_struct *t)<br>&#123;<br>    <span class="hljs-type">unsigned</span> <span class="hljs-type">long</span> flags;<br><br>    local_irq_save(flags); <span class="hljs-comment">// 保存中断</span><br>    <span class="hljs-comment">// 2. 放入队列</span><br>    t-&gt;next = <span class="hljs-literal">NULL</span>;<br>    *__this_cpu_read(tasklet_vec.tail) = t;<br>    __this_cpu_write(tasklet_vec.tail, &amp;(t-&gt;next));<br>    raise_softirq_irqoff(TASKLET_SOFTIRQ); <span class="hljs-comment">// 唤醒中断, 会导致调用该类型软中断对应的处理函数</span><br>    local_irq_restore(flags); <span class="hljs-comment">// 恢复中断到flags状态</span><br>&#125;<br>EXPORT_SYMBOL(__tasklet_schedule);<br></code></pre></td></tr></table></figure><p>产生硬件中断时，讹你好处理完硬件中断后，会处理软中断。对于TASKLET_SOFTIRQ软中断，会调用tasklet_action函数。</p><p>执行过程是：从队列中找到tasklet，进行状态判断后执行func函数，从队列中删除tasklet。<br />可知：<br />1）tasklet_schedule 调度tasklet时，其中的函数并不会立即执行，而只是把tasklet放入队列；<br />2）调用一次tasklet_schedule，只会导致tasklet的函数被执行一次；<br />3）如果tasklet的函数尚未执行，多次调用tasklet_schedule也是少的，只会放入队列一次。</p><p>普通软中断处理函数tasklet_action：</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><code class="hljs c"><span class="hljs-type">static</span> __latent_entropy <span class="hljs-type">void</span> <span class="hljs-title function_">tasklet_action</span><span class="hljs-params">(<span class="hljs-keyword">struct</span> softirq_action *a)</span><br>&#123;<br>    <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tasklet_struct</span> *<span class="hljs-title">list</span>;</span><br><br>    local_irq_disable();<br>    <span class="hljs-built_in">list</span> = __this_cpu_read(tasklet_vec.head);<br>    __this_cpu_write(tasklet_vec.head, <span class="hljs-literal">NULL</span>);<br>    __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&amp;tasklet_vec.head));<br>    local_irq_enable();<br><br>    <span class="hljs-keyword">while</span> (<span class="hljs-built_in">list</span>) &#123;<br>        <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tasklet_struct</span> *<span class="hljs-title">t</span> =</span> <span class="hljs-built_in">list</span>;<br><br>        <span class="hljs-built_in">list</span> = <span class="hljs-built_in">list</span>-&gt;next; <span class="hljs-comment">// 1. 从列表中去除每一项</span><br><br>        <span class="hljs-keyword">if</span> (tasklet_trylock(t)) &#123;<br>            <span class="hljs-keyword">if</span> (!<span class="hljs-type">atomic_read</span>(&amp;t-&gt;count)) &#123;<br>                <span class="hljs-comment">// 2. 判断：如果不是SCHED状态，就是有BUG</span><br>                <span class="hljs-keyword">if</span> (!test_and_clear_bit(TASKLET_STATE_SCHED,<br>                            &amp;t-&gt;state))<br>                    BUG();<br>                t-&gt;func(t-&gt;data); <span class="hljs-comment">// 3. 执行tasklet的func</span><br>                tasklet_unlock(t);<br>                <span class="hljs-keyword">continue</span>;<br>            &#125;<br>            tasklet_unlock(t);<br>        &#125;<br><br>        local_irq_disable();<br>        <span class="hljs-comment">// 4. 从队列中取出</span><br>        t-&gt;next = <span class="hljs-literal">NULL</span>;<br>        *__this_cpu_read(tasklet_vec.tail) = t;<br>        __this_cpu_write(tasklet_vec.tail, &amp;(t-&gt;next));<br><br>        __raise_softirq_irqoff(TASKLET_SOFTIRQ);<br>        local_irq_enable();<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;tasklet 是中断下半部的一种实现机制，主要用于小任务处理，耗时较短、不能阻塞的任务，用tasklet处理较合适。对于耗时较长，可以用work queue（工作队列）来处理。&lt;/p&gt;
&lt;p&gt;tasklet和内核定时器timer_list都是通过软中断方式来实现的。&lt;/p</summary>
      
    
    
    
    <category term="kernel" scheme="http://example.com/categories/kernel/"/>
    
    
    <category term="kernel" scheme="http://example.com/tags/kernel/"/>
    
  </entry>
  
</feed>
