<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.5">Jekyll</generator><link href="https://lizhiduo.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lizhiduo.github.io/" rel="alternate" type="text/html" /><updated>2019-05-04T13:54:22+08:00</updated><id>https://lizhiduo.github.io/feed.xml</id><title type="html">lizhiduo</title><subtitle>李志铎的个人博客</subtitle><author><name>ZhiDuo Li</name></author><entry><title type="html">linux process</title><link href="https://lizhiduo.github.io/2019/05/04/process/" rel="alternate" type="text/html" title="linux process" /><published>2019-05-04T00:00:00+08:00</published><updated>2019-05-04T00:00:00+08:00</updated><id>https://lizhiduo.github.io/2019/05/04/process</id><content type="html" xml:base="https://lizhiduo.github.io/2019/05/04/process/">&lt;p&gt;主要了解进程和线程在linux中是如何进行描述和管理的。&lt;/p&gt;

&lt;h4 id=&quot;进程线程&quot;&gt;进程/线程&lt;/h4&gt;

&lt;p&gt;进程是处于执行期的程序。但进程不仅仅是一段可执行的程序代码。通常还要包含其它资源，像打开的文件，挂起的信号，映射的内存地址空间，处理器状态等。进程就是处于执行时期的程序以及相关资源的总称，也就是&lt;strong&gt;资源分配的基本单位&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;线程是进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。&lt;strong&gt;内核调度的对象是线程，而不是进程&lt;/strong&gt;。在linux里没有特别区分线程和进程。对于linux而言，线程不过是一种特殊的进程（和其它进程共享某些资源的进程）。&lt;/p&gt;

&lt;h4 id=&quot;进程控制块pcb-process-control-block&quot;&gt;进程控制块(PCB， process control block)&lt;/h4&gt;

&lt;p&gt;操作系统管理控制进程运行所用的信息集合。操作系统用PCB来描述进程的基本情况以及运行变化的过程。每个进程都在操作系统中有一个对应的PCB。进程的组织和管理都是通过对PCB的组织管理实现的。&lt;/p&gt;

&lt;p&gt;在linux是用task_struct这个结构来描述PCB的。在linux系统中是通过&lt;strong&gt;预先分配和重复使用&lt;/strong&gt;task_struct，这样可以避免动态分配和释放所带来的资源消耗，让进程创建的更迅速。因为task_struct还是比较大的，32位机器上，大约有1.7KB。&lt;/p&gt;

&lt;p&gt;PCB包含什么信息，如何组织？&lt;/p&gt;

&lt;p&gt;PCB包含的数据能完整地描述一个正在执行的程序：它的标识信息、进程的地址空间、挂起的信号、打开的文件、进程的状态以及其它更多的信息(/proc/$pid)。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/process/pcb.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;PCB的组织会用到很多数据结构，因为应用场景不同，所以需要使用不同的数据结构。调度算法中会将PCB挂在链表上，不同状态的进程形成不同的链表（就绪链表、阻塞链表）；父子进程的关系用树来描述（可用pstree查看），CFS调度算法会用到红黑树；通过pid查找进程则是用hash表的结构。&lt;/p&gt;

&lt;p&gt;进程个数是否有上限？&lt;/p&gt;

&lt;p&gt;内核通过一个唯一的进程标识值（PID）来标识每个进程。为了与老版本的UNIx和linux兼容，PID的最大值默认设置为32768（short int最大值）。若不考虑老式系统兼容，可以修改 /proc/sys/kernel/pid_max 来提高上限。linux系统里面，每个用户的pid也是有限的，可以通过 ulimit -a 查看。&lt;/p&gt;

&lt;h4 id=&quot;进程状态&quot;&gt;进程状态&lt;/h4&gt;

&lt;p&gt;操作系统包括实时系统对应进程一般都有 3 个状态，进程在有 CPU 时对应运行态，无 CPU 时对应就绪态和睡眠态。就绪态指所有资源都准备好，只要有 CPU 就可以运行了。睡眠指有资源还未准备好，比如读串口数据时，数
据还未发送。此时有 CPU 也无法运行，需要等资源准备好后变成就绪态，然后得到 CPU 后才能变成运行态，其转换关系下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/process/sta.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;linux状态进程转换图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/process/linux-sta.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;TASK_RUNNING（运行）：正在运行或者在就绪队列中等待的进程。&lt;/p&gt;

&lt;p&gt;TASK_INTERRUPTIBLE（可中断）：可中断阻塞状态，除等待资源到位外还可以被其它进行的信号唤醒。&lt;/p&gt;

&lt;p&gt;TASK_UNINTERRUPTIBLE（不可中断）：不可中断阻塞状态，只能被等待资源到位唤醒。&lt;/p&gt;

&lt;p&gt;TASK_STOPPED（停止）：暂停状态，这种状态发生在收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号的时候。此外，在调试期间接收到任何信号，都会使进程进入这种状态。&lt;/p&gt;

&lt;p&gt;TASK_ZOMBIE（僵尸）：僵尸状态，表示进程结束但未消亡的一种状态。该状态下除了PCB资源未释放，不占用其它资源（一般是子进程退出，父进程执行waitpid获取子进程死亡原因之前的子进程状态）。&lt;/p&gt;

&lt;p&gt;以上几种状态，通常对应的PS命令下的stat是：TASK_RUNNING(R)、TASK_INTER(S)、TASK_UNINTER(D)、TASK_ZOMBIE(Z)、TASK_STOP(T)。&lt;/p&gt;

&lt;p&gt;暂停状态是进程在运行过程中，通过外部强制让进程进入的状态。通过这种方法可以指定进程的 CPU 占用率。使用ctrl+z(暂停)， fg/bg（前/后台继续），cpulimit（控制cpu占用率）等命令控制。&lt;/p&gt;

&lt;p&gt;就绪和执行在linux中都是用TASK_RUNNING 标记，进程的调度算法只关注就绪和执行这两个状态的切换。&lt;/p&gt;

&lt;p&gt;深度睡眠和浅度睡眠区别，深度睡眠只有资源到位才醒，收到信号也不醒，浅度睡眠资源到位或收到信号都会醒。&lt;/p&gt;

&lt;p&gt;睡眠和暂停区别，睡眠是代码中未得到资源主动进入的状态，暂停是程序外部强制进程进入的状态。&lt;/p&gt;</content><author><name>ZhiDuo Li</name></author><summary type="html">主要了解进程和线程在linux中是如何进行描述和管理的。</summary></entry><entry><title type="html">C语言面向对象</title><link href="https://lizhiduo.github.io/2019/04/14/C_design/" rel="alternate" type="text/html" title="C语言面向对象" /><published>2019-04-14T00:00:00+08:00</published><updated>2019-04-14T00:00:00+08:00</updated><id>https://lizhiduo.github.io/2019/04/14/C_design</id><content type="html" xml:base="https://lizhiduo.github.io/2019/04/14/C_design/">&lt;p&gt;本文主要想表达C语言设计也要有面向对象思想，但绝不是要去模拟面向对象语言本身。&lt;/p&gt;

&lt;h2 id=&quot;简介&quot;&gt;简介&lt;/h2&gt;

&lt;p&gt;C语言一旦达到一定规模，就特别强调良好的架构设计，以保证代码的可读性、简洁以及可复用性。此时，软件设计者势必要用C语言来做面向对象的设计，我们可以看到Linux内核、驱动等大型的软件框架里面充满了面向对象的思想。&lt;/p&gt;

&lt;h3 id=&quot;面向过程与面向对象&quot;&gt;面向过程与面向对象&lt;/h3&gt;

&lt;p&gt;面向过程是以函数作为最基本的逻辑单元构建出来的整个业务系统。随着处理问题越来月复杂，实现业务的函数也越来越大，越来越多。不得不细化和归类函数，这便是模块化的思想。 但当处理问题的复杂程度超出了函数的能力范围，于是人们不得不开始考虑编程的基本单元。于是数据和算法的结合，对象就诞生了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;面向对象不是抛弃面向过程，而是在面向过程的基础上，把数据加入抽象的范畴。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;相比面向过程，面向对象最主要的进化就是把问题领域中的事物给对象化了，对象包括属性和行为。在这个抽象的过程中，为了描述私有性，有了封装；为了描述传承性，有了继承；为了描述差异性，有了多态。&lt;/p&gt;

&lt;h3 id=&quot;封装&quot;&gt;封装&lt;/h3&gt;

&lt;p&gt;封装的作用：隐藏细节，让代码模块化。&lt;/p&gt;

&lt;h4 id=&quot;提炼数据结构&quot;&gt;提炼数据结构&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/c_design/1551528325975.png&quot; alt=&quot;1551528325975&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如上图所示是一个WAV文件的格式说明表，刚入门的人，可能就会通过指针偏移相应的字节数，然后来获取相应的成员信息。这样写出来的代码肯定很难读到，并且把数据和代码耦合到了一起。假如我们把表格提炼一个数据结构之后，会发现获取相应字段的信息变得非常方便。直接访问相对应的成员变量即可。而且改变也变得非常简单。提炼数据结构其实在我们平时的工程代码里面也是 随处可见的。&lt;/p&gt;

&lt;h3 id=&quot;封装行为&quot;&gt;封装行为&lt;/h3&gt;

&lt;p&gt;实例分析&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;命令解析器（过程式设计）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/c_design/cmd.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;面向过程设计的代码明显高耦合，要添加功能或修改某一个功能时会需要修改大部分内容。&lt;/p&gt;

&lt;p&gt;面向对象设计：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/c_design/cmd_oo.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;面向对象的设计思想，把命令看成一个对象，将命令码和命令操作绑定到一起。提供一个统一的注册接口。这样设计代码后，很明显的gpio的代码更内聚了。cmd和其它模块之间的耦合性也降低了。新增其它模块也不需要修改已有代码了。&lt;/p&gt;

&lt;p&gt;代码：&lt;a href=&quot;https://github.com/lizhiduo/cmdManager&quot;&gt;https://github.com/lizhiduo/cmdManager&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;菜单（过程式设计）&lt;/p&gt;

    &lt;p&gt;&lt;img src=&quot;/images/blog/c_design/menu.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如上图所示，是确认键和取消键的功能函数，但是对应的每一个菜单都会有不同的处理函数，每新增加一个菜单处理，就需要加一条switch-case。当菜单越来越多，就会有大量的switch-case代码去匹配具体的操作。&lt;/p&gt;

&lt;p&gt;面向对象设计：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/c_design/menu_oo.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;将按键看做一个对象，然后将属性和行为封装一起。避免了重复switch-case操作。&lt;/p&gt;

&lt;p&gt;代码：&lt;a href=&quot;https://github.com/lizhiduo/menu.git&quot;&gt;https://github.com/lizhiduo/menu.git&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;继承&quot;&gt;继承&lt;/h3&gt;

&lt;p&gt;继承是指一个对象直接使用另一个对象的属性和方法。&lt;/p&gt;

&lt;p&gt;目的是为了—代码重用。&lt;/p&gt;

&lt;p&gt;继承这种代码重用手段，本质上是对象的重用，基本都发生在运行时；而程序设计中还有另外一个层次的重用，这个就是纯粹的源代码的重用，这个是发生在编译时候。这个就是泛型编程（这里不予讨论)。&lt;/p&gt;

&lt;p&gt;C语言中如何达到类继承的这种作用？其实就是提炼软件中间层，其中间层的主要功能就是实现通用逻辑，对上统一接口，对下定义框架。linux里面大量使用了软件分层的思想。&lt;/p&gt;

&lt;p&gt;实例分析：&lt;/p&gt;

&lt;p&gt;linux的input子系统核心层主要实现了应用访问的file_operations接口，对输入设备驱动提供了一组注册和上报数据的接口（具体可以分析drivers/input/evdev.c）。&lt;/p&gt;

&lt;p&gt;键盘驱动的数据流向：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/c_design/input.png&quot; alt=&quot;1555213308843&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;多态&quot;&gt;多态&lt;/h3&gt;

&lt;p&gt;多态是同一操作作用于不同的对象，可以有不同的解释，产生不同的执行结果。在运行时可以通过指向基类的指针，来调用派生类中的实现方法。&lt;/p&gt;

&lt;p&gt;多态的目的—–接口重用&lt;/p&gt;

&lt;p&gt;继承提供了复用，但是现实中个体往往存在差异性。多态就是用于在统一的接口这个框中描述个体的差异性。多态是继承这种手法的延续。&lt;/p&gt;

&lt;p&gt;linux内核中同样是存在很多实例，主要是中间层通过判断底层是否实现相应行为（通过函数指针判断），未实现证明不特殊，使用中间层的统用逻辑即可(下图代码来自内核fbmem.c)。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/blog/c_design/fb.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;</content><author><name>ZhiDuo Li</name></author><summary type="html">本文主要想表达C语言设计也要有面向对象思想，但绝不是要去模拟面向对象语言本身。</summary></entry><entry><title type="html">Linux Lock</title><link href="https://lizhiduo.github.io/2019/02/17/linux_lock/" rel="alternate" type="text/html" title="Linux Lock" /><published>2019-02-17T00:00:00+08:00</published><updated>2019-02-17T00:00:00+08:00</updated><id>https://lizhiduo.github.io/2019/02/17/linux_lock</id><content type="html" xml:base="https://lizhiduo.github.io/2019/02/17/linux_lock/">&lt;p&gt;本文主要讨论内核中一些常用的锁机制，以及使用注意事项。&lt;/p&gt;

&lt;h2 id=&quot;主要内容&quot;&gt;主要内容&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;原子性&lt;/li&gt;
    &lt;li&gt;RMW&lt;/li&gt;
    &lt;li&gt;atomic&lt;/li&gt;
    &lt;li&gt;语义整体&lt;/li&gt;
    &lt;li&gt;spinlock&lt;/li&gt;
    &lt;li&gt;mutex&lt;/li&gt;
    &lt;li&gt;lookup detector&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;预备知识&quot;&gt;预备知识&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;处理器种类： CISC(复杂指令集) 和 RISC(精简指令集)。其中CISI处理器(X86)可以直接在内存上做加法，这在单核上可以保证原子性，多核上则不能保证（因在内存的访问不是原子的,可以细分为读-改-写三个过程，所以多核上单个指令完成的操作也可以被干扰。）；而RISC处理器(ARM)的所有操作都必须在CPU内部进行（如果要对内存里的某一个数加一，则需要先从内存load这个数到CPU寄存器里，然后加一，最后再store到内存里，这是一个典型的‘读-改-写’序列）。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;blockquote&gt;
    &lt;p&gt;如下图所示，分别是x86以及arm架构下的&lt;strong&gt;加一&lt;/strong&gt;汇编代码：&lt;/p&gt;

    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int main()
{
  4004d6:	55                   	push   %rbp
  4004d7:	48 89 e5             	mov    %rsp,%rbp
    int a = 20;
  4004da:	c7 45 fc 14 00 00 00 	movl   $0x14,-0x4(%rbp)
    a++;
  4004e1:	83 45 fc 01          	addl   $0x1,-0x4(%rbp)
    return 0;
  4004e5:	b8 00 00 00 00       	mov    $0x0,%eax
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int main()
{
    8380:	e52db004 	push	{fp}		; (str fp, [sp, #-4]!)
    8384:	e28db000 	add	fp, sp, #0
    8388:	e24dd00c 	sub	sp, sp, #12
    int a = 20;
    838c:	e3a03014 	mov	r3, #20
    8390:	e50b3008 	str	r3, [fp, #-8]
    a++;
    8394:	e51b3008 	ldr	r3, [fp, #-8]
    8398:	e2833001 	add	r3, r3, #1
    839c:	e50b3008 	str	r3, [fp, #-8]
    return 0;
    83a0:	e3a03000 	mov	r3, #0
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/blockquote&gt;

&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;RISC处理器，哪怕一个整数+1操作，也不是原子的，要经过‘读-改-写（R-M-W）’。RMW序列不是原子的，所以可能引起并发问题。例如两个线程同时对一个变量做加法，会存在预期和结果不一样，程序可能会这样执行，T1做a++,第一步先将a的值LDR到寄存器中，但是此时T2抢占了CPU然后执行完a++后，将CPU让给T1，但是此时T1并不会重新去LDR a的值，导致最终和我们想要的结果不一样。&lt;/li&gt;
    &lt;li&gt;部分CPU在设计上，为了避免RMW非原子序列，会将寄存器的写操作分成 SET_REG 和 CLR_REG， 此为原子操作。另外还有一种为‘bitband’的寄存器组，它将寄存器的每一个bit都映射成一个独立的寄存器（影子寄存器），对影子寄存器写0或1，就会作用于原始寄存器相应bit。这都是从硬件设计上实现原子操作。&lt;/li&gt;
    &lt;li&gt;排它性的‘ldr/sttr’指令,用来实现对内存的原子操作（或者说互斥操作），比如 ARM的LDREX/STREX指令。其实现原理就是将并行的load/store变成串行的，先写会成功，后写的若失败了，会重新load/store。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;实际应用： 程序毫无逻辑地随机死、通常有两个原因导致：（1）内存越界； （2）加锁不完整，存在并发的问题。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;1-原子性&quot;&gt;1. 原子性&lt;/h1&gt;
&lt;p&gt;原子操作是不可分割的，在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中， 能够在单条指令中完成的操作都可以认为是” 原子操作”，因为中断只能发生于指令之间。&lt;/p&gt;

&lt;h1 id=&quot;3-atomic&quot;&gt;3. atomic&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;atomic保证整数操作的原子性。而Linux内核将这些指令封装为一组原子锁API，用于实现整数的原子操作，比如 atomic_add()/atomic_sub()/atomic_inc()/atomic_dec() …&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;原子锁是Linux里最底层的锁，它是其他各种锁（如mutex、信号量等）实现的基础。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;原子锁只能对整数的操作加锁、不能对复杂数据结构的操作（如结构体）加锁，而加锁一定要锁住一个语义完整的整体。因此实际编程中用处不大。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;注atomich中有着它的定义以及操作函数typedef-struct--volatile-int-counter--atomic_t&quot;&gt;注：atomic.h中有着它的定义以及操作函数。typedef struct { volatile int counter; } atomic_t;&lt;/h3&gt;

&lt;p&gt;临界区是一些语义关联的事物构成的一个语义完整的整体。临界区需要加锁，以构成一个‘自洽、完整、统一、不自相矛盾’的整体。在进入临界区的时候，要么所以事情都没开始，要么所有的事情都已经做完了。&lt;/p&gt;

&lt;h1 id=&quot;4-spinlock&quot;&gt;4. spinlock&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;工作逻辑：&lt;strong&gt;核内锁调度，核间自旋&lt;/strong&gt;。 核内锁调度：在当前核内部，直接关闭调度器，使当前线程独占当前核，其它线程无法执行；核间自旋：在多核上，当前核被锁后，其它核需要忙等，直到当前核释放锁为止。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;spinlock实现原理：在核内直接锁调度，核间才是自旋，所以自旋只有在双核或多核里面才能体现出来，在单核上就是只是锁调度器。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;spinlock的应用场景：用于锁耗时特别短的区间；被锁区间绝对不能进行睡眠。（睡眠和唤醒需要进行两次上下文切换，这种开销可能会比我死等更大，这也是为什么需要spinlock锁住的区间需要耗时特别短，而且不能存在睡眠）。&lt;/li&gt;
    &lt;li&gt;如果临界资源只被线程访问，那么线程里调用spin_lock()/spin_unlock()即可。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;如果临界资源既要被线程访问，又要被中断访问，因为spinlock本身不会关中断，那么线程里就需要使用spinlock的修改版本 -spin_lock_irqsave()/spin_lock_irqrestore()，而在中断里仍旧使用spin_lock()/spin_unlock() 即可。
spin_lock_irqsave() = spin_lock() + local_irq_save() 【保存“中断使能配置”（即当前系统里中断的开关状态 ） + 关中断 】 
spin_lock_irqrestore () = spin_unlock() + local_irq_restore() 【恢复“中断使能配置” + 开中断】&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;h3 id=&quot;多核--中断存在竞争情况&quot;&gt;多核 + 中断存在竞争情况&lt;/h3&gt;
  &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        线程与线程
        线程与中断
        中断与中断
        此核与彼核
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; cpu0                   cpu1
  T1                    T3
  T2                    T4
  IRQ0                  IRQ1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;CPU0上有线程T1、T2和中断IRQ0都会访问某个临界区，CPU1上的线程T3、T4和IRQ1也会访问这个临界 区。这就形成了一个复杂的竞争网络,消除所以的竞态只需要记住一条编程规则: ：&lt;strong&gt;在线程里统一调用spin_lock_irqsave()/spin_lock_irqrestore()，在中断里统一调用 spin_lock()/spin_unlock()&lt;/strong&gt; 。&lt;/p&gt;

&lt;p&gt;注：Linux内核2.6.32以后，就不再允许中断嵌套了，也就是说，当前中断里本来就是关闭其他中断的，因此无需在中断里调用spin_lock_irqxxx()再去关中断，直接调用spin_lock()/spin_unlock()即可。 对于单核CPU来说，如果要偷懒，直接在线程里调用spin_lock_irqsave()/spin_lock_irqrestore()即可，中断里就不用管了，因为在线程里已经将本核（也就是全部的核）的中断关掉了。 当线程和中断都要访问同一临界区时，我们知道，中断里当然要使用spin_lock()，那么线程里能不能也使用spin_lock() 呢？不能！否则当线程执行临界区时来中断会导致死锁。因此线程里必须使用spin_lock_irqsave()关中断。&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  1                     2           3
cpu0 |          |               |  外设1
cpu1 |   &amp;lt;----  | 中断控制器     |  外设2
cpu2 |          |               |  外设3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;local_irq_disable/save ： 让本cpu不响应所有的中断（在位置1控制）； irq_disable： 让某号中断不能发给所有的cpu（在位置2控制）。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;blockquote&gt;
    &lt;h3 id=&quot;linux也提供了直接控制中断是否使能的api&quot;&gt;Linux也提供了直接控制中断是否使能的API：&lt;/h3&gt;
    &lt;ul&gt;
      &lt;li&gt;local_irq_disable()/local_irq_enable() ： 关闭/开启当前核的中断，有副作用 （直接去改CPU里面的CPRS寄存器，禁用所有中断）&lt;/li&gt;
      &lt;li&gt;local_irq_save()/local_irq_restore() ： 关闭/开启当前核的中断，无副作用&lt;/li&gt;
      &lt;li&gt;irq_disable()/irq_enable()：关闭/开启所有核的中断，有副作用。&lt;/li&gt;
      &lt;li&gt;irq_save()/irq_restore() ：关闭/开启所有核的中断，无副作用 Linux里没有一个API，能让你关掉其他核的中断。&lt;/li&gt;
      &lt;li&gt;副作用：如果调用local_irq_disable()之前，中断已经是关闭的，那么之后调用local_irq_enable()会导致中断被误开启，而不是恢复之前的关闭状态。因此，local_irq_save()/local_irq_restore()通过“保存中断状态-关中断-恢复中断状 态”来解决这个问题。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;local_irq_disable()和local_irq_save()关掉了当前核的中断，间接导致当前核的调度器被关闭，因为调度器是依赖中断的。&lt;strong&gt;local_irq_disable()和local_irq_save()一般很少使用，除非你非常清楚你正在做什么！&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;spin_lock()/spin_unlock() 和spin_lock_irqsave()/spin_lock_irqrestore()，是内核里并行编程时最常使用的API。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;核内&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;核间&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;spin_lock&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;锁住调度&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;自旋&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;local_irq_disable/save&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;锁住中断  间接锁住调度&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;无意义&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;spin_lock_irqsave&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;锁住中断 锁住调度&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;自旋&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h1 id=&quot;6-mutex&quot;&gt;6. mutex&lt;/h1&gt;

&lt;p&gt;mutex的用法：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;mutex_lock(&amp;amp;lock);
    临界区…
    可以睡眠
    mutex_unlock(&amp;amp;lock);&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;mutex工作机制：如果线程T1拿到了mutex，于是线程T2拿不到，它就会睡眠直到T1释放mutex，则T2被唤醒 -这里涉及两次contex switch。&lt;/li&gt;
  &lt;li&gt;如果临界区里需要睡眠，或者临界区很长，则应使用mutex。&lt;/li&gt;
  &lt;li&gt;加锁原则：同一把锁、语义整体、粒度最小；同一把锁和语义整体可以保证代码运行的正确性，粒度可以决定你的代码性能。软件设计时需要即要保证粒度最小也要语义整体完整。&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;7-lockup-detector&quot;&gt;7. lockup detector&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;lockup detector的实现源码：kernel/watchdog.c。注意这个不是硬件看门狗（drivers/watchdog/watchdog.c）， 而是用来监测系统里有没有锁住调度器和中断的。&lt;/li&gt;
    &lt;li&gt;lockup detector的工作原理：kernel/watchdog.c里，为每个CPU核都启动了一个高优先级的RT线程，这个线程会周 期性地对某个计数值执行+1操作。而另一个定时器中断会周期性地侦测这个计数值是否在自增。
      &lt;blockquote&gt;
        &lt;ul&gt;
          &lt;li&gt;NMI中断 + 定时器中断 + 高优先级RT线程&lt;/li&gt;
          &lt;li&gt;用定时器中断，检测高优先级线程有无机会执行-&amp;gt;soft lockup(只锁调度器)&lt;/li&gt;
          &lt;li&gt;用NMI，检测定时器中断有无机会执行-&amp;gt;hard lockup（中断和调度器都被锁住）&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/blockquote&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;如果发生了soft lockup，则定时器中断会监测到计数值停滞，于是打印出栈的backtrace（通过CPU的SP指针 来回溯），将事故现场报告给用户。&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;如果发生了hard lockup，比较难debug，只有CPU支持NMI（不可屏蔽的中断，一般由性能检测单元PMU来 实现），才能进行debug。X86是支持NMI的。但是ARM不支持NMI，所以Linux官方内核是不支持ARM的 hard lockup detector的。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;有两种补丁可以实现ARM上的hard lockup detector:&lt;/p&gt;

  &lt;p&gt;（1）使用FIQ来模拟NMI：由Linaro开发。注意，在Linux内核里，FIQ只作为特殊的debug用途，常规代 码里基本不会用到FIQ。&lt;/p&gt;

  &lt;p&gt;（2）使用另一个CPU核来检测：缺点是这个CPU无法访问hang死的CPU的栈（因为一个CPU无法访问别的 CPU的SP寄存器），因此无法进行栈回溯。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;内核hang死，通常是由关中断或者spinlock引起的。&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;</content><author><name>ZhiDuo Li</name></author><summary type="html">本文主要讨论内核中一些常用的锁机制，以及使用注意事项。</summary></entry></feed>