momo zone

调核人的blog

Monthly Archives: 一月 2013

各系copy and paste

Cut Copy Paste
Generic/Apple command-X command-C command-V
Windows/GNOME/KDE control-X / shift-Delete control-C / control-Insert control-V / shift-Insert
BeOS alt-X alt-C alt-V
Common User Access shift+Delete control+Insert shift+Insert
Emacs control-W (to mark)
control-K (to end of line)
meta-W (to mark) control-Y
vi d (delete) y (yank) p (put)
X Window System click-and-drag to highlight middle mouse button

copy and paste 的最高境界:了解各种OS 和应用的copy and paste  ~~~

Advertisements

再谈终端与X window键码映射

1.终端文本模式下

终端(console)有关键盘的任何设置都和kbd_struct这个内核数据结构有关。opensuse 中kbd工具包含如下命令:

kbd_mode

用来设置对应终端依附的键盘模式,不带参数则是查询

-s: scancode mode (RAW),

-k: keycode mode (MEDIUMRAW),

-a: ASCII mode (XLATE),

-u: UTF-8 mode (UNICODE).

因为kbd_struct这个内核数据结构是依附于console(vt or tty)层的,所以在X window中查询的结果是The keyboard is in some unknown mode ,设置任何模式都不影响输入。X server是监听的/dev/input/event*,evdev和console没有任何关系。 貌似console 默认只支持ASCII,UTF-8,其余的都是乱码。

kbdinfo

Usage: kbdinfo [-C DEVICE] getmode [text|graphics]
or: kbdinfo [-C DEVICE] gkbmode [raw|xlate|mediumraw|unicode]
or: kbdinfo [-C DEVICE] gkbmeta [metabit|escprefix]
or: kbdinfo [-C DEVICE] gkbled [scrolllock|numlock|capslock]

getmode比较特别,/dev/tty7 (X server)会是graphics 其余的都是text

kbdrate

用来查询和设置键重复的频率

getkeycodes

获得当前内核中scancodes 对应 keycodes的表

setkeycodes

设置当前内核中scancodes对应keycodes的表(试过,但好像不管用,报错)

loadkeys 

非常重要的命令,用来一次性载入map文件指定的keycodes -> 键盘符号表。该表也在kbd包内,位于/usr/share/kbd/keymaps。

dumpkeys

以loadkeys 时文件的表示方式来显示已经加载的keycodes -> 键盘符号表,包含unicode字符

showkey

以交互的方式测试并显示scancode和keycode

clrunimap, getunimap, loadunimap

这三个命令分别用来清除,查看和加载unicode字符表,就和加载键盘符号表类似,unicode符号也是从keycodes转过来的。

screendump

很好玩的一个命令,将把当前screen可视范围内的字符再在终端上打一遍

setleds

设置{+|-}caps  {+|-}num {+|-}scroll 键的状态和led指示灯,而且可以使led和实际的这些键相分离设定

setfont

加载终端字体,字体文件位于/usr/share/kbd/consolefonts。还可以用-m 参数指定consolemap (位于/usr/share/consoletrans)和 -u unicodemap (位于/usr/share/kbd/unimaps)。opensuse有个kbd.service 他会加载/etc/sysconfig/console中指定的字体,keycodes重映射表和unicode映射表,而且优先级最高。

showconsolefont

显示所有已加载的终端字体

setmetamode

设置meta键的生成模式。meta|bit|metabit 表示按下meta键将产生8bit keycode,其中的最高位置1,指示该键为meta+XXX  。esc|prefix|escprefix 表示按下meta键实际产生一个meta转义符+XXX 序列。注意,该命令不影响ESC键产生meta转义符。

 

opensuse中相关的一些配置已经放置在/etc/sysconfig/keyboard中了。

2.X Window 图形模式下

和kbd相对,X11下的一个叫xkb的架构,但实际复杂得多。另外一个是xmodmap 却简单很多。两者可以同时管理,但不建议。xkb架构更灵活更强大。xmodmap就是写个文本完事。

注意:X Window有一个keysym的概念,X11的键码其实是这个keysym而不是keycode,这是和kbd最大的不同,也就是说在keycode上又多了一层映射关系。X11的keysym在/usr/include/X11/keysymdef.h 中定义。

xmodmap

X11旧式的keycodes映射管理工具

-p 显示当前所有modifier键的设置

xmodmap: up to 4 keys per modifier, (keycodes in parentheses):

shift Shift_L (0x32), Shift_R (0x3e)
lock Caps_Lock (0x42)
control Control_L (0x25), Control_R (0x69)
mod1 Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd)
mod2 Num_Lock (0x4d)
mod3
mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf)
mod5 ISO_Level3_Shift (0x5c), Mode_switch (0xcb)

-pk 显示所有keycode对应的keysym

There are 7 KeySyms per KeyCode; KeyCodes range from 8 to 255.

KeyCode Keysym (Keysym) …
Value Value (Name) …

8
9 0xff1b (Escape) 0x0000 (NoSymbol) 0xff1b (Escape)
10 0x0031 (1) 0x0021 (exclam) 0x0031 (1) 0x0021 (exclam)
11 0x0032 (2) 0x0040 (at) 0x0032 (2) 0x0040 (at)
12 0x0033 (3) 0x0023 (numbersign) 0x0033 (3) 0x0023 (numbersign)
13 0x0034 (4) 0x0024 (dollar) 0x0034 (4) 0x0024 (dollar)
14 0x0035 (5) 0x0025 (percent) 0x0035 (5) 0x0025 (percent)
15 0x0036 (6) 0x005e (asciicircum) 0x0036 (6) 0x005e (asciicircum)
16 0x0037 (7) 0x0026 (ampersand) 0x0037 (7) 0x0026 (ampersand)
17 0x0038 (8) 0x002a (asterisk) 0x0038 (8) 0x002a (asterisk)
18 0x0039 (9) 0x0028 (parenleft) 0x0039 (9) 0x0028 (parenleft)
19 0x0030 (0) 0x0029 (parenright) 0x0030 (0) 0x0029 (parenright)

每个 keysym列 都对应指定的键组合:

  1. Key
  2. Shift+Key
  3. mode_switch+Key
  4. mode_switch+Shift+Key
  5. AltGr+Key
  6. AltGr+Shift+Key

在 keysym组合 没有被指定时, 使用 NoSymbol 代替。

Xmodmap 文件一共有3个,分别是/etc/X11/Xmodmap,/etc/X11/Xmodmap.remote,~/.Xmodmap 最后一个是针对用户的,优先级最高。

setxkbmap

-print:打印当前键盘的各种参数

xkb_keymap {
xkb_keycodes { include “evdev+aliases(qwerty)” };
xkb_types { include “complete+numpad(mac)” };
xkb_compat { include “complete” };
xkb_symbols { include “macintosh_vndr/apple(alukbd)+macintosh_vndr/us+inet(evdev)+terminate(ctrl_alt_bksp)”};
xkb_geometry { include “macintosh(applealu_ansi)” };
};

其中xkb_symbols 最关键,作用和Xmodmap差不多。

所有的xkb配置文件都在/usr/share/X11/xkb目录下。括号含义是匹配某个配置文件中xkb_symbols关键字指示的配置。

xkbprint

非常有意思命令,可以打印出ps格式的键盘布局图。

xkbprint $DISPLAY layout.ps

apple_g6_mod_layout

 

20141110 update:

编辑/usr/share/X11/xkb/types/numpad
partial xkb_types "mac" {
    type "KEYPAD" {
////    modifiers = None;
////    map[None] = Level2;
////    level_name[Level2] = "Number";
        modifiers = NumLock;
        map[None] = Level1;
        map[NumLock] = Level2;
        level_name[Level1] = "Base";
        level_name[Level2] = "Number";
    };
    include "extra(keypad)"
};

 

协议栈与网卡中间的RFS

RFS: Receive Flow Steering
===============

===========

 

RPS只依靠hash来控制数据包,提供了好的负载平衡,但是它没有考虑应用程序的位置(注:这个位置是指程序在哪个cpu上执行)。RFS则考虑到了应用程序的位置。RFS的目标是通过指派应用线程正在运行的CPU来进行数据包处理,以此来增加数据缓存的命中率。RFS依靠RPS的机制插入数据包到指定CPU的backlog队列,并唤醒那个CPU来执行。

 

 

RFS中,数据包并不会直接的通过数据包的hash值被转发,但是hash值将会作为流查询表的索引。这个表映射数据流与处理这个流的CPU。这个数据流的hash值(就是这个流中的数据包的hash值)将被用来计算这个表的索引。流查询表的每条记录中所记录的CPU是上次处理数据流的CPU。如果记录中没有CPU,那么数据包将会使用RPS来处理。多个记录会指向相同的CPU。确实,当流很多而CPU很少时,很有可能一个应用线程处理多个不同hash值的数据流。

 

 

rps_sock_flow_table是一个全局的数据流表,这个表中包含了数据流渴望运行的CPU。这个CPU是当前正在用户层处理流的CPU。每个数据流表项的值是CPU号,这个会在调recvmsg,sendmsg (特别是inet_accept(), inet_recvmsg(), inet_sendmsg(), inet_sendpage() and tcp_splice_read()),被更新。(注:使用sock_rps_record_flow()来记录rps_sock_flow_table表中每个数据流表项的CPU号。)

 

 

当调度器移动一个线程到一个新的CPU,而内核正在旧的CPU上处理接收到的数据包,这会导致数据包的乱序。为了避免这个, RFS使用了第二个数据流表来为每个数据流跟踪数据包:rps_dev_flow_table 是一个表,被指定到每个设备的每个硬件接收队列。每个表值存储了CPU号和一个计数值。这个CPU号表示了数据流中的数据包将被内核进一步处理的CPU。理想状态下,内核和用户处理发生正在同一个CPU上,由此在这两个表中这个CPU号是相同的。如果调度器已经迁移用户进程,而内核仍然有数据包被加到旧的CPU上,那么这两个值就不等了。

 

 

当这个流中的数据包最终被加到队列中, rps_dev_flow_table中的计数值记录了当前CPU的backlog队列的长度。每个backlog队列有一个队列头,当数据包从队列中出去后,这个队列头就会增加。队列尾部则等于队列头加上队列长度。换句话说,rps_dev_flow[i] 中的计数值记录了流i中的最后一个数据包,这个数据包已经添加到了目标CPU的backlog队列。当然,流i是由hash值选择的,并且多个数据流可以hash到同一个流i.

 

下面描述避免数据包乱序的技巧,当从get_rps_cpu()选择CPU来进行数据包处理,rps_sock_flow 和rps_dev_flow 将会进行比较。如果数据流的理想CPU(found in therps_sock_flow table)和当前CPU(found in the rps_dev_flow table)匹配,这个包将会加到这个CPU的backlog队列。如果他们不同,并且下面规则中任一个为真,则当前的CPU将会被更新,去匹配理想CPU。

 

– 当前CPU的队列头部大于等于rps_dev_flow[i]中记录的尾部计数值,这个计数值指向了CPU的队列的尾部。(说明当前cpu中没有多余的数据包未处理。)

– 当前CPU是未设置的。(等于NR_CPUS,RPS_NO_CPU=0xffff)

– 当前CPU是离线的。(注:应该是没有启用。)

 

(注:如果他们不同,并且当前CPU是有效的,则会继续用当前的CPU来处理。)检查了之后,数据包被发送到(可能)更新后的CPU.这些规则目标是当旧的CPU上没有接收到的数据包,才会移动数据流移动到一个新的CPU上。接收到的数据包能够在新的CPU切换后到达。

 

==== RFS Configuration

 

RFS需要内核编译CONFIG_RPS选项,直到明显的配置,RFS才起作用。全局数据流表(rps_sock_flow_table)的总数可以通过下面的参数来设置:

/proc/sys/net/core/rps_sock_flow_entries

每个队列的数据流表总数可以通过下面的参数来设置:

/sys/class/net/<dev>/queues/rx-<n>/rps_flow_cnt

 

== Suggested Configuration

 

针对每个接收队列启用RFS,上面的两个参数需要被设置。参数的值会被进位到最近的2的幂次方值。(参数的值是7,则实际有效值是8. 参数是值32,则实际值就是32.)建议的流计数依赖于期待的有效的连接数,这个值显著的小于连接总数。我们发现rps_sock_flow_entries设置成32768,在中等负载的服务器上,工作的很好。对于单队列设备,单队列的rps_flow_cnt值被配置成与 rps_sock_flow_entries相同。对于一个多队列设备,每个队列的rps_flow_cnt被配置成rps_sock_flow_entries/N, N是队列总数。例如,如果rps_sock_flow_entries设置成32768,并且有16个接收队列,每个队列的rps_flow_cnt最好被配置成2048.

 

Accelerated RFS(加速RFS)

===============

 

加速RFS对于RFS而言,就像RSS对于RPS。 加速RFS是一个硬件加速的负载平衡机制。加速RFS基于应用线程正在运行的CPU,使用“soft state”来控制流。加速RFS应该比RFS执行的好,因为数据包直接发送到CPU,而消耗数据包的线程也在这个cpu上。目标CPU要么是和应用线程相同的CPU,要么至少是和应用线程在同一缓存层次的CPU(注:意思可能是共享同个cache的其他CPU)。

 

要启用加速RFS,网络协议栈调用ndo_rx_flow_steer驱动函数为数据包通讯理想的硬件队列,这个队列匹配数据流。当rps_dev_flow_table中的每个流被更新了,网络协议栈自动调用这个函数。驱动轮流地使用一种设备特定的方法指定NIC去控制数据包。

 

 

 

一个数据流的硬件队列是从rps_dev_flow_table的CPU记录中推断出来的。协议栈需要向NIC驱动咨询CPU到硬件队列的映射,因为这个映射是由NIC驱动来维护的。这个是自动从IRQ亲和性表(通过/proc/interrupts显示)生成的反转表。驱动可以使用cpu_rmap (“CPU affinity reverse map”) 内核库函数来填充这个映射。For each CPU, the corresponding queue in the map isset to be one whose processing CPU is closest in cache locality.(不知道怎么翻译了 :-0)

 

==== Accelerated RFS Configuration

 

加速RFS需要内核编译CONFIG_RFS_ACCEL,并且需要NIC设备和驱动都支持。并且要求ntuple过滤已经通过ethtool启用。CPU到队列的映射是自动从每个接收队列的IRQ亲和性配置推断出来的,所以无需格外的配置。

 

 

== Suggested Configuration

不管什么时候,只要你想用RFS并且NIC支持硬件加速,这个技术都需要被启用。

(支持这个的硬件有哪些??)

 

XPS: Transmit Packet Steering
=============================

 

XPS 是一种机制,用来智能的选择多队列设备的队列来发送数据包。为了达到这个目标,从CPU到硬件队列的映射需要被记录。这个映射的目标是专门地分配队列到一个CPU列表,这些CPU列表中的某个CPU来完成队列中的数据传输。这个有两点优势,第一点,设备队列上的锁竞争会被减少,因为只有很少的CPU对相同的队列进行竞争。(如果每个CPU只有自己的传输队列,锁的竞争就完全没有了。)第二点,传输时的缓存不命中的概率就减少,特别是持有sk_buff的数据缓存。

 

 

XPS通过设置使用队列进行传输的CPU位图,对每一个队列进行配置。相反的映射,从CPU到传输队列,是由网络设备计算并维护的。当传输数据流的第一个数据包时,函数get_xps_queue()被调用来选择一个队列。这个函数使用正在运行的CPU的ID号作为指向CPU-到-队列的查找表的key值。如果这个ID匹配一个单独的队列,那么这个队列被用来传输。如果多个队列被匹配,通过数据流的hash值作为key值来选择队列。

 

 

选择传输特殊数据流的队列被存储在相应的数据流的socket结构体(sk_tx_queue_mapping)。

这个传输队列被用来传输接下来的数据包,以防乱序(OOO)的包。这个选择也分担了为这个流中的所有数据包调用 get_xps_queues() 的开销。为了避免乱序的包,只有这个数据流中的某个包的skb->ooo_okay标志被设置了,这个数据流所使用的队列才能改变。这个标志表示数据流中没有待解决的数据包(注:被解决的数据包应该是指tcp_packets_in_flight()等于0。也就是说发送出去的数据包都被应答了),所以,这个传输队列才能安全的改变,而不会有产生乱序包的危险。传输层即L4层相应地有责任来设置ooo_okay标志位。例如,当一个连接的所有数据包被应答了,tcp才设置这个标志位。(UDP协议没有流的概念,所以没有必要设置这个标志。)

 

==== XPS Configuration

 

XPS要求内核编译了CONFIG_XPS选项(SMP上默认是打开的)。尽管编译到内核,直到被配置了才能启用。为了使用XPS,需要使用sysfs来配置传输队列的CPU位图:

/sys/class/net/<dev>/queues/tx-<n>/xps_cpus

 

== Suggested Configuration

 

对于只有一个传输队列的网络设置而言,XPS的配置没有任何效果,因为这种情况下没有选择。对于一个多队列系统,XPS更好的配置是每个CPU映射到一个队列中。如果有CPU一样多的队列,那么每个队列可以映射到每个CPU上,这就导致没有竞争的专一配对。如果队列比CPU少,共享指定队列的CPU最好是与处理传输硬中断(这个中断用来清理队列传输结束后的工作)的CPU共享缓存的CPU。

 

Further Information
===================

 

RPS和RFS在内核2.6.35中被引入。XPS在2.6.38中被引入。原始的patches是由Tom Herbert
(therbert@google.com)来提交的。

加速RFS在2.6.35中被引入,原始的patches是由Ben Hutchings (bhutchings@solarflare.com)提交的。

 

Authors:
Tom Herbert (therbert@google.com)
Willem de Bruijn (willemb@google.com)

内核中为什么不应该使用volatile关键字

内核中的一篇译文,其实我觉得volatile意思就是避免丛寄存器或者cache中访问已经缓存的内存数据。有人在内核中误用可能是他把volatile和原子变量搞混淆了。前者只是一个编译器hint,而后者是会产生实际的cpu动作(比如体系结构依赖的指令或总线锁存)。我猜测在SMP中两者差距更大,原子变量的屏蔽效应可以跨cpu,而volatile屏蔽效应只有在当前cpu才有效吧。

原子操作和 volatile 关键字

volatile 关键字确实与原子操作有预定关联,但他们之间的关系并不像很多人想象的那么单纯:

C/C++ 中的 volatile 关键字提供了以下保证:

  1. 对声明为 volatile 的变量进行的任何操作都不会被优化器去除,即使它看起来没有意义(例如:连续多次对某个变量赋相同的值),因为它可能被某个在编译时未知的外部设备或线程访问。
  2. 被声明为 volatile 的变量不会被编译器优化到寄存器中,每次读写操作都保证在内存(详见下文)中完成。
  3. 在不同表达式内的多个 volatile 变量间的操作顺序不会被优化器调换(即:编译器保证多个 volatile 变量在 sequence point 之间的访问顺序不会被优化和调整)。

volatile *不* 提供如下保证

  1. volatile 声明不保证读写和运算操作的原子性。
  2. volatile 声明不保证对其进行的读写操作直接发生在主内存。相反,CPU 会尽可能让这些读写操作发生在 L1/L2 等 cache 上。除非:
    1. 发生了一个未命中的读请求。
    2. 所有级别的 cache 均已被配置为通过式写(write through)。
    3. 目标地址为 non-cacheable 区(主要是其它设备映射到内存地址空间的通信接口。例如:网卡的板载缓冲区、显卡板载显存、WatchDog 寄存器等等)。
  3. 编译器仅保证在生成目标码时不调整 volatile 变量的访问顺序,但通常并不保证该变量不受处理器的 out-of-order 特性影响。目前唯一一个已知的特例是安腾(IA64)处理器版的 VC:在生成 IA64 Target 时,VC 会自动在所有 volatile 访问之间添加内存屏障(详见下文)以保证访问顺序。但 ISO 标准并未要求编译器实现类似机制。实际上,其它编译器(或是面向其它平台的 VC)也都没有类似保证。也就是说,通常认为 volatile 并不保证代码在处理器上的执行顺序,如果需要类似的保证,程序员应当自己使用内存屏障操作。

而原子操作要求做到以下保证:

  • 对原子量的 ‘读出-计算-写入’ 操作序列是原子的,在以上动作完成之前,任何其它处理器和线程均无法访问该原子量。
  • 原子操作必须保证缓存一致性。即:在多处理器环境中,原子操作不仅要把计算结果同步到主存,还要以原子的语义将他们同步到当前平台上其他 CPU 的 cache 中。在大部分硬件平台上,cache 同步通常由锁总线指令完成。意即:逻辑上,所有原子操作都显式使用或隐含使用了一个锁总线操作。例如:x86 指令 ‘cmpxchg’ 中就隐含了锁总线操作。

可见,使用 volitale 关键字并不足以保证操作的原子语义。volitale 关键字的主要设计目的是支持 C/C++ 程序与内存映射设备间的通信。但这并不是说 volitale 关键字对原子操作没有任何帮助:

  • 对于一个被声明为 volitale 类型的原子量来说,如果所有写操作都是原子的,并且完成了必要的 cache 同步,那么读取该原子量时就不必再次对总线上锁。

    这是因为 volitale 关键字保证了对该变量的读操作起码是在当前 CPU cache 中完成的(即:该变量不会被优化到寄存器中)。与此同时,对该变量的所有写操作都已保证原子地完成了所有 CPU 间的 cache 同步以及主存同步操作。所以读操作不管在主存还是当前系统中任意一个 CPU 的 cache 上发生,读到的都是一致的数据。

    在这里,volitale 关键字配合原子量的写入操作一起实现了一个典型的读者/写者同步模型:所有写操作和 cache 同步都保证被原子、互斥地完成;读操作则可以被并发地实现。这也是 Windows API 中没有提供类似 ‘AtomicLoad’ 式语义操作的原因

    当然,这里还有一个隐含的附加条件,就是 CPU 必须能够在一个操作中读入这个原子量。这个条件通常可以忽略,因为超出 CPU 位宽的数据类型通常无法被实现成原子量类型。

 1 Chinese translated version of Documentation/volatile-considered-harmful.txt
  2 
  3 If you have any comment or update to the content, please contact the
  4 original document maintainer directly.  However, if you have a problem
  5 communicating in English you can also ask the Chinese maintainer for
  6 help.  Contact the Chinese maintainer if this translation is outdated
  7 or if there is a problem with the translation.
  8 
  9 Maintainer: Jonathan Corbet <corbet@lwn.net>
 10 Chinese maintainer: Bryan Wu <bryan.wu@analog.com>
 11 ---------------------------------------------------------------------
 12 Documentation/volatile-considered-harmful.txt 的中文翻译
 13 
 14 如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
 15 交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
 16 译存在问题,请联系中文版维护者。
 17 
 18 英文版维护者: Jonathan Corbet <corbet@lwn.net>
 19 中文版维护者: 伍鹏  Bryan Wu <bryan.wu@analog.com>
 20 中文版翻译者: 伍鹏  Bryan Wu <bryan.wu@analog.com>
 21 中文版校译者: 张汉辉  Eugene Teo <eugeneteo@kernel.sg>
 22                杨瑞  Dave Young <hidave.darkstar@gmail.com>
 23 以下为正文
 24 ---------------------------------------------------------------------
 25 
 26 为什么不应该使用“volatile”类型
 27 ------------------------------
 28 
 29 C程序员通常认为volatile表示某个变量可以在当前执行的线程之外被改变;因此,在内核
 30 中用到共享数据结构时,常常会有C程序员喜欢使用volatile这类变量。换句话说,他们经
 31 常会把volatile类型看成某种简易的原子变量,当然它们不是。在内核中使用volatile几
 32 乎总是错误的;本文档将解释为什么这样。
 33 
 34 理解volatile的关键是知道它的目的是用来消除优化,实际上很少有人真正需要这样的应
 35 用。在内核中,程序员必须防止意外的并发访问破坏共享的数据结构,这其实是一个完全
 36 不同的任务。用来防止意外并发访问的保护措施,可以更加高效的避免大多数优化相关的
 37 问题。
 38 
 39 像volatile一样,内核提供了很多原语来保证并发访问时的数据安全(自旋锁, 互斥量,内
 40 存屏障等等),同样可以防止意外的优化。如果可以正确使用这些内核原语,那么就没有
 41 必要再使用volatile。如果仍然必须使用volatile,那么几乎可以肯定在代码的某处有一
 42 个bug。在正确设计的内核代码中,volatile能带来的仅仅是使事情变慢。
 43 
 44 思考一下这段典型的内核代码:
 45 
 46     spin_lock(&the_lock);
 47     do_something_on(&shared_data);
 48     do_something_else_with(&shared_data);
 49     spin_unlock(&the_lock);
 50 
 51 如果所有的代码都遵循加锁规则,当持有the_lock的时候,不可能意外的改变shared_data的
 52 值。任何可能访问该数据的其他代码都会在这个锁上等待。自旋锁原语跟内存屏障一样—— 它
 53 们显式的用来书写成这样 —— 意味着数据访问不会跨越它们而被优化。所以本来编译器认为
 54 它知道在shared_data里面将有什么,但是因为spin_lock()调用跟内存屏障一样,会强制编
 55 译器忘记它所知道的一切。那么在访问这些数据时不会有优化的问题。
 56 
 57 如果shared_data被声名为volatile,锁操作将仍然是必须的。就算我们知道没有其他人正在
 58 使用它,编译器也将被阻止优化对临界区内shared_data的访问。在锁有效的同时,
 59 shared_data不是volatile的。在处理共享数据的时候,适当的锁操作可以不再需要
 60 volatile —— 并且是有潜在危害的。
 61 
 62 volatile的存储类型最初是为那些内存映射的I/O寄存器而定义。在内核里,寄存器访问也应
 63 该被锁保护,但是人们也不希望编译器“优化”临界区内的寄存器访问。内核里I/O的内存访问
 64 是通过访问函数完成的;不赞成通过指针对I/O内存的直接访问,并且不是在所有体系架构上
 65 都能工作。那些访问函数正是为了防止意外优化而写的,因此,再说一次,volatile类型不
 66 是必需的。
 67 
 68 另一种引起用户可能使用volatile的情况是当处理器正忙着等待一个变量的值。正确执行一
 69 个忙等待的方法是:
 70 
 71     while (my_variable != what_i_want)
 72         cpu_relax();
 73 
 74 cpu_relax()调用会降低CPU的能量消耗或者让位于超线程双处理器;它也作为内存屏障一样出
 75 现,所以,再一次,volatile不是必需的。当然,忙等待一开始就是一种反常规的做法。
 76 
 77 在内核中,一些稀少的情况下volatile仍然是有意义的:
 78 
 79   - 在一些体系架构的系统上,允许直接的I/0内存访问,那么前面提到的访问函数可以使用
 80     volatile。基本上,每一个访问函数调用它自己都是一个小的临界区域并且保证了按照
 81     程序员期望的那样发生访问操作。
 82 
 83   - 某些会改变内存的内联汇编代码虽然没有什么其他明显的附作用,但是有被GCC删除的可
 84     能性。在汇编声明中加上volatile关键字可以防止这种删除操作。
 85 
 86   - Jiffies变量是一种特殊情况,虽然每次引用它的时候都可以有不同的值,但读jiffies
 87     变量时不需要任何特殊的加锁保护。所以jiffies变量可以使用volatile,但是不赞成
 88     其他跟jiffies相同类型变量使用volatile。Jiffies被认为是一种“愚蠢的遗留物"
 89     (Linus的话)因为解决这个问题比保持现状要麻烦的多。
 90 
 91   - 由于某些I/0设备可能会修改连续一致的内存,所以有时,指向连续一致内存的数据结构
 92     的指针需要正确的使用volatile。网络适配器使用的环状缓存区正是这类情形的一个例
 93     子,其中适配器用改变指针来表示哪些描述符已经处理过了。
 94 
 95 对于大多代码,上述几种可以使用volatile的情况都不适用。所以,使用volatile是一种
 96 bug并且需要对这样的代码额外仔细检查。那些试图使用volatile的开发人员需要退一步想想
 97 他们真正想实现的是什么。
 98 
 99 非常欢迎删除volatile变量的补丁 - 只要证明这些补丁完整的考虑了并发问题。
100 
101 注释
102 ----
103 
104 [1] http://lwn.net/Articles/233481/
105 [2] http://lwn.net/Articles/233482/
106 
107 致谢
108 ----
109 
110 最初由Randy Dunlap推动并作初步研究
111 由Jonathan Corbet撰写
112 参考Satyam Sharma,Johannes Stezenbach,Jesper Juhl,Heikki Orsila,
113 H. Peter Anvin,Philipp Hahn和Stefan Richter的意见改善了本档。

[转]剖析程序的内存布局

内存管理模块是操作系统的心脏;它对应用程序和系统管理非常重要。今后的几篇文章中,我将着眼于实际的内存问题,但也不避讳其中的技术内幕。由于不少概念是通用的,所以文中大部分例子取自32位x86平台的Linux和Windows系统。本系列第一篇文章讲述应用程序的内存布局。

在多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盘中。这个沙盘就是虚拟地址空间(virtual address space),在32位模式下它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每一个进程拥有一套属于它自己的页表,但是还有一个隐情。只要虚拟地址被使能,那么它就会作用于这台机器上运行的所有软件,包括内核本身。因此一部分虚拟地址必须保留给内核使用:

1

这并不意味着内核使用了那么多的物理内存,仅表示它可支配这么大的地址空间,可根据内核需要,将其映射到物理内存。内核空间在页表中拥有较高的特权级(ring 2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化:

2

蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。在上面的例子中,Firefox使用了相当多的虚拟地址空间,因为它是传说中的吃内存大户。地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。记住,这些段只是简单的内存地址范围,与Intel处理器的段没有关系。不管怎样,下面是一个Linux进程的标准的内存段布局:

3

当计算机开心、安全、可爱、正常的运转时,几乎每一个进程的各个段的起始虚拟地址都与上图完全一致,这也给远程发掘程序安全漏洞打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间布局的一致性,摸索着选择这些地址。如果让他们猜个正着,有人就会被整了。因此,地址空间的随机排布方式逐渐流行起来。Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。不幸的是,32位地址空间相当紧凑,给随机化所留下的空当不大,削弱了这种技巧的效果。

进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储局部变量和函数参数。调用一个方法或函数会将一个新的栈桢(stack frame)压入栈中。栈桢在函数返回时被清理。也许是因为数据严格的遵从LIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈的内容,只需要一个简单的指针指向栈的顶端即可。因此压栈(pushing)和退栈(popping)过程非常迅速、准确。另外,持续的重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每一个线程都有属于自己的栈。

通过不断向栈中压入的数据,超出其容量就有会耗尽栈所对应的内存区域。这将触发一个页故障(page fault),并被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常是8MB),那么一般情况下栈会被加长,程序继续愉快的运行,感觉不到发生了什么事情。这是一种将栈扩展至所需大小的常规机制。然而,如果达到了最大的栈空间大小,就会栈溢出(stack overflow),程序收到一个段错误(Segmentation Fault)。当映射了的栈区域扩展到所需的大小后,它就不会再收缩回去,即使栈不那么满了。这就好比联邦预算,它总是在增长的。

动态栈增长是唯一一种访问未映射内存区域(图中白色区域)而被允许的情形。其它任何对未映射内存区域的访问都会触发页故障,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误。

在栈的下方,是我们的内存映射段。此处,内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用(实现)或Windows的CreateFileMapping() / MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用于加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。‘大块’意味着比MMAP_THRESHOLD还大,缺省是128KB,可以通过mallopt()调整。

说到堆,它是接下来的一块地址空间。与栈一样,堆用于运行时内存分配;但不同点是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。因此,满足内存请求就成了语言运行时库及内核共同的任务。在C语言中,堆分配的接口是malloc()系列函数,而在具有垃圾收集功能的语言(如C#)中,此接口是new关键字。

如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与。否则,堆会被扩大,通过brk()系统调用(实现)来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法,应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆也可能会变得零零碎碎,如下图所示:

4

最后,我们来看看最底部的内存段:BSS,数据段,代码段。在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,它们的值不是直接在程序的源代码中设定的。BSS内存区域是匿名的:它不映射到任何文件。如果你写static int cntActiveUsers,则cntActiveUsers的内容就会保存在BSS中。

另一方面,数据段保存在源代码中已经初始化了的静态变量内容。这个内存区域不是匿名的。它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static int cntWorkerBees = 10,则cntWorkerBees的内容就保存在数据段中了,而且初始值为10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响到被映射的文件。也必须如此,否则给全局变量赋值将会改动你硬盘上的二进制镜像,这是不可想象的。

下图中数据段的例子更加复杂,因为它用了一个指针。在此情况下,指针gonzo(4字节内存地址)本身的值保存在数据段中。而它所指向的实际字符串则不在这里。这个字符串保存在代码段中,代码段是只读的,保存了你全部的代码外加零零碎碎的东西,比如字符串字面值。代码段将你的二进制文件也映射到了内存中,但对此区域的写操作都会使你的程序收到段错误。这有助于防范指针错误,虽然不像在C语言编程时就注意防范来得那么有效。下图展示了这些段以及我们例子中的变量:

5

你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。下一篇文章讲说明这些“区域”(area)的真正含义。有时人们提到“数据段”,指的就是全部的数据段 + BSS + 堆。

你可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在Linux中是一种“灵活布局”(flexible layout),而且以此作为默认方式已经有些年头了。它假设我们有值RLIMIT_STACK。当情况不是这样时,Linux退回使用“经典布局”(classic layout),如下图所示:

6

对虚拟地址空间的布局就讲这些吧。下一篇文章将讨论内核是如何跟踪这些内存区域的。我们会分析内存映射,看看文件的读写操作是如何与之关联的,以及内存使用概况的含义。

一副不错的内核I/O架构图

linux-io-stack-diagram_v1.0

rep;nop 与pause优化

前几天我在对内核的pktgen做调优时发现主线程中会有cpu_relax()

static int pktgen_thread_worker(void *arg)
{
	DEFINE_WAIT(wait);
	struct pktgen_thread *t = arg;
	struct pktgen_dev *pkt_dev = NULL;
	int cpu = t->cpu;

	BUG_ON(smp_processor_id() != cpu);

	init_waitqueue_head(&t->queue);
	complete(&t->start_done);

	pr_debug("starting pktgen/%d:  pid=%d\n", cpu, task_pid_nr(current));

	set_current_state(TASK_INTERRUPTIBLE);

	set_freezable();

	while (!kthread_should_stop()) {
		pkt_dev = next_to_run(t);

		if (unlikely(!pkt_dev && t->control == 0)) {
			if (pktgen_exiting)
				break;
			wait_event_interruptible_timeout(t->queue,
							 t->control != 0,
							 HZ/10);
			try_to_freeze();
			continue;
		}
		__set_current_state(TASK_RUNNING);

		if (likely(pkt_dev)) {
			pktgen_xmit(pkt_dev);

			if (need_resched())
				pktgen_resched(pkt_dev);
			else
				cpu_relax();
		}
................................................

	return 0;
}

我觉得这个忙等待应该是没有必要的,于是把else分支去掉。再测试,结果性能不升反降,出乎意料。cpu_relax() 在arch/x86/boot/boot.h 中定义:

#define cpu_relax() asm volatile("rep; nop")

google了一下这个rep;nop,结果是这个语句不是循环N个nop,而是他们俩挨个出现的时候会被强制编译成pause指令,且只执行一次。查了下intel的构架手册,上面指明了pause指令可以提高spin-wait loops的性能,这个是用在spinlock上么?后面再看。

性能提高的原因是分支预测,指令重排的动作都不做了。另外也会使cpu进入省电状态。

再看一下spin_lock使用了同样的技巧:

static __always_inline void __ticket_spin_lock(arch_spinlock_t *lock)
{
	short inc = 0x0100;

	asm volatile (
		LOCK_PREFIX "xaddw %w0, %1\n"
		"1:\t"
		"cmpb %h0, %b0\n\t"
		"je 2f\n\t"
		"rep ; nop\n\t"
		"movb %1, %b0\n\t"
		/* don't need lfence here, because loads are in-order */
		"jmp 1b\n"
		"2:"
		: "+Q" (inc), "+m" (lock->slock)
		:
		: "memory", "cc");
}

dnsmasq+dnscrypt

dnsmasq+dnscrypt 一句话很好很强大~~~

pdnsd+dnscrypt 更好更强大!!!

dnsmasq+pdnsd+dnscrypt …………..

让apple magic trackpad在linux下跑起来

因为之前没有研究过linux下的bluttooth协议栈所以走了不少弯路。

先说apple magic trackpad这个产品本身。他是apple在推出magic mouse后的更新产品,前者的口碑并不好,主要是因为手感很差,比mighty mouse差许多。trackpad几乎就是一个支持10点触摸的无光电的magic mouse,保留了物理按键。

trackpad本身属于hid设备,设备id在drivers/hid/hid-ids.h 中有描述:

#define USB_VENDOR_ID_APPLE		0x05ac
#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE	0x0304
#define USB_DEVICE_ID_APPLE_MAGICMOUSE	0x030d
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD	0x030e

触摸控制器用的是bcm5974,也是macbook上触摸板的控制器。蓝牙控制器是BCM2042。

驱动方面,它和magic mouse共用一个驱动hid_magicmouse 在drivers/hid/hid-magicmouse.c 。内核中关于触控设备的驱动是越来越多了。但它确实和drivers/input/mouse/bcm5974.c 没有关系,尽管bcm5974确实是trackpad的触控芯片。但这个驱动只是用来支持macbook的触摸板的。另外一个是drivers/input/mouse/appletouch.c ,他也是用来支持苹果设备上的触控板,但具体是什么我也不知道。看代码中的注释好像只支持2005年2月前的型号。

接着看一下蓝牙适配器方面的。

我用的适配器是个usb设备。lsusb显示如下:

Bus 005 Device 003: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)

Bus 005 Device 003: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass          224 Wireless
  bDeviceSubClass         1 Radio Frequency
  bDeviceProtocol         1 Bluetooth
  bMaxPacketSize0        16
  idVendor           0x0a12 Cambridge Silicon Radio, Ltd
  idProduct          0x0001 Bluetooth Dongle (HCI mode)
  bcdDevice            1.34
  iManufacturer           0 
  iProduct                0 
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength          108
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           3
      bInterfaceClass       224 Wireless
      bInterfaceSubClass      1 Radio Frequency
      bInterfaceProtocol      1 Bluetooth
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0010  1x 16 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass       224 Wireless
      bInterfaceSubClass      1 Radio Frequency
      bInterfaceProtocol      1 Bluetooth
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            1
          Transfer Type            Isochronous
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0000  1x 0 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            1
          Transfer Type            Isochronous
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0000  1x 0 bytes
        bInterval               1
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       1
      bNumEndpoints           2
      bInterfaceClass       224 Wireless
      bInterfaceSubClass      1 Radio Frequency
      bInterfaceProtocol      1 Bluetooth
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            1
          Transfer Type            Isochronous
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0009  1x 9 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            1
          Transfer Type            Isochronous
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0009  1x 9 bytes
        bInterval               1
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       2
      bNumEndpoints           2
      bInterfaceClass       224 Wireless
      bInterfaceSubClass      1 Radio Frequency
      bInterfaceProtocol      1 Bluetooth
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x83  EP 3 IN
        bmAttributes            1
          Transfer Type            Isochronous
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0011  1x 17 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            1
          Transfer Type            Isochronous
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0011  1x 17 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)

两个接口, 三个端点, 端点0,用来接收键盘鼠标,端点1用来传送文件,端点2用来接蓝牙耳机(传送音视频)。

蓝牙适配器插上去后,usb core将提供usb设备支持,然后btusb.ko连同依赖的bluetooth.ko会被动地加载上去,提供内核层的蓝牙协议支持。接下来更重要的是在用户层,蓝牙的业务支持是在用户层一个叫bluez的软件包支持,他活动的主要进程叫bluetoothd。像pin码匹配,设备搜索,设备服务类型(service class)这类底层业务都是它来负责。这部分非常复杂,这里也无需展开去讲这些。用TCP/IP协议栈来作对比的话, 内核部分的蓝牙支持只相当于链路层和网络层,再往上就是bluez干的事 。

090914082304 090914082055  bluez-large 1-s2.0-S1570870510001551-gr3

折腾一段时间后我发现linux下蓝牙设备支持的好坏很大程度上取决于bluez这个软件包。

如果要看trackpad的电池状况还要打开内核选项,CONFIG_HID_BATTERY_STRENGTH然后cat /sys/class/power_supply/hid-<bd>-battery/capacity ,这个选项不是默认选中很令人奇怪。

设备,内核,及蓝牙都熟悉后设置一下xorg了:

Section "InputClass"
        Identifier "Magic Trackpad"
        Driver "synaptics"
# This option is recommend on all Linux systems using evdev, but cannot be
# enabled by default. See the following link for details:
# http://who-t.blogspot.com/2010/11/how-to-ignore-configuration-errors.html
# enable tap-to-click as default (bnc#722457)
	MatchUSBID "05ac:030e"
	Option "SHMConfig" "On"
	Option "VertTwoFingerScroll" "0"
	Option "VertEdgeScroll" "0"
	Option "HorizTwoFingerScroll" "0"
	Option "VertEdgeScroll" "0"
#	Option "TapButton1" "0"
	Option "TapButton2" "0"
	Option "TapButton3" "0"
#	Option "ClickFinger1" "0"
	Option "ClickFinger2" "0"
	Option "ClickFinger3" "0"
EndSection

把默认的设置都删除,重新写吧。xorg的驱动是synaptics。这里之所以把button2,3 都禁掉是因为后面要用touchegg来实现手势,如果xorg监听这些手势那么touchegg将不能监听。而不禁用掉button1是因为touchegg不能管理单指手势所以还是需要xorg来管理。

udev自启动相关设置如下:

新增 /lib/udev/rules.d/98-appletrackpad.rules:

ACTION==”add”, SUBSYSTEMS==”hid”, KERNELS==”0005:05AC:030E.*”, ENV{DISPLAY}=”:0″, ENV{XAUTHORITY}=”/root/.Xauthority”, RUN+=”/bin/systemctl restart touchegg.service”

ACTION==”remove”, SUBSYSTEMS==”hid”, KERNELS==”0005:05AC:030E.*”, ENV{DISPLAY}=”:0″, ENV{XAUTHORITY}=”/root/.Xauthority”, RUN+=”/bin/systemctl stop touchegg.service”

新增/etc/systemd/system/touchegg.service:

[Unit]
Description=touchegg service

[Service]
Environment=DISPLAY=127.0.0.1:0
ExecStart=/usr/bin/touchegg-wrap.sh
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

新增/usr/bin/touchegg-warp.sh:

#!/bin/sh
export HOME=/root
/usr/bin/synaptikscfg load  #这个还有问题,需要KDE上下文
exec /usr/bin/touchegg > /dev/NULL 2>&1

补充一下,kernel-3.4有一个bug,无法显示trackpad的电池电量,即使开启CONFIG_HID_BATTERY_STRENGTH特性

 

update:

opensuse 13.2:

/lib/udev/rules.d/98-appletrackpad.rules:

ACTION=="add", KERNEL=="mouse[0-9]*", SUBSYSTEM=="input", SUBSYSTEMS=="hid", DRIVERS=="magicmouse", KERNELS=="0005:05AC:030E.*", ENV{HOME}="/root", ENV{XAUTHORITY}="/root/.Xauthority",  ENV{DISPLAY}=":0.0", RUN+="/usr/bin/synaptikscfg load"

touchegg的安装方法:
到http://download.opensuse.org/repositories/home:/sirkonst:/touchegg下载这些rpm:
libutouch-evemu1 libutouch-grail1 utouch-geislibutouch-frame1 libutouch-geis1-2.2.10-2.1.x86_64.rpm touchegg-1.1.1-3.1.x86_64.rpm

也可以到http://download.opensuse.org/repositories/home:/scarabeus_iv/openSUSE_13.2/x86_64/下载touchegg和libgeis1的rpm装上就可以了。
/root/.config/touchegg/touchegg.conf中的<application name="...">的name可以通过xprop|grep WM_CLASS获得

新增/etc/systemd/user/touchegg.service:
[Unit]
Description=touchegg service

[Service]
Environment=DISPLAY=127.0.0.1:0
Environment=HOME=/root
ExecStart=/usr/bin/touchegg
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=default

编辑/usr/lib/systemd/user/default.target

[Unit]
Description=Default
Documentation=man:systemd.special(7)
Requires=basic.target
Wants=touchegg.service
After=basic.target touchegg.service
AllowIsolate=yes

红色的是新增的。