原文地址:http://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.html
或 http://ifeve.com/memory-barriersfences/
关键词:Load Barrier, Store Barrier, Full Barrier
本文我将和大家讨论并发编程中最基础的一项技术:内存屏障或内存栅栏,也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术。
CPU使用了很多优化技术来达成一个事实:CPU执行单元的速度要远超主存访问速度。在我上一篇文章 “Write Combing - 合并写”中我已经介绍了其中的一项技术。CPU避免内存访问延迟最常见的技术是将指令管道化,然后尽量重排这些管道的执行以最大利用缓存而把因为缓存未命中引起的延迟降到最小。
当一个程序执行时指令是否被重排并不重要,只要最终的结果是一样的。例如,在一个循环里,如果循环体内没用到这个计数器,循环的计数器什么时候更新(在循环开始,中间还是最后)并不重要。编译器和CPU可以自由的重排指令以最佳的利用CPU,只要下一次循环前更新该计数器即可。并且在循环执行中,这个变量可能一直存在寄存器上,并没有被推到缓存或主存,这样这个变量对其他CPU来说一直都是不可见的。
CPU核内部包含了多个执行单元。例如,现代Intel CPU包含了6个执行单元,可以组合进行算术运算,逻辑条件判断及内存操作。每个执行单元可以执行上述任务的某种组合。这些执行单元是并行执行的,这样指令也就是在并行执行。但如果站在另一个CPU角度看,这也就产生了程序顺序的另一种不确定性。
最后,当一个缓存失效发生时,现代CPU可以先假设一个内存载入的值并根据这个假设值继续执行,直到内存载入返回确切的值。

感觉把个人网站正在访问的在线人数显示在Nokia 5110液晶屏挺好玩,就稍微研究了一下如何提取实时在线人数。
实现方法
Google Analytics
Google Analytics具有很强大的实时流量分析功能,不过网站主必须登陆到后台才能看,但并没有提供Open API,所以就不能用这个服务了。
日志分析
不修改网站通过web服务器的日志分析,用一个脚本统计15分钟内日志的Unique IP可以粗略的获得一个在线人数。
但多个用户可能通过一个IP过来,这种做法肯定不精确。一般我们可以通过在页面上部署Javascript脚本,由Javascript为每一个浏览器产生一个独特的持久化Cookie,用这个Cookie代替IP来统计。但用Raspberry Pi来做这件事情会拖慢网站,于是一种方案是采用免费的Google App Engine来实现,打算有空来实现一个。
CNZZ
CNZZ也提供了15分钟内的在线人数统计功能。分析CNZZ的计数器代码后发现如下方法可以提取到在线人数:
```
Nokia 5110屏比前面介绍过的1602液晶屏功能好很多,淘宝上买价格相差不大(二手5110 12块左右, 全新1602 8块左右),Nokia 5110最少只需要占用4个GPIO引脚:
- 带蓝色背光
- 使用Philips PCD8544 LCD控制器(通过SPI接口)
- 84x48点阵,可显示100多个字符
硬件准备
- 树莓派
- Nokia 5110 拆机屏 或 焊好的? 注意不要买裸屏,需要带电路板的
- 杜邦线 母对母8条
- 8P排针 用来焊接5110屏幕PCB板
- 电烙铁
电路
5110电路板有8个引脚,使用排针(如下图)将其焊上,方便后面用杜邦线连接,如果不会焊也可以买焊接好的。
硬件准备
需要以下硬件:
- 树莓派
- 面包板
- 1602液晶屏一块
- 10K电位器
- 杜邦线
- 排针
- 面包板电源
1602 LCD液晶屏
LCD1602液晶屏提供了16列x2行的ASCII字符显示能力,工作电压5V,提供4位数据与8位数据两种工作模式,因为Raspberry Pi的GPIO口数量很有限,所以使用4位数据模式。LCD1602液晶屏模块提供了16个引脚,我们只需接其中的12个即可--请参考GPIO命名规则:
- VSS,接地,RPi PIN 6
- VDD,接5V电源,PRi PIN 2
- VO,液晶对比度调节,接电位器中间的引脚
- RS,寄存器选择,接GPIO 14,RPi PIN 8
- RW,读写选择,接地,表示写模式,PRi PIN 6
- EN,使能信号,接GPIO 15,RPi PIN 10
- D0,数据位0,4位工作模式下不用,不接
- D1,数据位1,4位工作模式下不用,不接
- D2,数据位2,4位工作模式下不用,不接
- D3,数据位3,4位工作模式下不用,不接
- D4,数据位4,接GPIO 17,RPi PIN 11
- D5,数据位5,接GPIO 18,RPi PIN 12
- D6,数据位6,接GPIO 27,RPi PIN 13
- D7,数据位7,接GPIO 22,RPi PIN 15
- A,液晶屏背光+,接5V,RPi PIN 2
- K,液晶屏背光-,接地,RPi PIN 6
树莓派和普通电脑不一样的地方在于它还带了17个可编程的GPIO(General Purpose Input/Output),可以用来驱动各种外设(如传感器,步进电机等)。但GPIO的编号方法有些混乱,不同的API(如wiringPi,RPi.GPIO等)对GPIO的端口号编号并不一样,下面则用图表标明了对应的叫法,这样在看程序例子的时候可以确定物理是哪个接口。
GPIO库
- wiringPi C,有Perl, PHP, Ruby, Node.JS和Golang的扩展,支持wiringPi Pin和BCM GPIO两种编号
- RPi.GPIO Python,支持Board Pin和BCM GPIO两种编号
- Webiopi,Python, 使用BCM GPIO编号
- WiringPi-Go, Go语言,支持以上三种编号
编号规范
- 第一列是wiringPi API中的缺省编号,
wiringPiSetup()
采用这列编号
- 第二列(Name)往往是转接板的编号
- 第三列是树莓派板子上的自然编号(左边引脚为1-15,右边引脚为2-26),
RPi.GPIO.setmode(GPIO.BOARD)
采用这列编号
- 树莓派主芯片提供商Broadcom的编号方法,相当于调用了
WiringPiSetupGpio()
或RPi.GPIO.setmode(GPIO.BCM)
采用这列编号
wiringPi Pin |
Name |
Board Pin |
BCM GPIO |
0 |
GPIO 0 |
11 |
17 |
1 |
GPIO 1 |
12 |
18 |
2 |
GPIO 2 |
13 |
21 |
3 |
GPIO 3 |
15 |
22 |
4 |
GPIO 4 |
16 |
23 |
5 |
GPIO 5 |
18 |
24 |
6 |
GPIO 6 |
22 |
25 |
7 |
GPIO 7 |
7 |
4 |
8 |
SDA |
3 |
0 |
9 |
SCL |
5 |
1 |
10 |
CE0 |
24 |
8 |
11 |
CE1 |
26 |
7 |
12 |
MOSI |
19 |
10 |
13 |
MISO |
21 |
9 |
14 |
SCLK |
23 |
11 |
15 |
TXD |
8 |
14 |
16 |
RXD |
10 |
15 |
本方法只适合小网站,主要是好玩。Raspberry Pi不是很合适需要实时控制的系统(比如,飞行器,遥控小车),因为Linux内核要多任务,应用程序的优先级不能保持最高,会带来延时,但做些实时性要求不高的系统还是可以的。
硬件安装
需要以下硬件:
- 可以工作的树莓派一个
- 1P杜邦线2条
- 面包板一个
- 面包板跳线 或 单排针 两根
- 发光二极管一个
- 300欧姆的电阻一个
GPIO接口

用杜邦线将上图的3.3V输出和GPIO 23引出(板子正面朝上,GPIO引脚在左上角),将电阻和LED串联起来(电阻防止LED电流过大烧掉),注意二极管的两根脚不一样长,长脚的接正级,这样GPIO 23如果输出高电平,二极管就不发光了,输出低电平就亮啦!
介绍
之前买的Raspberry Pi因为要跑这个网站,不能经常拔下来玩别的,所以又买了一个,这次安装的是Arch Linux。这个发行版安装好后非常基础,占用的空间也只有600M不到,比较合适已有Linux基础的同学玩。初学者可以玩官方推荐的Raspbian。
Arch Linux特点:
- 启动快,上电后只要3s完成启动
- 安装完没有图形界面,干净
- 面向开发者的系统
- 包管理系统pacman很好用,一个命令就可以完成各种操作
- ArchLinux缺省账号和密码是root/root,弄好了后要记得修改root密码
- 从中国用下载包很快,比Raspbian的源快多了
增加sudo用户
useradd hugo
passwd hugo
mkdir /home/hugo
chown hugo:hugo /home/hugo
pacman -S sudo
visudo
Raspberry Pi整天开着,如果用缺省SSH端口对外开放,就会经常遇到扫描SSH密码的肉鸡。虽然密码不是很简单,但还是感觉很不安全的。
系统的ssh登录日志文件在:/var/log/auth.log,登录失败时会记录以下格式的日志:
Mar 7 10:31:51 raspberrypi sshd[24510]: Failed password for root from 221.8.19.129 port 4066 ssh2
Mar 7 10:31:55 raspberrypi sshd[24514]: Failed password for root from 221.8.19.129 port 4079 ssh2
Mar 7 10:31:56 raspberrypi sshd[24518]: Failed password for sshd from 221.8.19.129 port 4080 ssh2
Mar 7 10:32:26 raspberrypi sshd[24522]: Failed password for sshd from 221.8.19.129 port 4149 ssh2
用最简单的Shell脚本来解决这个问题:
guard.sh
#!/bin/bash
last_ip=""
tail -f /var/log/auth | while read LINE; do
{
if [[ "${LINE}" =~ "Failed" ]]; then
ip="$(echo ${LINE} | awk '{print $(NF-3)}')"
if [[ "$last_ip" == "$ip" ]]; then
echo "block $ip"
#curl -s --data-ascii "uuid=<my iphone's uuid>" --data "body=${LINE}" http://raspberrypi/pushme
iptables -A INPUT -s "$ip" -j DROP
fi
last_ip=$ip
echo $LINE
fi
}
done