乐正

Actions speak louder than words.

什么是线程

要成就一个复杂的系统,各部分必须分工明确,各司其职。 —— 《人工科学》

线程和进程

在上篇文章中(进程描述和控制),提出的进程概 念包含两个特点:

  • 资源所有权:一个进程拥有包括一个存放进程映像的虚拟地址空间。一个进程总是拥 有对于资源的控制或所有权。操作系统提供对于资源访问的保护功能。

  • 调度/执行:一个进程具有一个执行状态和一个被分配的优先级,它是一个可以被操 作系统调度和分派的实体。

分派的单位通常称为线程或轻量级进程;而拥有资源所有权的单位通常仍称为进程或者任务。

多线程

多线程是指在操作系统单个进程内支持多个并发执行路径的能力。对应的每个进程只有 一个线程在执行的称为单线程方法

在多线程环境中,进程被定义成资源分配的单位和一个被保护的单位。在一个进程中,可能 有一个或者多个线程,与进程相关的有:

  • 存放进程映像的虚拟地址空间
  • 受保护的对处理器、其他进程、文件和I/O资源的访问。
  • 线程执行状态(运行、就绪等)。
  • 在未运行时保存的线程上下文。
  • 一个执行栈。
  • 用于每个线程局部变量的静态存储空间。
  • 与进程内的其他线程共享的对进程的内存和资源的访问。

在多线程环境中,进程只有一个与之关联的进程控制块和用户地址空间。但是每个线程都有 一个独立的栈,还有独立的控制块用于包含寄存器值、优先级和其他与线程有关的状态信息。

进程中的所有线程共享该进程的状态和资源,它们驻留在同一地址空间中,并且可以访问到 相同的数据。

在性能比较方面,线程有下面重要的优点:

  1. 在一个已有的进程中创建一个新的线程比创建一个全新的进程所需的时间要少的多。
  2. 终止一个线程比终止一个进程花的时间少。
  3. 同一进程内线程间切换比进程间切换花费的时间少。
  4. 线程提供了不同的执行程序间的通信效率。

因此,如果一个应用程序或函数被实现为一组相关联的执行单位,那么用一组线程比一组进 程更有效。

线程的功能特性

和进程一样,现在的关键状态有运行态、就绪态和阻塞态。一般来说,挂起态对于线程来说 没有意义。

有四种与线程状态改变的相关工作:

  • 派生:当派生一个新的进程时,同时也为该进程派生了一个线程。随后,进程中的线 程可以在同一进程中派生另一个线程。
  • 阻塞:当线程需要等待一个事件时,它将被阻塞,此时处理器转为执行另一个就绪态 线程。
  • 解除阻塞:当阻塞一个线程的事件发生时,该线程被转移到就绪队列中。
  • 结束:当一个线程完成时,其寄存器上下文和栈都被释放。

线程分类

用户级和内核级线程

线程的实现分为两大类:用户级线程和内核级线程(User-Level Thread, ULT和Kernel-Level Thread, KLT) ,后者又被称为轻量级进程。

在一个纯粹的用户级线程软件中,有关线程管理的所有工作都是由应用程序完成,内核意识 不到线程的存在。任何应用程序都可以通过使用线程库被设计成多线程程序。线程库是用于 用户级线程管理的一个例程包,它包含用于创建和销毁线程的代码、在线程间传递消息和数 据的代码、调度线程执行的代码,以及保存和恢复线程上下文的代码。

用户级线程和内核级线程

线程调度活动都发生在用户空间中,并且发生在一个进程内,内核并不知道这些活动。内核 继续以进程为单位进行调度,并且给该进程指定一个执行状态。

使用用户级线程而非内核级线程有很多优点,包括:

  • 由于所有线程管理数据结构都在一个进程的用户地址空间中,线程切换不需要内核特权, 这节省了状态转换的开销。
  • 调度可以是应用程序相关的。可以为应用程序量身定制调度算法。
  • 用户级线程可以在任何操作系统中运行,不需要对低层内核进行修改以支持用户级线程。

用户级线程相对于内核级线程有两个明显的缺点:

  • 在典型的操作系统中,许多系统调用都会引起阻塞。因此,当用户级线程执行一个系统调 用时,不仅这个线程会被阻塞,进程中所有线程都会被阻塞。
  • 在纯粹的用于几线程策略中,一个多线程应用程序不能利用多处理技术。

第一次与 Arduino 交互

这是一个比较简单的实验,不过好歹也是让 Arduino 接受外界电子元件的输入值了。这是 套互动交通灯的实验,Arduino 等待行人按下按钮,这样行车灯会变红,行人灯会变绿。

需要的电子元件

  • LED 灯(2红,2绿,1黄)
  • 100Ω 电阻
  • 10KΩ 高阻值电阻(用于下拉电阻)
  • 按钮(有时供应商称之为微动开关)
  • 面包板与导线

代码回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
int carRed    = 12;
int carGreen  = 10;
int carYellow = 11;

int pedRed    =  8;
int pedGreen  =  9;

int buttonPin =  2;

int crossTime = 5000;
unsigned long changeTime; // Time of press button

void setup() {
  pinMode(carRed,    OUTPUT);
  pinMode(carGreen,  OUTPUT);
  pinMode(carYellow, OUTPUT);

  pinMode(pedRed,    OUTPUT);
  pinMode(pedGreen,  OUTPUT);

  pinMode(buttonPin, INPUT);

  digitalWrite(carGreen, HIGH);
  digitalWrite(pedRed, HIGH);
}

void loop() {
  int state = digitalRead(buttonPin);

  if (state == HIGH && (millis() - changeTime) > 5000)
    changeLights();
}

void changeLights() {
  digitalWrite(carGreen, LOW);
  digitalWrite(carYellow, HIGH);
  delay(2000);

  digitalWrite(carYellow, LOW);
  digitalWrite(carRed, HIGH);
  delay(1000);

  digitalWrite(pedRed, LOW);
  digitalWrite(pedGreen, HIGH);
  delay(crossTime);

  for (int i = 0; i < 10; i ++) {
    digitalWrite(pedGreen, HIGH);
    delay(250);
    digitalWrite(pedGreen, LOW);
    delay(250);
  }

  digitalWrite(pedRed, HIGH);
  delay(500);

  digitalWrite(carYellow, HIGH);
  digitalWrite(carRed, LOW);
  delay(1000);
  digitalWrite(carGreen, HIGH);
  digitalWrite(carYellow, LOW);

  changeTime = millis();
}

这次使用了一个能存储大数字的数据类型

1
unsigned long chageTime;

由于 Arduino 使用的 Atemga 32 只有很小的内存,所以合理分配与使用内存在这之上就显 得非常重要。

接下来就是让 Arduino 读取按钮输出值了,

1
pinMode(buttonPin, INPUT);

这句代码让 Arduino 将按钮所在的引脚设置为 Input 模式。在程序循环中,使用

1
int state = digitalRead(buttonPin);

来检查引脚的状态。

硬件回顾

本次与 Arduino 交互的元件是按钮,或者叫开关。按钮不是之间连接在电源线和引脚之间 的,在按钮和地之间有一个电阻,这个电阻叫下拉电阻。对应的还有上拉电阻,它 们对保证按钮正常工作是非常重要的。

逻辑状态

逻辑电路是一种只有开、关两种状态的电路,用布尔数0和1表示。电路处于关状态时, 输出端的电压接近0V。电路处于开状态时用高电平表示,输出端接近于电源供电电压。

如果不能确定状态接近所需要的电压,这部分电路可以被认为是浮动的(既不是高电平 ,也不是低电平。),这种浮动也被称为电子噪声。电子噪声被随即的解释为0或者1。

上拉电阻或下拉电阻可用来保证状态确定为高或低。

下拉电阻

如左图:

下拉电阻和上拉电阻

如果按钮被按下,电流以电阻最小的路径在5V 端与输入引脚之间流过。 当按钮没有被按下 时,输入引脚通过100KΩ 电阻接地。如果没有这个电阻,当按钮没有被按下时,这个引脚将 不连接任何东西,因此它的电压是在 0V 和 5V 之间浮动。在这个电路中,当按钮没有被按 下输入将总是接地的,或者是0V,当按钮被按下时它将指向5V端。

上拉电阻

如右图:

下拉电阻和上拉电阻

交换下拉电阻和开关的位置,现在电阻变成了上拉电阻。当按钮没有被按下时,输入引脚通 过上拉电阻接到5V端,所以引脚上总是高电平。当按钮被按下时,通过限流电阻的路径引脚 接地,所以引脚被拉向地或者低电平状态。如果没有5V端和地之间的电阻,电路将被短路, 这将损坏电路或电源。上拉电阻在数字电路中应用更广泛。

上拉电阻在数字电路中经常用来保证输入保持高电平。

Arduino 内部的上拉电阻

Arduino 内部包含了上拉电阻。它连接在引脚上,阻值为20KΩ,使用时需要通过软件激活。

1
2
pinMode(pin, INPUT);
digitalWrite(pin, HIGH);

同理,当一个输出脚为 HIGH 时,转换这个引脚到 INPUT 模式,那么内部上拉电阻将激活。

电位计与从模拟引脚读值

电位计

电位计就是一个可调节电阻,调解范围从0到一个设定的值。电位计有三个引脚。若只连接 两个引脚,电位计可变为一个可变电阻。通过连接三个引脚,并为其提供电压,它将称为 一个分压器。

电位计提供了一种在0和设定的最大值之间调整数值的方法。

模拟引脚读值

Arduino有6个模拟输入/输出引脚,每个引脚带有一个10位模/数转换器。这意味着模拟引脚 能够读取0V 到 5V之间的电压,用0到1023之间的正数代表0V 到 5V之间的电压。每个分度 表示 5V / 1024 电压,即每个分度是4.9mV。

通过直接读取电位计引脚数值到ledDelay这个变量中:

1
2
byte potPin = 2; // 电位计连接到的模拟引脚
int ledDelay = analogRead(potPin);

注意:模拟引脚不需要像数字引脚一样设置输入或输出模式。

Arduino 学习笔记——LED 闪烁实验中隐含的简单原理

我是软件出身,但是许久以前便对硬件充满了兴趣,终于在最近买了一套 Arduino 基础开 发套件,希望能在硬件上学习一些知识。

几乎所有单片机学习的第一个实验都是blink,在这个小小的实验中也蕴含着一些我不知 道的电子电路知识,所以记录下在这个实验中所学习的知识,方便回顾整理。

代码回顾

blink.ino
1
2
3
4
5
6
7
8
9
10
11
12
int ledPin = 10

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  digitalWrite(ledPin, HIGH);
  delay(1000);
  digitalWrite(ledPin, LOW);
  delay(1000);
}

这一段类似 C 的 Arduino 代码对于软件出身的我来说并不难理解。但是,这里要注意的是 Arduino 程序必须包含 setup()loop() 两个函数,否则它将不能工作。顾名思义, 前者只在程序开始时运行一次,一般做初始化用,如设置引脚形式,设置波特率等等。后者 则是在程序中循环执行的,是主要的过程函数。

在这段代码的 setup() 函数中,只执行了一句代码:pinMode(ledPin, OUTPUT),它告 诉 Arduino 设置引脚的模式为输出模式。由此易知,对应的应该还有一个 INPUT 的输入 模式。

loop() 函数中,让 LED 以一秒的频率闪烁,主要依靠 digitalWritedelay 函数。其中,digitalWrite(ledPin, HIGH) 告诉数字引脚打开电源,delay(1000)让程 序暂停1秒,而 digitalWrite(ledPin, LOW) 则要求数字引脚关闭电源。

硬件回顾

在 LED 闪烁实验中,用到的硬件有:

  • 面包板
  • 5mm LED
  • 220Ω 电阻(或者其他适合你 LED 的数值)
  • 跳线(也称面包线)

面包板

面包板是一个可重复使用的非焊接单元,用于制作一个电子线路原型或者线路设计实验。这 个板在一个栅格中有一系列的孔,在板子背面,这些孔通过两条导电金属条相连。 如图:

面包板排列

电阻

电阻会对电流产生一定的阻力,引起它两端的电压下降。在我的 LED 闪烁实验里面,数字 引脚输出5V、40mA 直流电,而我的 LED 需要的是2V、35mA 电流,因此我需要一个电阻降 低电压和电流。

计算需要的电阻阻值的公式是:

R = (VS - VL) / I

就是用电源电压减去 LED 电压除以 LED 电流。

那么怎样找到所需要的阻值的电阻呢?电阻使用色环代码表示电阻阻值的大小。

颜色 1st 2nd 3rd(幂) 4th(误差)
黑色 0 0 x100
棕色 1 1 x101 +/- 1%
红色 2 2 x102 +/- 2%
橘黄 3 3 x103
黄色 4 4 x104
绿色 5 5 x105 +/- 0.5%
蓝色 6 6 x106 +/- 0.25%
紫色 7 7 x107 +/- 0.1%
灰色 8 8 x108 +/- 0.05%
白色 9 9 x109
金色 x10-1 +/- 5%
银色 x10-2 +/- 10%
+/- 20%

LED

LED 是一个标准的发光二极管。二极管是一种器件,值允许电流从一个方向流进。二极管用 来防止在电路中意外地将电流和地连接以至于损坏其他元件。

LED 的引脚的长度不同,长的一边为正极,需要连接电源;短的一边为负极,需要接地。给 LED 串联一个电阻是必要的,以确保提供给 LED 正确的电流。

总结

实验虽然简单,确也有许多我不曾知道的知识点。希望自己能坚持下去,完成对于 Arduino 的学习。