How to automatically start supervisord on Linux (Ubuntu)


sudo chmod +x /etc/init.d/supervisord
and to automatically schedule it, do

sudo update-rc.d supervisord defaults
Make ensure correct pid in /etc/supervisord.conf which is mapped in /etc/init.d/supervisord

example: pidfile=/var/run/
Stop and Start work properly

service supervisord stop
service supervisord start

Ubuntu 14.04上安装smokeping


$ sudo apt-get install smokeping curl libauthen-radius-perl libnet-ldap-perl libnet-dns-perl libio-socket-ssl-perl libnet-telnet-perl libsocket6-perl libio-socket-inet6-perl apache2

  •  MASTER模式


cd /etc/apache2/conf-available
sudo ln -s ../../smokeping/apache2.conf smokeping.conf


$ cd /etc/smokeping
$ cat apache2.conf

ScriptAlias /smokeping/smokeping.cgi /usr/lib/cgi-bin/smokeping.cgi
Alias /smokeping /usr/share/smokeping/www
Directory "/usr/share/smokeping/www">
Options FollowSymLinks

sudo a2enconf smokeping
sudo a2enmod cgid

编辑/etc/smokeping/slavesecrets.conf 和 /etc/smokeping/slave-secrets。其中key是密码,slave-server是”被监控者”的名字。

$ cd /etc/smokeping/
$ echo key > slavesecrets.conf
$ echo slave-server:key >> smokeping_secrets




++ Site
menu = Corp
title = CORP – slave server
host = # slave server的地址
slaves = slave-server


$ cat /etc/default/smokeping


$ ls -la /etc/smokeping
total 48
drwxr-xr-x 3 root root 4096 Jul 6 23:57 .
drwxr-xr-x 128 root root 12288 Jul 6 15:45 ..
-rw-r–r– 1 root root 191 Jul 6 17:21 apache2.conf
-rw-r–r– 1 root root 2031 Jan 29 2014 basepage.html
-rw-r–r– 1 root root 290 Jan 29 2014 config
drwxr-xr-x 2 root root 4096 Jul 7 00:16 config.d
-rw——- 1 smokeping root 21 Jul 6 23:56 slavesecrets.conf
-rw-r–r– 1 root root 1564 Jan 29 2014 smokemail
-rw——- 1 www-data www-data 87 Jul 6 22:21 smokeping_secrets
-rw-r–r– 1 root root 3815 Jan 29 2014 tmail
以上文件中smokeping_secrets是给web server用的(所有者应该是:www-data),因为slave端是通过web server取得密匙。

  • SLAVE模式


$ cat /etc/default/smokeping

select master or slave mode


in master mode, the rest of the configuration is in



in slave mode, uncomment and set the following variables too

see smokeping(1)


Mandatory configuration

MASTER_URL= ## 该url对照master的apache conf档配置

Optional configuration



$ echo key > slavesecrets.conf


$ ls -la /etc/smokeping
yiwang@localhost:/etc/smokeping$ ls -la
total 48
drwxr-xr-x 3 root root 4096 Jul 6 23:40 .
drwxr-xr-x 140 root root 12288 Jul 6 17:05 ..
-rw-r–r– 1 root root 191 Jan 29 2014 apache2.conf
-rw-r–r– 1 root root 2031 Jan 29 2014 basepage.html
-rw-r–r– 1 root root 290 Jan 29 2014 config
drwxr-xr-x 2 root root 4096 Jul 6 22:31 config.d
-rw——- 1 smokeping smokeping 21 Jul 6 23:40 slavesecrets.conf
-rw-r–r– 1 root root 1564 Jan 29 2014 smokemail
-rw-rw-rw- 1 smokeping smokeping 21 Jul 6 23:40 smokeping_secrets
-rw-r–r– 1 root root 3815 Jan 29 2014 tmail

Cloud Contacts

Make your iPhone support Google contacts, right now!

Google Contacts is the default address book provider for all of Google software and devices(Gmail, Android,etc.).
(The website of Google Contacts:

Feature List:
1.It is an independent address book App on iOS, and it has a convenient UI likes Apple\’s contacts.
2.It supports multi-account. You can hold multiple Google accounts (unlimited count) in the App.
3.It can migrate contacts content to your iOS devices.
4.It does not gather any personal informations. All of content data only stores in your service provider(Google) and devices.
5.It was developed base on Google Data Protocol (Google native API), and provides more compatibility.

Other Features:
1.Direct call your contacts from iPhone.
2.Synchronous data arranged in groups, do not have to worry about the address book classification confusion.
3.You can edit your contact content locally, and also synchronise to iOS contacts(by using migration assistant).
4.On \”Summary\” page, you can send email and make phone calls.
5.Address book data on the service providers is a read-only access by default, delete the account and group does not affect the original content.

Possible Issues:
1.Google updated the password authentication methods, provides the \”2-step verification”. In order to avoid the password validation fail, you should set an application special password for this App if to use this function. Refer to:
2.[For Chinese mainland user] Google contacts are downloaded for too long time and timeout. Google’s servers are in the United States, subject to the environment of the Chinese network, the Google’s servers are often blocked. The solution: When a timeout occurs, retry more times.
3.I have to remove the Facebook contacts supporting, because Facebook does not permit other Apps to access contacts.

git subtree vs submodule

Alternatives To Git Submodule: Git Subtree


The Internet is full of articles on why you should not use Git submodules. I mostly agree, although I am not so harsh in my evaluation. As I explained in a previous postsubmodules are useful for a few use cases but have several drawbacks.

Are there alternatives? The answer is: yes! There are (at least) two tools that can help track the history of software dependencies in your project while allowing you to keep using git:

In this post I will be looking at git subtree and show why it is an improvement – albeit not perfect – over git submodule.

As a working example I run to my usual use case. How do I easily store and keep up to date the vim plugins used in my dotfiles?

Why use subtree instead of submodule?

There are several reasons why you might find subtree better to use:

  • Management of a simple workflow is easy.
  • Older version of git are supported (even before v1.5.2).
  • The sub-project’s code is available right after the clone of the super project is done.
  • subtree does not require users of your repository to learn anything new, they can ignore the fact that you are using subtree to manage dependencies.
  • subtree does not add new metadata files like submodules doe (i.e. .gitmodule).
  • Contents of the module can be modified without having a separate repository copy of the dependency somewhere else.

In my opinion the drawbacks are acceptable:

  • You must learn about a new merge strategy (i.e. subtree).
  • Contributing code back upstream for the sub-projects is slightly more complicated.
  • The responsibility of not mixing super and sub-project code in commits lies with you.

How to use git subtree?

git subtree is available in stock version of git available since May 2012 – 1.7.11+. The version installed by homebrew on OSX already has subtree properly wired but on some platforms you might need to follow the installation instructions.

Let me show you the canonical example of tracking a vim plug-in using git subtree.

The quick and dirty way without remote tracking

If you just want a couple of one liners to cut and paste just read this paragraph.

First add the subtree at a specified prefix folder:

git subtree add –prefix .vim/bundle/tpope-vim-surround master –squash

(The common practice is to not store the entire history of the sub-project in your main repository, but If you want to preserve it just omit the –squash flag.)

The above command produces this output:

git fetch master
warning: no common commits
remote: Counting objects: 338, done.
remote: Compressing objects: 100% (145/145), done.
remote: Total 338 (delta 101), reused 323 (delta 89)
Receiving objects: 100% (338/338), 71.46 KiB, done.
Resolving deltas: 100% (101/101), done.
* branch            master     -} FETCH_HEAD
Added dir \’.vim/bundle/tpope-vim-surround\’

As you can see this records a merge commit by squashing the whole history of the vim-surroundrepository into a single one:

1bda0bd [3 minutes ago] (HEAD, stree) Merge commit \’ca1f4da9f0b93346bba9a430c889a95f75dc0a83\’ as \’.vim/bundle/tpope-vim-surround\’ [Nicola Paolucci]
ca1f4da [3 minutes ago] Squashed \’.vim/bundle/tpope-vim-surround/\’ content from commit 02199ea [Nicola Paolucci]

If after a while you want to update the code of the plugin from the upstream repository you can just subtree pull:

git subtree pull –prefix .vim/bundle/tpope-vim-surround master –squash

This is very quick and painless but the commands are slightly lengthy and hard to remember. We can make the commands shorter by adding the sub-project as a remote.

Adding the sub-project as a remote

Adding the subtree as a remote allows us to refer to it in shorter form:

git remote add -f tpope-vim-surround

Now we can add the subtree (as before), but now we can refer to the remote in short form:

git subtree add –prefix .vim/bundle/tpope-vim-surround tpope-vim-surround master –squash

The command to update the sub-project at a later date becomes:

git fetch tpope-vim-surround master
git subtree pull –prefix .vim/bundle/tpope-vim-surround tpope-vim-surround master –squash

Contributing back to upstream

We can freely commit our fixes to the sub-project in our local working directory now.

When it’s time to contribute back to the upstream project we need to fork the project and add it as another remote:

git remote add durdn-vim-surround ssh://

Now we can use the subtree push command like the following:

git subtree push –prefix=.vim/bundle/tpope-vim-surround/ durdn-vim-surround mastergit push using:  durdn-vim-surround master
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 308 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)
To ssh://
02199ea..dcacd4b  dcacd4b21fe51c9b5824370b3b224c440b3470cb -} master

After this we’re ready and we can open a pull-request to the maintainer of the package.

Without using the subtree command

git subtree is different from the subtree merge strategy. You can still use the merge strategy even if for some reason git subtree is not available. Here is how you would go about it:

Add the dependency as a simple git remote:

git remote add -f tpope-vim-surround

Before reading the contents of the dependency into the repository it’s important to record a merge so that we can track the entire tree history of the plug-in up to this point:

git merge -s ours –no-commit tpope-vim-surround/master

Which outputs:

Automatic merge went well; stopped before committing as requested

We then read the content of the latest tree-object in the plugin repository into our working directory ready to be committed:

git read-tree –prefix=.vim/bundle/tpope-vim-surround/ -u tpope-vim-surround/master

Now we can commit (and it will be a merge commit that will preserve the history of the tree we read):

git ci -m\”[subtree] adding tpope-vim-surround\”[stree 779b094] [subtree] adding tpope-vim-surround

When we want to update the project we can now pull using the subtree merge strategy:

git pull -s subtree tpope-vim-surround master


After having used submodule for a while I appreciate git subtree much more, lots of submodule problems are superseded and solved by subtree. As usual, with all things git, there is a learning curve to make the most of the feature.

Follow me @durdn and the awesome @AtlDevtools team for more Git rocking.

Android 安全机制

1 Android 安全机制概述

Android 是一个权限分离的系统 。 这是利用 Linux 已有的权限管理机制,通过为每一个 Application 分配不同的 uid 和 gid , 从而使得不同的 Application 之间的私有数据和访问( native 以及 java 层通过这种 sandbox 机制,都可以)达到隔离的目的 。 与此 同时, Android 还 在此基础上进行扩展,提供了 permission 机制,它主要是用来对 Application 可以执行的某些具体操作进行权限细分和访问控制,同时提供了 per-URI permission 机制,用来提供对某些特定的数据块进行 ad-hoc 方式的访问。

1.1 uid 、 gid 、 gids

Android 的权限分离的基础是建立在 Linux 已有的 uid 、 gid 、 gids 基础上的 。

  • UID 。 Android 在 安装一个应用程序,就会为 它 分配一个 uid (参考 PackageManagerService 中的 newUserLP 实现)。其中普通 A ndroid 应用程序的 uid 是从 10000 开始分配 (参见 Process.FIRST_APPLICATION_UID ), 10000 以下是系统进程的 uid 。
  • GID 。对 于普通应用程序来说, gid 等于 uid 。由于每个应用程序的 uid 和 gid 都不相同, 因此不管是 native 层还是 java 层都能够达到保护私有数据的作用 。
  • GIDS 。 gids 是由框架在 Application 安装过程中生成,与 Application 申请的具体权限相关。 如果 Application 申请的相应的 permission 被 granted ,而且 中有对应的 gid s , 那么 这个 Application 的 gids 中将 包含这个 gid s 。

uid gid gids 的 详细 设置过程: 请参考 ActivityManagerService 中的 startProcessLocked 。在通过 zygote 来启动一个 process 时,直接将 uid 传给 给了 gid 。再通过 zygote 来 fork 出新的进程( 中的forkAndSpecialize ),最终在 native 层( dalvik_system_zygote.c )中的 forkAndSpecializeCommon 中通过 linux 系统调用来进行 gid 和 uid 和 gids 的设置。

1.2 permission

一个权限主要包含三个方面的信息:权限的名称;属于的权限组;保护级别。一个权限组是指把权限按照功能分成的不同的集合。每一个权限组包含若干具体权限,例如在 COST_MONEY 组中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和费用相关的权限。

每个权限通过 protectionLevel 来标识保护级别: normal , dangerous , signature , signatureorsystem 。不同的保护级别代表了程序要使用此权限时的认证方式。 normal 的权限只要申请了就可以使用; dangerous 的权限在安装时需要用户确认才可以使用; signature 和 signatureorsystem 的权限需要使用者的 app 和系统使用同一个数字证书。

Package 的权限信息主要 通过在 AndroidManifest.xml 中通过一些标签来指定。如 <permission> 标签, <permission-group> 标签 <permission-tree> 等标签。如果 package 需要申请使用某个权限,那么需要使用 <use-permission> 标签来指定。

Android permission 管理机制

2.1 Framework permission 机制

2.1.1 安装入口

permission 的初始化,是指 permission 的向系统申请,系统进行检测并授权,并建立相应的数据结构。绝大多数的情况下 permission 都是从一个 package 中扫描所得,而这发生在 package 安装和升级的时候。一般有如下几种 安装入口:

  • packageInstaller , package 被下载安装时会触发使用。 packageInstaller 会通过 AppSecurityPermissions 来检查 dangerous 的权限,并对用户给出提示。
  • pm 命令 。
  • adb install 。最终还是 调用 pm install 来安装 apk 包。
  • 拷贝即安装。 PackageManagerService 中使用 AppDirObserver 对 /data/app/ 进行监视 ,如果有拷贝即触发安装。

这些安装方式 最终都会通过调用 PackageManagerService 中的函数来完成程序的安装。

2.1.2 permission 创建

第一步,从 AndroidManifest.xml 中提取 permission 信息。主要提取如下信息:

² shared uid 指定与其它 package 共享同一个 uid  ² permission 提取 permissions 标签指定属性。它使用 permissionInfo 来描述一个权限的基本信息。需要指定 protectedLevel 信息,并指定所属 group 信息。它将被添加到这个 package 的 permissions这个 list 结构中。 ² permission-tree 提取 permissions-tree 标签属性。 permissions-tree 也通过 permissionInfo 来描述,并被添加到 package 的 permissions 这个 list 结构中。 permission-tree 只是一个名字空间,用来向其中动态添加一些所谓 Dynamic 的 permission ,这些 permission 可以动态修改。这些 permission 名称要以 permission-tree 的名称开头。它本身不是一种权限,没有 protectedLevel 和所属group 。只是保存了所属的 packge 和权限名(带有 package 前缀的)。 ² permission-group 定义 permission 组信息,用 PermissionGroup 表示。本身不代表一个权限,会添加进入 package 的 permissionGroups 这个 list 中。 ² uses-permission 定义了 package 需要申请的权限名。将权限名添加到 package 的 requestedPermissions 这个 list 中。 ² adopt-permissions 将该标签指定的 name 存入 package 的 mAdoptPermissions 这个 list 中。 Name 指定了这个 package 需要从 name 指定的 package 进行权限领养。在 system package 进行升级时使用。 第二步。获取 Package 中的证书,验证,并将签名信息保存在 Package 结构中。 1. 如果该 package 来自 system img (系统 app ),那么只需要从该 Package 的 AndroidManifest.xml 中获取签名信息,而无需验证其完整性。但是如果这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。 2. 如果是普通的 package ,那么需要提取证书和签名信息,并对文件的完成性进行验证。 第三步。如果是普通的 package ,那么清除 package 的 mAdoptPermissions 字段信息(系统 package 升级才使用)。

第四步。如果在 AndroidManifest.xml 中指定了 shared user ,那么先查看全局 list 中( mSharedUsers )是否该 uid 对应的 SharedUserSetting 数据结构,若没有则新分配一个 uid ,创建 SharedUserSetting 并保存到全局全局 list ( mSharedUsers )中。 mUserIds 保存了系统中已经分配的 uid 对应的 SharedUserSetting 结构。每次分配时总是从第一个开始轮询,找到第一个空闲的位置 ,然后加上 FIRST_APPLICATION_UID 即可。 第五步。创建 PackageSettings 数据结构。并将 PackageSettings 与 SharedUserSetting 进行绑定。其中 PackageSettings 保存了 SharedUserSetting 结构;而 SharedUserSetting 中会使用 PackageSettings 中的签名信息填充自己内部的签名信息,并将 PackageSettings 添加到一个队列中,表示 PackageSettings 为其中的共享者之一。 在创建时,首先会以 packageName 去全局数据结构 mPackages 中查询是否已经有对应的 PackageSettings 数据结构存在。如果已经存在 PackageSettings 数据结构(比如这个 package 已经被 uninstall ,但是还没有删除数据,此时 package 结构已经被释放)。那么比较该 package 中的签名信息(从 AndroidManifest 中扫描得到)与 PackageSettings 中的签名信息是否匹配。如果不匹配但是为 system package ,那么信任此 package ,并将 package 中的签名信息更新到已有的 PackageSettings 中去,同时如果这个 package 与其它 package 共享了 uid ,而且 shared uid 中保存的签名信息与当前 package 不符,那么签名也验证失败。 第六步。如果 mAdoptPermissions 字段不为空,那么处理 permission 的领养(从指定的 package 对应的 PackageSettings 中,将权限的拥有者修改为当前 package ,一般在 system app 升级的时候才发生,在此之前需要验证当被领养的 package 已经被卸载,即检查 package 数据结构是否存在)。

第七步。添加自定义权限。将 package 中定义的 permissionGroup 添加到全局的列表 mPermissionGroups 中去;将 package 中定义的 permissions 添加到全局的列表中去(如果是 permission-tree 类型,那么添加到 mSettings.mPermissionTrees ,如果是一般的 permission 添加到 mSettings.mPermissions 中)。

第八步。清除不一致的 permission 信息。 1. 清除不一致的 permission-tree 信息。如果该 permission-tree 的 packageSettings 字段为空,说明还未对该 package 进行过解析(若代码执行到此处时 packageSettings 肯定已经被创建过),将其 remove 掉。如果 packageSettings 不为空,但是对应的 package 数据结构为空(说明该 package 已经被卸载,但数据还有保留),或者 package 数据结构中根本不含有这个permission-tree ,那么将这个 permission-tree 清除。 2. 清除不一致的 permission 信息。如果 packageSettings 或者 package 结构为空(未解析该 package 或者被卸载,但数据有保留),或者 package 中根本没有定义该 permission ,那么将该 permission 清除。 第九步。对每一个 package 进行轮询,并进行 permission 授权。 1. 对申请的权限进行检查,并更新 grantedPermissions 列表 2. 如果其没有设置 shared user id ,那么将其 gids 初始化为 mGlobalGids ,它从 permission.xml 中读取。 3. 遍历所有申请的权限,进行如下检查 )如果是该权限是 normal 或者 dangerous 的。通过检查。 )如果权限需要签名验证。如果签名验证通过。还需要进行如下检查  如果程序升级,而且是 system package 。那么是否授予该权限要看原来的 package 是否被授予了该权限。如果被授予了,那么通过检查,否则不通过。  如果是新安装的。那么检查通过。 4. 如果 中检查通过,那么将这个 permission 添加到 package 的 grantedPermissions 列表中,表示这个 permission 申请成功( granted )。申请成功的同时会将这个申请到的 permission的 gids 添加到这个 package 的 gids 中去。 5. 将 permissionsFixed 字段标准为 ture ,表示这个 packge 的 permission 进行过修正。后续将禁止对非 system 的 app 的权限进行再次修正。

2.1.3 Dynamic permission 的管理

PackageManagerService 提供了 addPermission/ removePermission 接口用来动态添加和删除一些权限。但是这些权限必须是所谓的动态权限( BasePermission.TYPE_DYNAMIC )。

一个 Package 如果要添加 Dynamic permissions ,首先必须要在 manifest 中申明 <permission-tree> 标签,它实际上是一个权限的名字空间(例如,“ ”这个权限就是 permission-tree “ ”的成员),本身不是一个权限。一个 Package 只能为自己的 permission-tree 或者拥有相同的 uid 的 package 添加或者删除权限。

Package 不能够通过这种接口去修改在 manifest 中静态申请的权限,否则抛出异常。 首先查找这个 permission 在全局 permission 列表 mSettings.mPermissions 中是否存在。如果存在,而且类型为 BasePermission.TYPE_DYNAMIC 那么根据传入的权限信息修改全局表中的权限信息,并触发 permissions.xml 的持久化。 如果在全局的 permission 列表 mSettings.mPermissions 中没有找到,先找到这个 permission 所在 permissionTree ,然后添加到全局 permission 列表 mSettings.mPermissions 中去,并触发 permissions.xml 的持久化。

2.1.4 Uri permission 的管理

下面两个 接口 主要用于 Uri permission 的管理 (其实现在 ActivityManagerService 中)。 // 为指定的 uid 和 targetPkg 添加对某个 content Uri 的读或者写权限。 public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode) throws RemoteException; // 清除所有通过 grantUriPermission 对某个 Uri 授予的权限。 public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode) throws RemoteException; grantUriPermission 主要的实现过程分析。 grantUriPermission 分析: 1. 验证 caller 的 ProcessRecord 和 targetPkg 不为空。否则检测不通过。 2. 验证所请求的 mode 为 Intent.FLAG_GRANT_READ_URI_PERMISSION 或者为 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ,否则不通过。 3. 确保参数 Uri 是一个 content Uri 。否则,则检测不通过。 4. 通过 Uri 得到目标 ContentProvider ,如果不存在,则检测不通过。 5. 从 PackageManagerService 中获得 targetPkg 对应的 uid  6. 检查 target uid 所对应的 package 是否真正需要这个权限? 先判断要申请的是读还是写权限,然后查看对应的 ContentProvider 中对应的 readPermission writePermission 字段是否保存了权限名称。 如果该字段不为空,则以 target uid 和该权限名去PackageManagerService 中去查找该 uid 是否被 granted 了该权限。如果已经获得了该权限,那么无需再去为这个 Activity 去申请这个 Uri 权限了,返回。否者继续执行如下操作。 7. 检查这个 ContentProvider 的 grantUriPermissions 开关变量,是否允许对其它 package 进行权限的 grant 操作。如果禁止,那么抛出异常。 8. 检查这个 ContentProvider 是否设置了 Uri 的过滤类型 uriPermissionPatterns ,如果设置了过滤类型,则将需要申请权限的 Uri 与之匹配。匹配不同过,则抛出异常。 9. 检查调用者自己是否有权限访问这个 Uri 。如果没有,抛出异常。 10. 从 mGrantedUriPermissions 中取得 target uid 对应的 HashMap<Uri, UriPermission> 数据结构。用 target uid 和 Uri 生成 UriPermission 并保存在 mGrantedUriPermissions 中。 revokeUriPermission 实现分析。 找到该 Uri 对应的 ContentProvider ,然后删除 mGrantedUriPermissions 中与 Uri 对应的所有权限。

2.2 permission 的动态检查

这里的动态检查是指是 package 在程序运行过程中进行某些操作或者数据访问时才进行的 check ,与之对应的是应用程序安装或者升级时 PackageManagerService 通过扫描包中的静态权限信息相对应。

系统与权限 检查 相关的机制的实现主要集中在 PackageManagerService 和 ActivityManagerService 中。 ActivityManagerService 主要负责的是底层的 uid 层次的身份检查; PackageManagerService 则维护了 uid 到自己拥有的和被授予的权限的一张表。在通过 ActivityManagerService 的身份检查后, PackageManagerService 根据请求者的 uid 来查看这张表,判断其是否具有相应的权限。

除此之外, per-URI permission 机制的实现也需要一张表,它维护在 ActivityManagerService 中,它建立了从 content URI 到被授权访问这个 URI 的 component 之间的映射。但是它也需要借助 PackageManagerService 的机制来辅助实现。

2.2.1 framework 提供的接口

Android framework 中提供了一些接口用来对外来的访问(包括自己)进行权限检查 。 这些接口 主要通过 ContextWrapper 提供,具体实现在 ContextImpl 中 。如果 package 接受到外来访问者的操作请求,那么可以调用这些接口进行权限检查。一般情况下可以把这些接口的检查接口分为两种,一种是返回错误,另一种是抛出异常。


n permission 和 uid 检查 API

下面这一组接口主要用来检查某个调用(或者是其它 package 或者是自己)是否拥有访问某个 permission 的权限。参数中 pid 和 uid 可以指定,如果没有指定,那么 framework 会通过 Binder 来获取调用者的 uid 和 pid 信息,加以填充。返回值为 PackageManager.PERMISSION_GRANTED 或者 PackageManager.PERMISSION_DENIED 。 public int checkPermission(String permission, int pid, int uid) // 检查某个 uid 和 pid 是否有 permission 权限 public int checkCallingPermission(String permission) // 检查调用者是否有 permission 权限,如果调用者是自己那么返回 PackageManager.PERMISSION_DENIED public int checkCallingOrSelfPermission(String permission) // 检查自己或者其它调用者是否有 permission 权限 下面这一组和上面类似,如果遇到检查不通过时,会抛出异常,打印消息 。 public void enforcePermission(String permission, int pid, int uid, String message) public void enforceCallingPermission(String permission, String message) public void enforceCallingOrSelfPermission(String permission, String message) n per-URI 检查 API

为某个 package 添加访问 content Uri 的读或者写权限。 public void grantUriPermission(String toPackage, Uri uri, int modeFlags) public void revokeUriPermission(Uri uri, int modeFlags) 检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,返回值表示是否被 granted 。 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) public int checkCallingUriPermission(Uri uri, int modeFlags) public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) public int checkUriPermission(Uri uri, String readPermission,String writePermission, int pid, int uid, int modeFlags) 检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,如果失败则抛出异常,打印消息 。 public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) public void enforceUriPermission(Uri uri, String readPermission, String writePermission,int pid, int uid, int modeFlags, String message)

2.2.2 实现分析 中提供的 API ,其实都是由 ActivityManagerService 中的如下几个接口进行的封装。 public int checkPermission(String permission, int pid, int uid) throws RemoteException; // 主要用于一般的 permission 检查 public int checkUriPermission(Uri uri, int pid, int uid, int mode) throws RemoteException; // 主要用于 Content Uri 的 permission 检查 n checkPermission 的实现分析 1. 如果传入的 permission 名称为 null ,那么返回 PackageManager.PERMISSION_DENIED  2. 判断调用者 uid 是否符合要求  ) 如果 uid 为 ,说明是 root 权限的进程,对权限不作控制。 ) 如果 uid 为 system server 进程的 uid ,说明是 system server ,对权限不作控制。 ) 如果是 ActivityManager 进程本身,对权限不作控制。 )如果调用者 uid 与参数传入的 req uid 不一致,那么返回 PackageManager.PERMISSION_DENIED  3. 如果通过 的检查后,再 调用 PackageManagerService.checkUidPermission ,判断 这个 uid 是否拥有相应的权限,分析如下  ) 首先它通过调用 getUserIdLP ,去 PackageManagerService.Setting.mUserIds 数组中,根据 uid 查找 uid (也就是 package )的权限列表。一旦找到,就表示有相应的权限。 ) 如果没有找到,那么再去 PackageManagerService.mSystemPermissions 中找。这些信息是启动时,从 /system/etc/permissions/platform.xml 中读取的。这里记录了一些系统级的应用的 uid 对应的 permission  )返回结果  n 同样 checkUriPermission 的实现 主要在 ActivityManagerService 中,分析如下: 1. 如果 uid 为 ,说明是 root 用户,那么不控制权限。 2. 否则,在 ActivityManagerService 维护的 mGrantedUriPermissions 这个表中查找这个 uid 是否含有这个权限,如果有再检查其请求的是读还是写权限。

3 Android 签名机制


包扫描阶段需要进行完整性和证书的验证。普通 package 的签名和证书是必须要先经过验证的。具体做法是对 manifest 下面的几个文件进行完整性检查。完整性检查包括这个 jar 包中的所有文件。如果是系统 package 的话,只需要使用 AndroidMenifest.xml 这个文件去提取签名和验证信息就可以了。

在权限创建阶段。如果该 package 来自 system img (系统 app ),那么 trust it ,而且使用新的签名信息去替换就的信息。前提是如果这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。有些时候系卸载一个 app ,但是不删除数据,那么其 PackageSettings 信息会保留,其中会保存签名信息。这样再安装是就会出现不一致。

3.1 Android Package 签名原理

android 中系统和 app 都是需要签名的。可以自己通过 development/tools/make_key 来生成公钥和私钥。

android 源代码中提供了工具 ./out/host/linux-x86/framework/signapk.jar 来进行手动签名。签名的主要作用在于限制对于程序的修改仅限于同一来源。系统中主要有两个地方会检查。如果是程序升级的安装,则要检查新旧程序的签名证书是否一致,如果不一致则会安装失败;对于申请权限的 protectedlevel 为 signature 或者 signatureorsystem 的,会检查权限申请者和权限声明者的证书是否是一致的。签名相关文件可以从 apk 包中的 META-INF 目录下找到。

signapk.jar 的源代码在 build/tools/signapk ,签名主要有以下几步:

l 将除去 CERT.RSA , CERT.SF , MANIFEST.MF 的所有文件生成 SHA1 签名 首先将除了 CERT.RSA , CERT.SF , MANIFEST.MF 之外的所有非目录文件分别用 SHA-1 计算摘要信息,然后使用 base64 进行编码,存入 MANIFEST.MF 中。 如果 MANIFEST.MF 不存在,则需要创建。存放格式是 entry name 以及对应的摘要 l 根据 之前计算的 SHA1 摘要信息,以及 私钥生成 一系列的 signature 并写入 CERT.SF 对 整个 MANIFEST.MF 进行 SHA1 计算,并将摘要信息存入 CERT.SF 中 。然后对之前计算的所有摘要信息使用 SHA1 再次计算数字签名,并写入 CERT.SF 中。 l 把公钥和签名信息写入 CERT.RST 把之前整个的签名输出文件 使用私有密钥计算签名。同时将签名结果,以及之前声称的公钥信息写入 CERT.RSA 中保存。

3.2 Package 的签名验证

安装时对一个 package 的签名验证的主要逻辑在 文件的 verifyCertificate 函数中实现。 其主要的思路是通过提取 cert.rsa 中的证书和签名信息,获取签名算法等信息,然后按照之前对 apk 签名的方法进行计算,比较得到的签名和摘要信息与 apk 中保存的匹配。

第一步。提取证书信息,并对 cert.sf 进行完整性验证。 1. 先找到是否有 DSA 和 RSA 文件 ,如果找到则对其进行 decode ,然后读取其中的所有的证书列表(这些证书会被保存在 Package 信息中,供后续使用)。 2. 读取这个文件中的签名数据信息块列表,只取第一个签名数据块。读取其中的发布者和证书序列号。 3. 根据证书序列号,去匹配之前得到的所有证书,找到与之匹配的证书。 4. 从之前得到的签名数据块中读取签名算法和编码方式等信息 5. 读取 cert.sf 文件,并计算整个的签名,与数据块中的签名(编码格式的)进行比较,如果相同则完整性校验成功。 第二步。使用 cert.sf 中的摘要信息,验证 MANIFEST.MF 的完整性。 在 cert.sf 中提取 SHA1-Digest-Manifest 或者 SHA1-Digest 开头的签名 数据块 ( -Digest-Manifest 这个是整个 MANIFEST.MF 的摘要 信息,其它的是 jar 包中其它文件的摘要信息 ), 并逐个对这些数据块 进行验证。验证的方法是,现将 cert.sf 看做是很多的 entries ,每个 entries 包含了一些基本信息,如这个 entry 中使用的摘要算法( SHA1 等),对 jar 包中的哪个文件计算了摘要,摘要结果是什么。 处理时先找到每个摘要数据开中的文件信息,然后从 jar 包中读取,然后使用 -Digest 之前的摘要算法进行计算,如果计算结果与摘要数据块中保存的信息的相匹配,那么就完成验证。 作者:GangWang 出处: 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。




  传统SSL VPN通过端口代理的方法实现,代理服务器根据应用协议的类型(如http,telnet等)做相应的端口代理,客户端与代理服务器之间建立SSL安全连接,客户端与应用服务器之间的所有数据传输通过代理服务器转发。这种实现方式烦琐,应用范围也比较窄:仅适用于用TCP固定端口进行通信的应用系统,且对每个需要代理的端口进行单独配置;对于每个需要用到动态端口的协议都必须重新开发,且在代理中解析应用协议才能实现代理,如FTP协议;不能对TCP以外的其它网络通信协议进行代理;代理服务器前端的防火墙也要根据代理端口的配置变化进行相应调整。






















  解压及安装 # tar xvf lzo-2.04.tar.gz # cd lzo-2.04 # ./configure –prefix=/usr/local/lzo # make && make install # cd ../ # tar xvf openvpn-2.1.4.tar.gz # cd openvpn-2.1.4 # ./configure –prefix=/usr/local/openvpn –with-lzo-headers=/usr/local/lzo/include/ –with-lzo-lib=/usr/local/lzo/lib/ –with-ssl-headers=/usr/include/openssl/ –with-ssl-lib=/usr/lib # make && make install # cd ../



  [root@test1 ~]# mkdir /etc/openvpn [root@test1 ~]# cp /root/openvpn-2.1.4/easy-rsa/ /etc/openvpn/ -r [root@test1 ~]# cd /etc/openvpn/easy-rsa/2.0/ [root@test1 2.0]# cat vars |grep -v \’^$\’|grep -v \’^#\’ export EASY_RSA=\”`pwd`\” RSA在当前目录 export OPENSSL=\”openssl\” export PKCS11TOOL=\”pkcs11-tool\” export GREP=\”grep\” export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA` 这个是一个很特别的参数没有“”, openssl的配置文件 export KEY_DIR=\”$EASY_RSA/keys\” 密钥的位置 echo NOTE: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR export PKCS11_MODULE_PATH=\”dummy\” export PKCS11_PIN=\”dummy\” export KEY_SIZE=1024 密钥的大小长度 export CA_EXPIRE=3650 CA证书的过期时间天数 export KEY_EXPIRE=3650 密钥的时间过期天数 export KEY_COUNTRY=\”CN\” 国家 export KEY_PROVINCE=\”HB\” 省份 export KEY_CITY=\”WH\” 城市 export KEY_ORG=\”\” 组织 export KEY_EMAIL=\”root@localhost\” 邮箱

  2.2下面这个命令在第一次安装时可以运行,以后在添加客户端时千万别运行,这个命令 会清除所有已经生成的证书密钥。
# ./clean-all


  [root@test1 2.0]# ./build-ca Generating a 1024 bit RSA private key ..++++++ ……………………………………………..++++++ writing new private key to \’ca.key\’ —– You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \’.\’, the field will be left blank. —– Country Name (2 letter code) [CN]:CN State or Province Name (full name) [HB]:HUB Locality Name (eg, city) [WH]:WH Organization Name (eg, company) []: Organizational Unit Name (eg, section) []: Common Name (eg, your name or your server\’s hostname) [ CA]:test Name []: Email Address [root@localhost]:

  2.4生成服务器端密钥key, 后面这个server-name就是服务器名,可以自定义

  [root@test1 2.0]# ./build-key-server test Generating a 1024 bit RSA private key …………………………………++++++ ……………………..++++++ writing new private key to \’test.key\’ —– You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \’.\’, the field will be left blank. —– Country Name (2 letter code) [CN]:CN State or Province Name (full name) [HB]:HB Locality Name (eg, city) [WH]:WH Organization Name (eg, company) []: Organizational Unit Name (eg, section) []: Common Name (eg, your name or your server\’s hostname) [test]: Name []: Email Address [root@localhost]: Please enter the following \’extra\’ attributes to be sent with your certificate request A challenge password []:123456 An optional company name []: Using configuration from /etc/openvpn/easy-rsa/f Check that the request matches the signature Signature ok The Subject\’s Distinguished Name is as follows countryName :PRINTABLE:\’CN\’ stateOrProvinceName :PRINTABLE:\’HB\’ localityName :PRINTABLE:\’WH\’ organizationName :PRINTABLE:\’\’ commonName :PRINTABLE:\’test\’ emailAddress :IA5STRING:\’root@localhost\’ Certificate is to be certified until Oct 6 03:30:01 2023 GMT (3650 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated

  2.5生成客户端 key

  注意在进入 Common Name (eg, your name or your server\’s hostname) []: 的输入时, 每个证书输入的名字必须不同.

  [root@test1 2.0]# ./build-key test Generating a 1024 bit RSA private key …………………………………………………………………………………………………………….++++++ ………………….++++++ writing new private key to \’test.key\’ —– You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter \’.\’, the field will be left blank. —– Country Name (2 letter code) [CN]: State or Province Name (full name) [HB]: Locality Name (eg, city) [WH]: Organization Name (eg, company) []: Organizational Unit Name (eg, section) []: Common Name (eg, your name or your server\’s hostname) [test]: 每个client的hostname都不能一样 Name []: Email Address [root@localhost]: Please enter the following \’extra\’ attributes to be sent with your certificate request A challenge password []:123456 An optional company name []: Using configuration from /etc/openvpn/easy-rsa/f Check that the request matches the signature Signature ok The Subject\’s Distinguished Name is as follows countryName :PRINTABLE:\’CN\’ stateOrProvinceName :PRINTABLE:\’HB\’ localityName :PRINTABLE:\’WH\’ organizationName :PRINTABLE:\’\’ commonName :PRINTABLE:\’test\’ emailAddress :IA5STRING:\’root@localhost\’ Certificate is to be certified until Oct 6 03:36:48 2023 GMT (3650 days) Sign the certificate? [y/n]:y 1 out of 1 certificate requests certified, commit? [y/n]y Write out database with 1 new entries Data Base Updated

  2.6生成Diffie Hellman参数,加强安全

[root@test1 2.0]# ./build-dh
Generating DH parameters, 1024 bit long safe prime, generator 2
This is going to take a long time


  local #换成服务端的IP

  [root@test1 2.0]# mkdir /etc/openvpn/easy-rsa/2.0/conf [root@test1 2.0]# cp /root/openvpn-2.1.4/sample-config-files/nf /etc/openvpn/easy-rsa/2.0/conf/ [root@test1 2.0]# cat /etc/openvpn/easy-rsa/nf |grep -v \’^$\’|grep -v \’^;\’|grep -v \’^#\’ port 1194 proto udp dev tun ca /etc/openvpn/easy-rsa/2.0/keys/ca.crt cert /etc/openvpn/easy-rsa/2.0/keys/test.crt key /etc/openvpn/easy-rsa/2.0/keys/test.key # This file should be kept secret dh /etc/openvpn/easy-rsa/2.0/keys/dh1024.pem server ifconfig-pool-persist ipp.txt push \”dhcp-option DNS\” push \”dhcp-option DNS\” client-to-client keepalive 10 120 comp-lzo user nobody group nobody persist-key persist-tun status openvpn-status.log log openvpn.log verb 3


[root@test1 2.0]# /usr/local/openvpn/sbin/openvpn  –config  /etc/openvpn/easy-rsa/nf &
[3] 11924


vim /etc/nf

net.ipv4.ip_forward = 1


sysctl –p

iptables -t nat -A POSTROUTING -o eth0 -s -j MASQUERADE

/etc/init.d/iptables save



  4.2将服务器上的/etc/openvpn/easy-rsa/2.0/keys下的文件拷贝到C:Program Files (x86)OpenVPNconfig

  4.3在windows7客户端上将C:Program Files (x86)OpenVPNsample-configclient.ovpn拷贝到C:Program Files (x86)OpenVPNconfig下

  4.4编辑client.ovpn 修改如下

  remote 服务器IP 1194

  4.5 运行client.ovpn测试是否可以ping通10.8.0.1,,也就是服务器的tun接口IP。。



  TLS_ERROR: BIO read tls_read_plaintext error: error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate returned

  TLS Error: TLS object -> incoming plaintext read error

  TLS Error: TLS handshake failed






Sun Mar 18 20:25:54 2012 [jesse] Peer Connection Initiated with
Sun Mar 18 20:25:55 2012 SENT CONTROL [jesse]: \’PUSH_REQUEST\’ (status=1)
Sun Mar 18 20:25:55 2012 PUSH: Received control message: \’PUSH_REPLY,route,route,topology net30,ping 10,ping-restart 120,ifconfig\’
Sun Mar 18 20:25:55 2012 Options error: Unrecognized option or missing parameter(s) in [PUSH-OPTIONS]:3: topology (2.0.9)
Sun Mar 18 20:25:55 2012 OPTIONS IMPORT: timers and/or timeouts modified
Sun Mar 18 20:25:55 2012 OPTIONS IMPORT: –ifconfig/up options modified
Sun Mar 18 20:25:55 2012 OPTIONS IMPORT: route options modified
Sun Mar 18 20:25:55 2012 TAP-WIN32 device [本地连接 3] opened: .Global{2893A584-9C99-43FE-B17C-E1F7160BE530}.tap


  There are no TAP-Win32 adapters on this system. You should be able to create a TAP-Win32 adapter by going to Start -> All Programs -> OpenVPN -> Add a new TAP-Win32 virtual ethernet adapter.

  5.3 All TAP-Win32 adapters on this system are currently in use





  1.你得到如下错误信息: TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity). 这个错误指出客户端不能跟服务器建立网络链接.


b.如果你的OpenVPN服务器是单网卡,并处在受保护的局域网中,请确认你你的网关防火墙使用了正确的端口转发规则。比如:你的OpenVPN机器的地址是192.168.4.4,但处在防火墙保护下,时刻监听着UDP协议1194的连接请求,那么负责维护192.168.4.x子网的网关就会有一个端口转发策略,即所有访问UDP协议1194端口的请求都被转发到192.168.4.4 。

  2.你得到如下错误信息: Initialization Sequence Completed with errors – 这个错误可能发生在windows下(a)你没有启用DHCP客户端服务(b)你的XP SP2使用了某个第三方的个人防火墙。

  解决办法: 启动DHCP客户端服务或者你确认你的XP SP2正确使用了个人防火墙.

  3.你虽然获得了Initialization Sequence Completed 的信息,但ping测试还是失败了,那就通常是在服务器或者客户端的防火墙阻止过滤了在TUN/TAP设备结构上的网络流量。

  解决办法: 关闭客户端的防火墙,如果防火墙过滤了TUN/TAP设备端口的流量。比如在Windows XP SP2系统,你可以到Windows 安全中心 -> Windows 防火墙 -> 高级 然后不要选择TAP-Win32 adapter设备 (即禁止TUN/TAP设备使用防火墙过滤 ,实质上就是告诉防火墙不要阻止VPN认证信息)。 同样在服务器端也要确认TUN/TAP设备不实用防火墙过滤 (也就是说在TUN/TAP接口上选择过滤是有一定的安全保障的. 具体请看下面一节的访问策略).


TLS: Initial packet from x.x.x.x:x, sid=xxxxxxxx xxxxxxxx


  解决办法: 你只拥有单向连接从客户端到服务器,从服务器到客户端的连接被防火墙挡住, 通常在客户端这边,防火墙(a)可能是个运行在客户端的个人防火墙软件(b)或者服务客户端的NAT路由 网关被设置为从服务器端访问客户端的UDP协议包被阻挡返回。


云计算技术背后的那些天才程序员:KVM之父Avi Kivity

摘要:KVM以其精简的架构,清晰的定位获得Linux社区多数开发人员的支持并快速被合并入主干,从而成为x86全虚拟化技术的一颗新星,其提出者Avi Kivity对于计算机体系架构和Linux内核有深入理解,并且精通汇编和C。

提到以色列,自然会使人联想到犹太人和犹太教及其在二十世纪前半叶所遭受的种种苦难,作为这个世界上最古老的民族之一,除了其沉重的历史之外,更使人惊叹的是,这个面积仅有2.2万平方公里的弹丸之地,在短短几十年中转变成为了高科技研发和创新的“全球性大国”。或许,时刻处于强敌环伺的危险境地能够激发人类最原始的创造力,使得以色列在诸多领域出类拔萃,尤其在信息科技领域尤为突出,世界上最大的科技公司不约而同都选择在以色列设立研发中心,而且成果卓著,例如,Intel x86家族中比较重要的两代CPU,Sandy Bridge和Ivy Bridge都是由Intel以色列的研发中心研发的,蓝色巨人IBM也早在上世纪七十年代就在以色列设立了庞大的研发中心,且成果斐然。


但是,值得称道的是科技巨头在以色列的成功并不能掩盖个体的创新力量所散发出的光芒,尤其在云计算领域,当崇尚人格自由,同时又具有坚强性格这两种特质结合在一起所爆发出的创新的力量,使得草根创业公司和个体也敢于在前沿领域与科技巨头一争长短,其中的代表性人物就是Avi Kivity和他所在的以色列初创公司Qumranet,Avi Kivity提出并推动KVM(基于Linux内核的全虚拟化方案),以其精简的架构,清晰的定位很快获得Linux社区多数开发人员的支持得以快速被合并进入主干,从而成为x86全虚拟化技术的一颗新星。




要知道虚拟化技术并不新,早在1967年,第一代的硬件虚拟化技术就由IBM提出,蓝色巨人在他们的大型机中实现了以CP/CMS为代表的虚拟化技术。但直到2006年,Intel和AMD才分别在各自的处理器中加入了“有限”的硬件虚拟化特性,实际上是给CPU打了一个补丁:),分别称为,Intel VT-x和AMD-V。与大型机所采用的专为虚拟化设计的处理器不同,从PC机起家的以Intel为代表的x86家族的处理器生来就并不是为虚拟化设计的。要在x86家族处理器上完全向后兼容的同时加入硬件虚拟化特性,无疑成为一个挑战,硬件层面实现较为困难,导致软件层面的实现复杂度也随之水涨船高。据估算,Linux内核中,虚拟化部分代码,x86架构部分的代码行是IA64架构的1倍,S390架构的7倍,PPC架构的8倍。即使和ARM架构相比,x86架构的复杂度依然是其4倍,其复杂度之高从中可见一斑。


Talk is cheap, show me the code
硬件层面较为复杂的局面意味着在系统层面需要一个极为精简的设计从而确保x86的硬件虚拟化特性能够在Linux内核层面有较好的可维护性,良好的性能,同时保持一定程度的可扩展性以便为未来新的特性(补丁)的加入留有余地,另外,在那个时候,Xen和Vmware依靠自己独有的技术分别在不同领域如日中天,尤其值得一提的是,Xen在开源领域几乎成为了虚拟化的事实标准,而且开源领域一向奉行Don’t reinvent the wheel这一金科玉律,这一切无疑都为提出新的虚拟化实现增加了巨大的不确定性,但是当时Linux内核中还没有任何虚拟化实现,这一现状又使得提出新的设计成为可能,唯一的问题就是谁能够在足够短的时间内另辟蹊径提出新的方案并且获得多数人的支持,这一点难度之大,恐怕只有深入了解开源社区的人才会感受到,要知道Linux社区恐怕是这个星球上最为“独裁”的组织,没有之一,而且语言“暴力”与人身“攻击”司空见惯(Alan Cox就是无法容忍这一点才选择退出Linux社区),要想在其中被认可并取得一席之地,除了超强的技术实力之外,更要有谦逊的品格,非凡的耐心和绝佳的沟通技巧。

Do one thing and do it well
Avi Kivity提出的方案非常清晰而且巧妙,充分遵循Linux源自Unix的设计思想,把方案的关注点聚焦于Linux内核部分,User space部分交给了稳定可靠的Qemu,采用Qemu作为其用户空间的入口,而且KVM仅仅实现HVM功能。遵循惯例,Linux内核中的KVM模块通过设备文件暴露系统呼叫给User space的Qemu。这一切现在看起来似乎平淡无奇,但是,Avi Kivity方案的背后是要在极短时间内通过代码实现来证实并获得社区认可这一几乎不可能实现的任务,这有赖于Avi Kivity对于计算机体系架构的深入理解,不单单是x86架构的Intel VT-x和AMD-V,还有后续陆续支持的其它架构,并且深入了解Linux内核,精通汇编和C。
以Avi Kivity为主的工程师仅仅花了不到一年时间就让Linux社区接受KVM的设计方案并且通过了代码Review,最终于2006年10月合并进入2.6.20主干,时至今日,人们依然对于KVM合并进入Linux主干的速度之快感到不可思议,从中也产生了一些不同的声音,但是多数人还是倾向于最简单的一种答案,那就是KVM良好的设计和实现是其最终被社区快速接受的主要原因。几年以后,Avi Kivity接受采访,最经常被问到的仍然是这个问题,他的回答也总是轻描淡写,说只不过提前花了半年时间在Linux社区学习如何更好的与社区开发人员协作,但是我们知道,这背后实际是其投入的大量心血和超强实力。
KVM的出现震撼了整个了Linux社区和业界,人们惊异于其出众的性能,更加折服于其精简的设计和良好实现(在SPEC virt2013 Benchmark中基于同样的处理器、内存和存储,KVM单在VM Density指标上以37%的优势超越对手),以至于,在极短的时间内,这个世界上最重要的科技公司纷纷宣称他们将把KVM作为他们x86虚拟化的解决方案,要知道,这极为不同寻常,在这之前以蓝色巨人IBM为代表的科技巨头已经在Xen上投入了大量的财力物力和人力,如果没有决定性的因素,科技巨头的Strategic dicision是不可能轻易改变的,时至今日,IBM在准备推出Power KVM,打算把KVM移植到Power Linux上。就连以Google Glass、无人驾驶汽车和Project Loon著称的跟现代科技格格不入的前沿科技巨头Google也在其Google Compute Engine中采用KVM技术作为x86的虚拟化方案来和Amazon对抗。这一切只能说明KVM凭借其优异的性能和精简架构当仁不让成为了一项无法忽视的虚拟化技术屹立于云计算之巅。


在KVM合并进入Linux主干之后不久Avi Kivity很快成为内核KVM子模块的Maintainer,2012年11月21日一封标题为“KVM: Retire as maintainer”的简短邮件出现在Linux社区邮件列表中,在以KVM模块Maintainer身份贡献Linux社区6年零6个月之后,Avi Kivity选择开启了新的征程,发起并担任云计算创业公司Cloudius的CTO,这是一个不同寻常的选择,要知道在这个世界上,在虚拟化和云计算领域恐怕没有多少人能够比Avi Kivity更有发言权,这一次,Avi Kivity敏锐的意识到除了x86家族的处理器架构之外,已经成为云计算基石的Linux同样天生不是为云计算设计的,因此具有极大的性能提升空间,大胆提出并设计了专为云计算设计的操作系统OSv,当然,其依然是建立在Linux基础上,但是其无需Context switch,真正的Zero Copy以及Lock Free等特性使得其Less One second boot相对于众多宣称秒级响应的厂商来说更加具有说服力。尤其重要的是,KVM之父通过OSv项目表达了对以Linux CGroup技术为核心的Containner技术的看法。
Avi Kivity是如此低调,以至于你很难在网上找到太多的关于他的信息,只有寥寥几篇报道和访谈,但是KVM在整个云计算领域中又是如此的炙手可热,虽然今年已经是KVM进入内核的第八个年头,但是KVM依然活跃,我们依然难以估量其在云计算领域的真正价值,而这一切都源自于这个地球上最危险的那个角落中的那名天才程序员Avi Kivity。
注:仅以此文表达对Avi Kivity、Yaniv Kamay、Dor Laor、Uri Lublin还有Anthony Liguori还有其他KVM以及Qemu开发人员的敬意。(责编:周小璐)

原文链接:不朽传奇-云计算技术背后的那些天才程序员:KVM之父Avi Kivity