不停地疯

Work as a hacker, hack as a artist.

新年新气象

| Comments

今天是中国农历新年的大年初一,在此,简单总结一下去年的收获与成就。

去年,最大的收获应该要说是我的宝贝儿子出生了。小家伙为了赶在2013年之前出生,竟然提前一个月来到人世。这让作父母的我们真是又兴奋又小心,对他的照顾也更加细致体贴。如今,小宝宝已经出生将近一个半月,这期间多少个不眠夜,多少次喂奶换尿布,让我对做父母的责任也有了较深刻的体会,真心不容易。看到小孩一天天长大,体重也增加了不少,由最初的五斤张到现在的将近九斤,也算是给我们作父母的最大安慰吧。

去年是本人以驱动工程师的身份工作在工作岗位上的第一年,这一年中有坎坷,有迷茫,也有收获。但是最大的收获可能是我对于工作,人际等等的重新思考和审视吧。曾经的各种自以为是在经历了去年的一些事情之后,都有了新的认识。工作期间也结识了了一些新朋友,都是些很热心的家伙们,大家有共同的话题,希望以后都能长久保持联系。

另外,在去年中因为工作而结识了优秀软件tmux,自学了lisp语言,算是比较过得去的主要收获。哦,还有一件重要的收获,那就是我终于摆脱了三年的契约,扔掉了手中的烂黑莓,重新换上了iPhone:)。同时还收获到了HHKB。

说到成就,去年一年中似乎没有达成什么耀目的成就。总体来讲,对Linux内核驱动开发算是入了个门;看完十本书(十本书说实在算不上什么成就,人家一个月读的都比我这多,但是聊胜于无吧);工作中搞定了一键搭建高通编译环境的软件;提了三篇专利;搭建了这个博客。然后,好像没有了……

在新的一年里,我希望宝宝可以健健康康,我可以多看几本书,系统学习及掌握Omnifocus,继续学习驱动开发技术,学习Lua和Ruby,多参加几个开源项目。要求不多,一步步来。

入手HHKB Pro2

| Comments

入手HHKB的想法在心中已经埋藏了快三年了,但一直没有付诸行动。原因主要是价钱有点太那啥了,一个键盘要卖1700,对任何一个苦穷程序员来说用这么个价钱买个键盘都是过于昂贵的。所幸去年年底之前在公司完成了3份专利,总共能拿到6k的奖金(申请前期只能拿到一半,也就是只能拿到3k),于是心中的小恶魔又开始骚动了。入手的奖金中有1k已经花去用来购买kindle paperwhite,还剩下不到2k的奖金正好可以购买一个HHKB(老婆旁白:最烦买东西前找个理由的人了):p

既然决定入手HHKB,于是到网上查了下购买渠道。由于HHKB并未正式引入国内,所以只能通过淘宝或者海外代购来购买了。淘宝上找到的HHKB卖家对HHKB Pro2的要价普遍为16xx,1700不到的样子;而在日本Amazon网站上,HHKB Pro2英文键盘的报价只有17,834日元,合计约人民币1,222,便宜了500多。即便加上300多的代购费用,也要比国内淘宝购买便宜200多人刀。所以,我最后选择通过萌购 的代购服务直接从日本亚马逊购买HHKB。由于萌购的购买汇率比银行牌价稍微高一点,实体价格换为人民币为1323元,加上所有代购手续费用和邮费155元,总共为1478元。下单后一般会在两周内收到,实际上,从我下单到收到货只经历了大概一周多一点的时间,总体来说还是蛮便捷的。

收到的键盘包装如下,相当朴素简陋,从外观上看根本看不出这货能卖到1k+。打开包装,里面更是简陋的用一只塑料袋包装键盘,外加两张纸做简要说明就什么都没有了。当然,入HHKB的肯定不单单是看上他的外表,更重要的是看上它轻巧的键盘手感以及键位设置。 ./images/blog/hhkb_case.jpg

由于HHKB支持在多个平台供不同习惯的使用者使用,它提供了6个DIP开关用来配置键盘的功能。

SW1SW2設定モード
OFFOFFHHKモード(PC)
ONOFFLite拡張モード(PC)
OFFONMacintoshモード
ONON設定禁止
DIP SW機能
状態キー機能
3OFFDeleteDelete
ONDeleteBS(BackSpace)
4OFF左◇左◇
ON左◇Fn
5OFFAltAlt
ONAlt
Alt
6OFFWake Up 無効
ONWake Up 有効

我平时使用iMac,所以我HHKB的配置是:011100。也就是Mac键盘模式,Delete键为退格键,同时左Cmd键设置为Fn键。之所以将左Cmd键设置为Fn键,是因为Pro2的英文键盘配置上只有一个Fn键,而且在键盘的最右边,即便使用右手掌去按也不是很方便,于是退其次将左右上常用的Cmd按键配置为了Fn键。说到这点,我有点后悔入手英文键盘了,因为日文键盘上有左右两个Fn键,而且有指针键,而且还有三个日文输入法中常用的转换键。这么配置下来,由于失去了之前常用的左手Cmd按键,某些操作必须配合右手来使用,有点小小的不适应,目前还在进一步熟悉中。当然,HHKB在Emacs下使用是非常清脆干爽舒适的。 关于HHKB按键布置以及DIP配置可以参见:HHKB按键布置HHKB DIP配置

最后,来张我的HHKB和iMac的合影,看起来还是蛮搭的。

./images/blog/hhkb_imac.jpg

当爸爸了

| Comments

不经意间,9个月过去了,宝宝也赶在13年前夕,提前预产期一个月降临人世。由于早产,被医院隔离监护了2天,第3天才将小孩从保暖房里接出来。第一次看到自己小孩娇小的模样,真是又兴奋,又担心。开始当爸爸后,各种工作也就接踵而至。首先是喂奶,由于是初期,母乳还没有多少,只能泡奶粉。但是我这个毛手爸爸除了能喂饱自己以外,小孩奶粉还真不会泡。看了奶粉说明,试了水温,冲了奶粉,结果小孩还是不爱吃。后来我才知道,那是因为之前调的奶粉温度太低了。好在到了第2天,母乳已经很多了,可以直接母乳喂养,才算将我从冲泡奶粉的差事中解放出来。除了喂奶,换尿布也是重头戏。小孩2小时吃一次奶换一次尿布,初为父母的我们,第一天为此真是忙得手忙脚乱。还好有“尿不湿”的存在,否则光是换洗尿布,就够我受了。

现在,从小孩出生,已经过去了半个月,小孩也从之前的5斤长到如今的7斤。看着他一天天长大,心中充满了欣慰。真心希望他能越来越好,健健康康。

加油,小子!

纪念冯华君

| Comments

这几天听到的最令人震惊也是最让人遗憾的消息就是,年轻的31岁程序开发者冯华君去世了。当我刚在twitter上看到这个消息的时候,我都不相信自己的眼睛。我与冯华君并未谋过面,只在几年前和他在MSN上简单聊过几句。那时他正在开发Mac上的FIT输入法,而我联系他是跟他反馈几个关于输入法的改进建议。从那次简单的交流中,我所认识到的冯华君是一位有想法,有理想抱负的年轻人。当他得知我也是一名程序员时,也曾鼓励我去为FIT做贡献。可惜,我的懒惰和拖延症使得这一切成为遗憾,这是唯一一次可以和华君一起工作的机会。此后我们在网上接触的机会少了,再后来出现了twitter和微博,我关注了他。但在微博上我们基本没有怎么互动过,因为那时的我变得更加懒惰,只是彼此价值观接近,我时常转发一些他的帖子。然而现如今,斯人已然离去。

今天,在为冯华君惋惜的同时,我也感到无比地懊恼和惭愧。只愿这位年轻人在世界地另一个地方能过得好吧。

嵌入式系统内核驱动模块化转换的简单框架

| Comments

如前篇文章所述,使用模块化方式开发内核驱动可以有效减少编译时间,从而提高开发效率。除此之外,内核模块使用 insmod 载入内核时可以像使用应用程序一样像内核模块中传入特定参数,参数完全由开发者定义。像中断号,GPIO管脚,总线号,设备地址,log等级等等,都可以通过内核参数进行传入。这意味着可以在只编译一次内核驱动模块的情况下,通过传入不同的参数就可以修改驱动程序的属性,大大提高灵活性,对于Debug更加方便。

由于在嵌入式系统中,注册设备驱动时也要相应将设备注册到系统中,而设备注册逻辑一般都存放在如 Board_xxxx.c 这类板级驱动文件中。这种安排方式在模块化驱动中显得不是很方便,因为载入模块的系统中需要先注册过设备,这也意味着需要先将相应设备信息添加入板级配置文件后才能使用模块驱动。为此,我想实现一个简单的包装框架,实现以下两个目的:

  1. 修改尽可能少的代码进行驱动模块化
  2. 模块化的驱动可以方便的整合到原系统中,无需做多余的改动

按照这个想法,我使用ft5x0x的tp驱动完成了驱动模块化转换的简单框架。其中包含两个部分,分别如下:

模块包装文件
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>

#include <linux/fs.h>
#include <linux/mm.h>

#include "ft5x06_ts.h"

extern int init_wrapper(void);
extern void exit_wrapper(void);

#define module_PRINT_ERR     (1U << 0)
#define module_PRINT_WARNING (1U << 1)
#define module_PRINT_INFO    (1U << 2)
#define module_PRINT_DEBUG   (1U << 3)

#ifndef DEFAULT_DEV_NAME
#define DEFAULT_DEV_NAME "ft5x0x_ts"
#endif
#ifndef DEFAULT_DEV_ADAP
#define DEFAULT_DEV_ADAP 255
#endif
#ifndef DEFAULT_DEV_ADDR
#define DEFAULT_DEV_ADDR 0x38
#endif

#define pr_module(debug_level_mask, args...)                    \
    do {                                                        \
        if (debug_mask & module_PRINT_##debug_level_mask) {     \
            printk(KERN_##debug_level_mask "[module_driver] "args);    \
        }                                                       \
    } while (0)

static int debug_mask = module_PRINT_ERR | \
    module_PRINT_INFO  | \
    module_PRINT_WARNING  | module_PRINT_DEBUG ;
module_param_named(debug_mask, debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
static u8 local_device_adap = DEFAULT_DEV_ADAP;
module_param_named(adap, local_device_adap, byte, S_IRUGO | S_IWUSR | S_IWGRP);
MODULE_PARM_DESC(adap, "Set the i2c adapter of device.");
static u8 local_device_addr = DEFAULT_DEV_ADDR;
module_param_named(addr, local_device_addr, byte, S_IRUGO | S_IWUSR | S_IWGRP);
MODULE_PARM_DESC(addr, "Set the address of device.");

static struct i2c_client *this_client = NULL;
static struct MODULE_DRIVER_INFO {
    struct i2c_board_info *this_device_info;
    void(*prepare_func)(void);
} module_driver_info = {
    .this_device_info = &ft5x0x_device_info,
    .prepare_func = ft5x06_touchpad_setup
};

static int __init module_driver_init(void)
{
    /* int rc; */
    struct i2c_adapter *i2c_adap;

    pr_module(INFO,"Enter in %s\n", __func__);

    /* Init GPIOs */
    if(module_driver_info.prepare_func)
        (*module_driver_info.prepare_func)();

    /* Add device driver. */
    init_wrapper();
    module_driver_info.this_device_info->addr = local_device_addr;
    /* Add i2c device to platform */
    i2c_adap = i2c_get_adapter(local_device_adap);
    if (NULL == i2c_adap)
    {
        pr_module(ERR, "%s: i2c_get_adapter for %d failed\n", __func__, local_device_adap);
        goto error_adapter;
    }
    this_client = i2c_new_device(i2c_adap, module_driver_info.this_device_info);
    if (NULL == this_client)
    {
        pr_module(ERR, "%s: i2c_new_device for %s failed\n", __func__, module_driver_info.this_device_info->type);
        goto error_device;
    }
    pr_module(INFO, "%s: this_client:%p, addr:%#x\n", __func__, this_client, this_client->addr);
    i2c_put_adapter(i2c_adap);
    return 0;

  error_device:
    i2c_put_adapter(i2c_adap);
  error_adapter:
    exit_wrapper();
    return -1;
}

static void __exit module_driver_exit(void)
{
    pr_module(INFO,"Enter in %s\n", __func__);
    exit_wrapper();
    i2c_unregister_device(this_client);
}

module_init(module_driver_init);
module_exit(module_driver_exit);

MODULE_AUTHOR("zhiqiang.xu<zhiqiang.xu@phicomm.com.cn>");
MODULE_DESCRIPTION("i2c device module driver");
MODULE_LICENSE("GPL v2");
以上为部分内容, 其中需要实现板级设备信息 ft5x0x_device_info 和设备初始化函数 ft5x06_touchpad_setup 。其实也就是将板级文件中的相应信息拷贝过来即可。

原驱动文件的修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#if defined(MODULE)
int init_wrapper(void)
{
    return ft5x0x_ts_init();
}
EXPORT_SYMBOL(init_wrapper);

void exit_wrapper(void)
{
    ft5x0x_ts_exit();
}
EXPORT_SYMBOL(exit_wrapper);
#else
module_init(ft5x0x_ts_init);
module_exit(ft5x0x_ts_exit);

MODULE_AUTHOR("<luowj>");
MODULE_DESCRIPTION("FocalTech ft5x0x TouchScreen driver");
MODULE_LICENSE("GPL");
#endif

由于内核模块中只能存在一对 module_initmodule_exit ,所以在原驱动文件中使用模块宏 MODULE 将这部分排除,同时使用统一的包装函数名称将驱动初始化函数和退出函数包装起来,并导出符号。

最后,参照上篇文章内容编写 Makefile 文件,如下:

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
# Author: zhiqiang.xu
# EMail : xeonxu@gmail.com
# Date  : 2012-12-11
CROSS_ARCH:=ARCH=arm CROSS_COMPILE="$(ARM_EABI_TOOLCHAIN)/arm-eabi-"
KDIR:=$(ANDROID_PRODUCT_OUT)/obj/KERNEL_OBJ/
PWD:=$(shell pwd)

test_driver-objs := module_driver.o ft5x06_ts.o focaltech_ctl.o  ft5x06_ex_fun.o
obj-m:= test_driver.o
.PHONY: modules package clean
all:package
modules:
    @if [ "$(ANDROID_BUILD_TOP)" = "" ]; then echo "You have to run \". build/envsetup.sh\" to init enviroment first. \nAnd then you have to run
\"choosecombo\" to setup the project."&&exit 1; fi
    @if [ ! -d $(KDIR) ]; then echo "Build kernel first."&&cd $(ANDROID_BUILD_TOP)&&make -j4 bootimage&&cd -; fi
    $(MAKE) $(CROSS_ARCH) -C $(KDIR) M=$(PWD) modules

package:modules
    @mkdir -p ./package
    @cp *.bat ./package
    @cp $(obj-m:.o=.ko) ./package
    @tar --transform='s,package,test_driver,' -jcf test_driver.tar.bz2 ./package/

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.order *.symvers package test_driver.tar.bz2
修改后的驱动文件使用make即可直接编译出模块驱动,同时该驱动中也包含了设备注册的相关处理,所以相对来说更加独立。完整的驱动文件如下: test_driver.tar.bz2

在高通平台Android环境下编译内核模块

| Comments

高通Android环境中Linux内核会作为Android的一部分进行编译,直接使用make即可一次性从头编到尾。而有的平台比如Marvell,内核的编译操作相对比较独立,必须使用标准的内核编译命令进行单独编译。一般来说,用高通的这种方式比较傻瓜化,一步到底的感觉;而用Marvell的方式用户干预较多,灵活性也更大。当然这里不是比较他们孰优孰劣,对我来说这两种方式各有千秋。在遇到具体问题时,有时还会觉得独立编译内核的方式比较方便,比如编译内核模块这一点上。

编译内核模块之前必须先编译内核,编译内核之前必须先指定内核配置。在独立编译内核情况下,编译一遍内核后,可以直接使用 make module 来编译内核模块,如果修改了相应模块文件,使用相同的命令也能很快的进行增量编译。而在高通环境下,由于内核的编译过程已经被集成到Android的编译中,所以每次编译内核或者内核模块时,都必须通过Android的编译环境进行启用。虽然Android提供诸如 make bootimage 命令,可以只编译bootimage相关内容,但是Android庞大的编译体系在初始化时也会占用很多的时间。前段时间在调试一个独立的内核模块时就一直被这个问题困扰着,每次修改模块代码后都必须通过 make bootimage 来编译。虽然只有一个文件,但是每次编译都花费至少1min30sec,严重影响了开发进度。为此,自己参考内核模块独立编译的Makefile和Android的环境特点写了一个内核模块编译Makefile。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Author: zhiqiang.xu
# EMail:  xeonxu@gmail.com
CROSS_ARCH:=ARCH=arm CROSS_COMPILE="$(ARM_EABI_TOOLCHAIN)/arm-eabi-"
KDIR:=$(ANDROID_PRODUCT_OUT)/obj/KERNEL_OBJ/
PWD:=$(shell pwd)

obj-m:= my_module.o
.PHONY: modules package clean
all:package
modules:
    @if [ "$(ANDROID_BUILD_TOP)_yes" = "_yes" ]; then echo "You have to run \". build/envsetup.sh\" to init enviroment first. \nAnd then you have to run
\"choosecombo\" to setup the project."&&exit 1; fi
    @if [ ! -d $(KDIR) ]; then echo "Build kernle first."&&cd $(ANDROID_BUILD_TOP)&&make bootimage&&cd -; fi
    $(MAKE) $(CROSS_ARCH) -C $(KDIR) M=$(PWD) modules

package:modules
    @mkdir -p ./package
    @cp $(obj-m:.o=.ko) ./package

clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.order *.symvers package
该Makefile默认会将当前目录下的 my_module.c 文件编译为内核模块。同时,在编译时会强制检查Android的环境是否正确配置,如果没有配置它会进行相应提示后退出编译处理。编译模块时使用的内核配置是编译Android时指定项目所配置的内核配置。如果内核还没有编译,则在编译模块之前会自动编译内核主体。如果一切OK,则每次只会编译修改过的模块文件。编译好后会将模块文件单独拷贝到当前目录下的 package 目录中,方便使用。

使用该编译脚本后,模块的编写调试效率高了不少,至少每次编译模块都可以在5sec内搞定了。加上上机实测调试,也能在30sec内完成。生命很可贵,像我一样当个懒人吧。

获取并操作内核中已注册的I2C设备

| Comments

之前写过一篇关于如何在Linux内核模块中注册操作I2C设备的文章 ,那篇文章最后介绍的方法虽然可行,但是会带来一个问题:如果内核中已经包含有某设备的驱动时,那么在模块中注册该设备的I2C client之前必须先将内核中的驱动进行反注册解挂,然后才能再次注册模块中定义的驱动。这样做带来的问题就是,当你将模块从内核中卸载后,系统将无法再次注册内核中原有的驱动,导致相应设备无法使用。今天补充的方法可以在挂载模块时使用模块内的设备驱动,而在卸载后恢复回系统原来的驱动。

内容相当简单,上次我们已经可以通过内核提供的接口函数,找到相应I2C总线相应地址I2C设备的I2C client结构指针。而拥有该指针后,其实就可以做很多事了。比如调用 i2c_master_send 接口向该client指向的设备发送I2C命令。这样,如果需要扩展内核中原有的驱动程序,比如向procfs或sysfs中添加相应的用户空间接口等。一般可以在 module_init 中注册sysfs入口的操作函数,然后在操作函数中通过操作该client指针而实现一定的功能。这种方法可以沿用系统内核中原有的设备驱动,可以单纯添加一些系统驱动中没有的功能。

除此之外,还有一种替换内核中现有驱动的方法。通过查阅源代码,可以发现内核中还提供一个 device_reprobe(dev) 的API,该函数接受一个device结构体指针,实现重新匹配设备驱动的操作。同时,I2C client结构体中也有相应的device结构体。我们知道Linux内核匹配I2C设备驱动是通过名称来进行匹配的,所以,我们的方法就是用Hack的方式将系统中获取到的I2C Client结构体的名称改为我们需要的名称。一般修改为我们模块中新建的驱动的名称,这样,当调用 device_reprobe 接口后,系统会将原有驱动remove并重新为相应I2C设备适配一个驱动程序。当然,没有出错的话,它会适配到我们修改的名称指向的驱动。如此,我们便可以在内核模块中编写独立的设备驱动程序了。以下是简单示例框架:

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
struct i2c_client *this_client = NULL;

static struct i2c_driver my_driver = {
    .driver = {
        .name = NEW_DRIVER_NAME,
        .owner = THIS_MODULE,
    },
    .probe = my_probe,
    .remove = my_remove,
};

static int __init module_driver_init(void)
{
    struct device *ts_dev = NULL;
    int rc;

    i2c_add_driver(&my_driver);

    ts_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "1-0011");
    if(!ts_dev)
    {
        pr_info("Did not match the device name:1-0011!\n");
        goto device_error_exit;
    }
    this_client  =  container_of(ts_dev, struct i2c_client, dev);

    if(!this_client)
    {
        goto device_error_exit;
    }

    memcpy(this_client->name, NEW_DRIVER_NAME, I2C_NAME_SIZE);
    rc = device_reprobe(ts_dev);
        return 0;

  device_error_exit:
    i2c_del_driver(&my_driver);
    pr_info("ts i2c del driver");
    return -1;
}
只需要实现其中的 my_probe , my_remove 等函数即可实现一个完整的驱动。需要注意的是一定要在调用 device_reprobe 接口之前将相应的设备驱动使用 i2c_add_driver 添加到系统中,否则重新适配中会找不到驱动。移除模块时,用同样的方法将I2C client的名称更改为系统中原有驱动的名称,并重新适配驱动,即可实现卸载模块后系统能够使用原有驱动的功能。示例如下:
1
2
3
4
5
6
7
8
9
10
static void __exit module_driver_exit(void)
{
    int rc;

    memcpy(this_client->name, ORIGIN_DRIVER_NAME, I2C_NAME_SIZE);

    rc = device_reprobe(&this_client->dev);

    i2c_del_driver(&my_driver);
}
最后需要注意,文中方法皆为本人翻查文档自己琢磨搞出来的,所以不排除存在隐患的可能,但在自己测试环境下使用中没有发现任何问题。如果有疑问,也希望各位看官能提出自己的看法。

Raspberry Pi上搭建自动ssh代理

| Comments

这是一篇 功夫网 系列文章,本篇作为该系列文章的第一篇,一直拖了好久才真正开始动笔。这篇文章中,我将要介绍的是如何在Raspberry Pi上搭建基于SSH连接的Socks代理服务器。关于我为什么使用Raspberry Pi,是因为我觉得这玩意小巧,携带方便,而且买了它不用也是闲着。至于为什么搭建Socks服务器,知者自知,不知者我也不想过多解释。总之这是该系列文章第一篇,所以内容上相对来说都是比较简单和基础的,大家往下看就是了。另外需要说明的是,我所使用的Raspberry Pi是B型板,操作系统为Arm版的Slackware系统ArmedSlack。如果使用的是其它发行版,可能需要相应修改以下的命令。

本片文章主要实现三个目的:

  1. 实现ssh免密码自动连接远程服务器,并建立Socks代理
  2. 开机时自动通过ssh与远程服务器建立安全通道
  3. 设置守护进程,使得ssh连接异常断开后可以自动进行重新连接

操作之前,首先使用终端软件通过ssh方式连接到Raspberry Pi上。

ssh免密码自动连接远程服务器

为了ssh可以在无人干预的情况下自动连接远程服务器,普遍的做法是使用公钥认证方式进行连接。同样,我们也使用公钥认证的方法进行自动连接。为了实现公钥认证连接,首先需要生成密钥对。

生成密钥

生成密钥时可以使用以下 ssh-keygen 命令进行。

1
ssh-keygen -t rsa
命令执行中会出现一些提示,大致是一些关于密钥存放路径以及密钥主密码设定之类的。基本上一路回车就可以了,同时注意不要去设置密钥主密码,否则每次访问密钥时都会提示输入,这样就达不到自动认证连接的目的了。由于 ssh-keygen 包含在openssh软件包中,所以如果linux中曾经安装过openssh的话,应该直接就能使用。反之如果提示找不到该命令,可以通过类似下面的命令来安装slackware的n包中的openssh来获取。
1
installpkg /slackware/n/openssh-6.0p1-arm-1.tgz
生成好的密钥对默认会保存在 ~/.ssh/ 目录下,如果生成的时rsa密钥,则密钥文件分别为 id_rsaid_rsa.pub 。其中没有 .pub 扩展名的文件为私钥,另外一个为公钥。现在我们需要将我们刚才生成的公钥文件上传到我们的远程服务器的相应目录中。

上传密钥

上传密钥可以使用通用的scp命令,也可以使用ssh工具中的 ssh-copy-id 命令。 ssh-copy-id 命令比较简单,指定密钥文件和远程机即可,该命令会自动添加公钥内容到远程机的授权文件中。但要注意该命令不会改变远程机相应文件的属性,所以如果是第一次操作的话,建议使用scp命令比较靠谱。下面我们使用scp命令

1
2
3
4
5
scp ~/.ssh/id_rsa.pub user@server:/home/user #user和server需要根据实际内容更改,可能需要输入远程机密码
ssh user@server #连接至远程服务器,可能需要输入远程机密码。user和server需要根据实际内容更改
cd
cat id_rsa.pub >> .ssh/authorized_keys #将刚拷贝过来的公钥文件内容添加到.ssh/authorized_keys文件中
chmod 600 .ssh/authorized_keys #必须确保.ssh/authorized_keys文件的属性为600,及他人不可读写,否则公钥认证将会失败
OK,大功告成。退出远程机,使用 ssh user@server 命令重新连接远程机,此时会提示加密指纹认证的提示,回答 yes 即可。此后再次连接远程机时就会直接登录进入,而不会出现任何提示了。

建立socks服务

ssh软件自带功能可以生成socks代理服务器,并通过ssh连接的远程机进行网络访问。使用相当简单,只需要在执行ssh命令连接远程机时使用 -D 参数指定相应端口即可,如

1
ssh -D 9090 user@server
以上例子将ssh连接生成端口号为9090的socks代理。该代理可以通过Firefox等浏览器直接使用,每次需要使用socks代理时只需执行以上的命令即可。但是现实情况是,我们希望RaspberryPi在每次开机后即可自动运行ssh连接远程机并建立相应的代理端口。

开机时自动建立连接

开机后立即进行ssh连接有很多实现方法,最省事的办法就是修改 rc.local 或者 inittab 文件来实现。方法非常简单,添加相应语句到 rc.local 中即可。以下,我通过新建一个连接脚本,然后在 rc.local 文件中进行调用来实现开机自动连接ssh:

1
2
3
4
mkdir -p ~/bin
echo ssh -D 9090 user@server >> ~/bin/socks_proxy.sh
chmod a+x ~/bin/socks_proxy.sh
echo ~/bin/socks_proxy.sh >> /etc/rc.d/rc.local
如果想做的更正式一点,可以参考 /etc/rc.d/ 目录下的脚本文件,新建一个rc风格的服务脚本文件,然后修改 rc.M 文件,添加相应的服务启动代码即可。不过接下来马上要做防止断线的处理,需要用到其它工具,所以这一步暂时跳过也没关系。

设置守护进程防止断线

目前为止我们设置了ssh的免密码登录以及开机自动登录,要求不高的话也差不多可以凑合使用。但是网络这东西有很多不稳定因素,如果碰到ssh服务器出状况或者线路不稳定时,ssh的连接就可能会被中断,这时必须自己手动再连接一次才能继续使用,这显然不是我们希望的。为了防止这类情况的发生,我们需要借助autossh软件来实现断线自动连接。autossh的功能就像它的名称一样直接简单,它可以监测ssh的连接状态,并在有需要的时候自动重新连接ssh。接下来,我来讲解如何安装配置autossh。

安装autossh

由于slackware官方没有提供autossh的安装包,所以需要自己从源码编译。首先从autossh官网 下载最新的源代码包 autossh-1.4c.tgz ,然后执行以下命令进行编译安装:

1
2
3
4
5
gunzip -c autossh-1.4c.tgz | tar xvf -
cd autossh-1.4c
./configure --prefix=/usr --sysconfdir=/etc
make
sudo make install
整个过程没有什么难度,标准的从源代码安装软件的方法,除了RPi的编译速度有点慢以外。为了方便,我这里也用slackbuild编译了一份适用于RPi使用的二进制软件包: autossh-1.4c-arm.tgz ,只需要执行 sudo installpkg autossh-1.4c-arm-1_SBo.tgz 即可。

初始化脚本

autossh安装好后不需要特别的设置,只需要使用它改写之前的ssh连接脚本即可。修改 ~/bin/socks_proxy.sh 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
#
# autossh:  Connect remote with ssh automatically.
#
# processname: autossh
# pidfile: /var/run/autossh.pid
#                       --Zhiqiang Xu <xeonxu@gmail.com>


export AUTOSSH_PIDFILE="/var/run/autossh.pid"
PIDFILE=$AUTOSSH_PIDFILE
SSH_SERVER=user@server

if [ -e $AUTOSSH_PIDFILE ]; then
        exit 0
elif [ -x /usr/bin/autossh -a -x /usr/bin/ssh ]; then
        echo "Starting autossh..."
        autossh -M 40000 $SSH_SERVER -f -q -C -g -N -D 9090
fi
如果之前还没有修改过 /etc/rc.d/rc.local 文件,记得在该文件最后加上一句 ~/bin/socks_proxy.sh 。如此便可在每次开机后自动使用autossh来进行ssh连接,同时再也不用担心该ssh会发生断线问题了。

守护进程

说实在,通过之前的那些操作基本可以保证我们拥有一个可以自动监护连接,并能稳定提供socks代理的功能了。以下操作可以不必进行,不过如果你纠结于更稳定的保全的连接,可以继续看下去。这一步操作主要是将autossh加入守护进程,从而防止autossh的异常退出(还记得autossh是干什么的来着?如果autossh异常退出会发生什么事?Yes, 这就是我们要防止的)。

将autossh脚本以守护进程方式运行的方法很多,最简单的就是修改 inittab 文件。如果之前修改过 /etc/rc.local 文件,记得将其中关于运行autossh的语句删掉或者注释掉,否则和守护进程的设置会重复。 打开 inittab 文件,参考其它项目的写法添加一行就搞定,如下:

1
AUTOSSH:12345:respawn:sh /home/user/bin/socks_proxy.sh
另外,使用守护进程方式运行autossh时要注意一点,运行的脚本必须不是daemon方式的。所以还需要将前面的连接语句:
1
autossh -M 40000 $SSH_SERVER -f -q -C -g -N -D 9090
修改为:
1
autossh -M 40000 $SSH_SERVER -q -C -g -N -D 9090
也即是去掉其中的 -f 选项,否则,init会不断尝试重新运行autossh命令,导致服务不稳定。

守护进程还可以通过第三方工具来配置,比如daemontools,monit等等之类,功能相对来说更强大一点,但是所提供的基本功能都是一样的,所以这里也就不再详细介绍了,有兴趣的自己看文档编译配置一份就能用。

这个就是我配了壳后的Raspberry Pi, 运行起来还是很稳定的:

./images/blog/RPi_shell.jpg

配了壳的RPi

在Linux内核模块中操作I2C设备

| Comments

近期公司项目较为空闲,抽空做了一些学习性质的研发内容,其中涉及到在Linux内核模块中使用I2C对外部器件进行控制的操作。虽然在Linux中操作使用I2C设备并不复杂,但本人接触Linux内核驱动开发时间并不算长,此次学习中也算较为系统的了解了Linux中对I2C设备的操控方式,谨在此做下记录。

通过Linux内核文档中关于操作I2C设备的文章后不难看出Linux中注册使用I2C设备一般通过四种方法1

  1. 通过总线号声明设备
  2. 立即探测设备
  3. 通过Probe探测相应设备
  4. 在用户空间立即探测

简单来说,第一种方式一般应用在嵌入式设备中。因为对于嵌入式设备来说,外围器件基本都是固定的,只需提供有限几款器件的支持即可。使用这种方式的时候,需要在板级配置文件中定义并设置 i2c_board_info 这个结构体的内容。其中需要配置设备名称和设备地址,此外设备中断和私有数据结构也可以选择设置。然后使用 i2c_register_board_info 这个接口对设置的设备进行注册使用。需要注意的是这种方法注册的设备是在注册I2C总线驱动时进行驱动适配的。

第二种方法可以通过给定的I2C适配器以及相应的I2C板级结构体,自行通过 i2c_new_device 接口进行添加注册所需的设备。这种方法灵活性要较第一种方法大,可以很方便的在模块中使用。

第三种方法是 2.6 内核之前的做法,使用 detect 方法去探测总线上的设备驱动。因为探测机制的原因,会导致一些副作用的发生,所以不建议使用,除非真的没有别的办法。

第四种方法是在Linux的控制台上,在用户空间通过sysfs,使用 /sys/bus/i2c/devices/i2c-3/new_device 节点进行设备的添加注册。

从上面可以看出,如果需要在Linux内核中以模块的方式对I2C设备进行驱动控制的话,第二种方法是比较推荐的。通过测试,在module的init中使用

1
2
3
4
5
struct i2c_adapter *i2c_adap;
struct i2c_client *i2c_client;
i2c_adap = i2c_get_adapter(1);
i2c_client = i2c_new_device(i2c_adap, &i2c_device);
i2c_put_adapter(i2c_adap);
即可成功注册I2C设备。其中:
1
2
3
static struct i2c_board_info ft5306_i2c_device = {
    I2C_BOARD_INFO("test_i2c", 0x11),
};
以上,对于如何在模块中注册使用I2C设备简单做了描述。那么如何在另外的模块中对已经注册的I2C设备进行反注册呢?由于内核中操作I2C设备都是通过 i2c_client 结构进行,所以问题可以抽象为如何在内核中获取指定设备的 i2c_client 结构指针。通过查阅内核API,也找到了一个方法可以达到这样的目的,如下:
1
2
3
4
5
6
7
8
struct i2c_client *i2c_client;
struct device *i2c_dev;

i2c_dev = bus_find_device_by_name(&i2c_bus_type, NULL, "1-0011");
if(i2c_dev)
    i2c_client  =  container_of(i2c_dev, struct i2c_client, dev);
if(i2c_client)
    i2c_unregister_device(i2c_client);
该反注册例子的内容就是对前面注册的 0x11 地址的设备进行反注册。注意 bus_find_device_by_name 函数中第三项参数,该参数是需要查找的设备在总线上注册的名称。”1”代表着1号适配器,”0011”是16位的I2C地址。如此便可方便的在内核模块中对I2C设备进行挂载和解挂了。

Footnotes:

1 参考Linux内核目录下的Documentation/i2c/instantiating-devices

近期的烦恼

| Comments

算下来两周没有在这里写博客了,其中一大原因在于天气渐凉,不高兴一人在电脑旁受冻-_-!!! 咳咳咳,其实天气有一定影响,不过更主要的问题在于最近遇到的事情繁杂,虽然没有什么难不可为,但自己也确实花了些心思。

首先是工作上的两个任务。近期项目正好有点空挡,于是自己决定将高通平台的电量管理从MP端移植至AP端内核里,同时添加一些判断逻辑,以使统计的电量结果不至于像原版那样生硬怪异。只可惜高通平台好多处理需要依赖远程调用,而自己手里却没有一份关于远程调用的官方文档。最后通过高通的support搞定了内核中对PMIC的一些操作,但又发现无法正常取得相应中断状态。总之,这个看起来简单地任务搞了2周还没有实质性突破。另外一个任务是协助同事完成一个用户层接口,方便在用户层升级器件的firmware。这个任务进展倒是蛮快,3个小时就写好框架和功能调试。可惜最后发现buffer有4k限制,现在还在找对策方法,这两天就在考虑这个问题。

其次,周末在家各种收拾打扫,洗衣做饭。连续两周了,除了干家务和觅食以外,其它什么都没干。想来自己效率低是一个问题,着实应该改进一下。哦,今天物业还过来处理家里渗水的墙面,也算拖了大半年之久的工程开始了第一步。

再者,为了维护简历,将简历移植到moderncv上并使用git来管理。后来又想更统一使用orgmode来写简历,但是苦于找不到好的解决方案,现在琢磨着是不是要自己搞一个。

最后,算是小成就。前几日知道了支持kindle3的kindlepdfviewer这个越狱软件,抽空搞了一下,发现对扫描版pdf的支持好到不可思议。于是kindle上瞬间多了很多扫描版的图书。首当其冲就是一直想看但一直没有看的《emacs lisp编程入门》和《GEB》。前者已经看了一半以上,作为emacs爱好者,到现在才较为系统的了解学习lisp是有点晚,不过至少我开始入门了:)

今日纯属碎碎念,希望下周工作上任务都能快速完成,个人的几个安排都能顺利进行。另,下篇文章没差错的话应该是功夫网专题,否则又要被人数落跳票了。