搜索

Google
 

星期三, 十二月 26, 2007

在Gentoo上构建windows交叉编译环境

最近公司开发一款跨平台的产品,主要支持的平台包括linux和windows,由于我的日常工作操作系统是linux,因此要调试windows部分代码极其不方便,要么在linux下安装一个虚拟机跑windows,要么双系统重启机器进入windows,总之是比较费时且麻烦。于是不安份的情绪让我折腾了一上午时间,终于成功地在我的Gentoo Linux上构建好了windows交叉编译环境。

其实在Gentoo Linux上构建交叉编译环境非常方便,crossdev一行命令就搞定一切,关于crossdev的用法很简单,可以参考Gentoo官方网站或者Gentoo-Wiki网站上的相关文档。构建windows交叉编译环境直接执行crossdev i686-mingw32即可。

构建好交叉编译环境之后可以写个hello world程序尝试一下,i686-mingw32-gcc -o test.exe test.c,然后file test.exe即可看到文件类型为“MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit”,你也可以用wine test.exe执行一下看看结果。

似乎到此就圆满结束了?为了更方便地进行交叉编译,继续往下看吧。。。

开源软件大多数采用autotools来完成项目的自动构造,我们能不能利用autotools来方便地进行管理规模稍大的项目呢?答案是肯定的,今天上午为了测试交叉编译写了一个简单的win32 service,可以通过以下链接下载:SMQService,方便懒人们快速尝试交叉编译,不过你可能马上就会遇到挫折,来看看结果吧。

debianl@ldb /data/codes/test/win32service $ ./configure --target=i686-mingw32 --host=i686-mingw32 --build=i686-mingw32 --prefix=/usr/i686-mingw32/usr
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for i686-mingw32-gcc... i686-mingw32-gcc
checking for C compiler default output file name... a.exe
checking whether the C compiler works... configure: error: cannot run C compiled programs.
If you meant to cross compile, use `--host'.
See `config.log' for more details.

仔细看一下config.log即可看出configure会采用编译器编译一个测试程序并执行,在这儿会产生一个a.exe并直接执行,在linux直接执行exe文件如果不做点工作是不行的,直接执行a.exe报错configure也就停下了,告诉你"cannot run C compiled programs",其实compiler工作得好好的,那么我们现在只要解决直接在linux下运行exe文件就可以让autotools顺利地工作了。

其实要实现这个功能很简单,编译内核时注意选上Executable file formats->Kernel support for MISC binaries,如果编译成模块modprobe binfmt_misc即可加载,然后给你的/etc/fstab里加上一行“none /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0”,在系统引导完成之后执行"echo ':DOSWin:M::MZ::/usr/bin/wine:' > /proc/sys/fs/binfmt_misc/register",在Gentoo下可以将这行指令写到/etc/conf.d/local.start文件,现在再试应该就好了。

以下是我的编译日志:
debianl@ldb /data/codes/test/win32service $ ./configure --target=i686-mingw32 --host=i686-mingw32 --build=i686-mingw32 --prefix=/usr/i686-mingw32/usr
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for style of include used by make... GNU
checking for i686-mingw32-gcc... i686-mingw32-gcc
checking for C compiler default output file name... a.exe
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables... .exe
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether i686-mingw32-gcc accepts -g... yes
checking for i686-mingw32-gcc option to accept ISO C89... none needed
checking dependency style of i686-mingw32-gcc... gcc3
checking how to run the C preprocessor... i686-mingw32-gcc -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking for stdlib.h... (cached) yes
checking for string.h... (cached) yes
checking stdio.h usability... yes
checking stdio.h presence... yes
checking for stdio.h... yes
checking windows.h usability... yes
checking windows.h presence... yes
checking for windows.h... yes
checking for an ANSI C-conforming const... yes
checking for inline... inline
checking for size_t... yes
checking for stdlib.h... (cached) yes
checking for GNU libc compatible malloc... yes
checking for stdlib.h... (cached) yes
checking for GNU libc compatible realloc... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating src/config.h
config.status: executing depfiles commands

debianl@ldb /data/codes/test/win32service $ make
Making all in src
make[1]: Entering directory `/data/codes/test/win32service/src'
make all-am
make[2]: Entering directory `/data/codes/test/win32service/src'
i686-mingw32-gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
i686-mingw32-gcc -g -O2 -o SMQService.exe main.o
make[2]: Leaving directory `/data/codes/test/win32service/src'
make[1]: Leaving directory `/data/codes/test/win32service/src'
make[1]: Entering directory `/data/codes/test/win32service'
make[1]: Nothing to be done for `all-am'.
make[1]: Leaving directory `/data/codes/test/win32service'

debianl@ldb /data/codes/test/win32service $ file src/SMQService.exe
src/SMQService.exe: MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit

星期四, 七月 26, 2007

汇编学习之系统调用

最近工作比较闲散,因此抽空看了看汇编,基本上从头开始,看了前面几章不知不觉也学会了“Hello world”,刚好看的这本书基本上都是以GNU的开发工具为开发环境的,这比较适合我的兴趣。其实看的这几章也就只有一章(这一章还没看完^_^)才是汇编语言的基础,刚学会了点AT&T的汇编语法,知道了跟Intel的汇编语法的区别,另外这一章用不同的方式实现了cpuid的调用,学会了如何调用系统调用以及如何调用C函数库中的函数。

先看看下面通过系统调用实现的hello world代码:

.section .data
msg:
.ascii "Hello world!\n"

.section .text

.globl _start

_start:
movl $4, %eax
movl $1, %ebx
movl $msg, %ecx
movl $13, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80

系统调用是通过int 0x80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从/usr/include/asm/unistd.h中可以看到exit的功能号_NR_exit为1,write(_NR_write)功能号为4,因此第一个int $0x80调用之前eax寄存器值为4,ebx为文件描述符,stdout的文件描述符为1,ecx则为buffer的内存地址,edx为buffer长度。第二个int $0x80之前eax为1表示调用exit,ebx为0表示返回0。编译链接步骤如下所示:
as -o helloworld.o helloworld.s
ld -o helloworld helloworld.o

再看看调用C函数的代码:

.section .data
output:
.asciz "Hello world!\n"

.section .text

.globl _start

_start:
pushl $output
call printf
addl $8, %esp
pushl $0
call exit

这个例子相对来说看起来简单得多,将参数压入堆栈调用相应的函数即可,不过要注意的是:1、C函数需要调用的字符串参数必须以asciz声明,而不是ascii,这样才会给字符串后面加'\0'。2、压栈顺序刚好与C函数顺序相反,最后的参数应最先入栈。3、链接的时候需要链接libc.so库并指定动态链接库加载器/lib/ld-linux.so.2,步骤如下:
as -o helloworld2.o helloworld2.s
ld -dynamic-linker /lib/ld-linux.so.2 -lc -o helloworld2 helloworld2.o

星期三, 七月 11, 2007

iptables端口映射

一个简单的应用:我的机器IP是192.168.1.240,期望访问另一个局域网的web服务器其IP为192.168.0.109,中间有台双网卡的服务器用来做透明代理,该服务器eth0为192.168.1.2,eth1为192.168.0.2。

首先需要将到达192.168.1.2:80的包顺利转给192.168.0.109,因此有了下面的DNAT:
iptables -t nat -A PREROUTING -s 192.168.1.0/24 -d 192.168.1.2 -p tcp --dport 80 -j DNAT --to 192.168.0.109:80

乍一看这应该没什么问题了,可是接下来问题是发现我的包是到0.109了,可是我的机器与0.109仍然无法正常通信,原来是到达0.109的源地址仍然是1.240,0.109回包的时候当然无法直接给1.240了,接下来就是SNAT的事了:
iptables -t nat -A POSTROUTING -d 192.168.0.109 --dport 80 -o eth1 -j SNAT --to 192.168.0.2

当然别忘了echo 1 > /proc/sys/net/ipv4/ip_forward,最好连/etc/sysctl.conf里的net.ipv4.ip_forward=1一并加上。。。

星期四, 六月 21, 2007

关于inotify

inotify是什么?用它能干些什么?这个问题我们还是首先从内核的文档开始吧--Documentation/filesystems/inotify.txt(说点题外话,内核文档虽然是没有任何格式的txt文档,给人的感觉却非常好,而且作者总是以最精炼的语言清楚地描述了相关的内容): a powerful yet simple file change notification system,通俗点说它是一个内核用于通知用户空间客户程序文件系统变化的系统,并且它是powerful yet simple的。

inotify的用户接口原型主要有以下3个:
初始化:int inotify_init(void);
添加监视对象:int inotify_add_watch(int fd, const char *path, uint32_t mask);
删除监视对象:int inotify_rm_watch(int fd, uint32_t wd);

内核文档里对于inotify_rm_watch的用户接口原型描述似乎有点问题,第2个参数写成了mask,实际上它应该是inotify_add_watch返回的watch descriptor。

根据文档描述可以看出,inotify使用大概分为以下几个步骤:
1、int fd = inotify_init(); 初始化inotify实例。
2、int wd = inotify_add_watch(fd, path, mask); 添加监视对象,这里的mask是一个或多个事件的位标记集合,具体的事件定义可参考(linux/inotify.h)或者(sys/inotify.h),前者为linux内核头文件,后者为glibc提供的头文件。
3、size_t len = read(fd, buf, BUF_LEN); 读取事件数据,buf应是一个指向inotify_event结构数组的指针。不过要注意的是inotify_event的name成员长度是可变的,这个问题后面再解释。
4、已经存在的监视对象可通过int ret = inotify_rm_watch(fd, wd);来删除。

下面我们来看一个示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/inotify.h>

static void
_inotify_event_handler(struct inotify_event *event)
{
printf("event->mask: 0x%08x\n", event->mask);
printf("event->name: %s\n", event->name);
}

int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <file/dir>\n", argv[0]);
return -1;
}

unsigned char buf[1024] = {0};
struct inotify_event *event = {0};
int fd = inotify_init();
int wd = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS);

for (;;) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
if (select(fd + 1, &fds, NULL, NULL, NULL) > 0) {
int len, index = 0;
while (((len = read(fd, &buf, sizeof(buf))) < 0) && (errno == EINTR));
while (index < len) {
event = (struct inotify_event *)(buf + index);
_inotify_event_handler(event);
index += sizeof(struct inotify_event) + event->len;
}
}
}

inotify_rm_watch(fd, wd);

return 0;
}


由以上代码可以看出inotify_init返回的file descriptor是可以用select或者poll进行I/O复用的。由于inotify_event长度是可变的,因此在读取inotify_event数组内容的时候需要动态计算下一个事件数据的偏移量(index += sizeof(struct inotify_event) + event->len),len成员即name成员的长度。

在实际测试过程中,通过运行以上的测试程序监视一个文件,还遇到过两个奇怪的现象:用vim编辑那个被监视的文件,修改并保存,触发的是IN_DELETE_SELF和IN_MOVE_SELF事件而不是我们所期望的IN_MODIFY事件;再次修改并保存的时候不再有任何事件发生。希望能给看官一个教训,其实这是由于vim的工作机制引起的,vim会先将源文件复制为另一个文件,然后在另一文件基础上编辑(一般后缀名为swp),保存的时候再将这个文件覆盖源文件,因此会出现上述的第一个现象,第二个现象是因为原来的文件已经被后来的新文件代替,因此监视对象所监视的文件已经不存在了,所以自然不会产生任何事件。

另外,内核文档第四部分介绍了inotify的背景以及设计思路,不可不看:

Q: What is the design decision behind not tying the watch to the open fd of
the watched object?

A: Watches are associated with an open inotify device, not an open file.
This solves the primary problem with dnotify: keeping the file open pins
the file and thus, worse, pins the mount. Dnotify is therefore infeasible
for use on a desktop system with removable media as the media cannot be
unmounted. Watching a file should not require that it be open.

Q: What is the design decision behind using an-fd-per-instance as opposed to
an fd-per-watch?

A: An fd-per-watch quickly consumes more file descriptors than are allowed,
more fd's than are feasible to manage, and more fd's than are optimally
select()-able. Yes, root can bump the per-process fd limit and yes, users
can use epoll, but requiring both is a silly and extraneous requirement.
A watch consumes less memory than an open file, separating the number
spaces is thus sensible. The current design is what user-space developers
want: Users initialize inotify, once, and add n watches, requiring but one
fd and no twiddling with fd limits. Initializing an inotify instance two
thousand times is silly. If we can implement user-space's preferences
cleanly--and we can, the idr layer makes stuff like this trivial--then we
should.

There are other good arguments. With a single fd, there is a single
item to block on, which is mapped to a single queue of events. The single
fd returns all watch events and also any potential out-of-band data. If
every fd was a separate watch,

- There would be no way to get event ordering. Events on file foo and
file bar would pop poll() on both fd's, but there would be no way to tell
which happened first. A single queue trivially gives you ordering. Such
ordering is crucial to existing applications such as Beagle. Imagine
"mv a b ; mv b a" events without ordering.

- We'd have to maintain n fd's and n internal queues with state,
versus just one. It is a lot messier in the kernel. A single, linear
queue is the data structure that makes sense.

- User-space developers prefer the current API. The Beagle guys, for
example, love it. Trust me, I asked. It is not a surprise: Who'd want
to manage and block on 1000 fd's via select?

- No way to get out of band data.

- 1024 is still too low. ;-)

When you talk about designing a file change notification system that
scales to 1000s of directories, juggling 1000s of fd's just does not seem
the right interface. It is too heavy.

Additionally, it _is_ possible to more than one instance and
juggle more than one queue and thus more than one associated fd. There
need not be a one-fd-per-process mapping; it is one-fd-per-queue and a
process can easily want more than one queue.

Q: Why the system call approach?

A: The poor user-space interface is the second biggest problem with dnotify.
Signals are a terrible, terrible interface for file notification. Or for
anything, for that matter. The ideal solution, from all perspectives, is a
file descriptor-based one that allows basic file I/O and poll/select.
Obtaining the fd and managing the watches could have been done either via a
device file or a family of new system calls. We decided to implement a
family of system calls because that is the preferred approach for new kernel
interfaces. The only real difference was whether we wanted to use open(2)
and ioctl(2) or a couple of new system calls. System calls beat ioctls.

星期日, 六月 10, 2007

rs422在linux跟我开了个玩笑

很久没有更新blog了,就跟抽烟一样,如果长时间不抽的话也就不想抽了,看来这是一个不好的现象,不过长时间的沉默也说明了自己这段时间在技术没有什么积累。罢了,今天的blog就记录这段时间来比较郁闷的事情。

这个项目的开发就像当年初中的长跑一样,前几圈劲头十足信心满怀,接下来的情况我想大部分人跟我一样,慢慢地感觉有点累,再后来压力越来越大,越来越感觉大脑缺氧呼吸困难,到后来已经身心疲累。人这一生从出生开始就背负了责任,要么为责任活着要么超越责任为理想活着,所以有人活得辛苦而无趣,有人轻松而超然。有点跑题了,还是说一下前几天遇到的技术问题。

客户方有一外部设备是N年前购买的,一直用于原来的系统中,这次需要在本项目中使用此外设,关于这个外设的文档早已缺失,唯一可以参考的就是一个windows下的动态链接库源代码(不过这也是最重要的参考资料,候大侠一本书上不是也说“源码面前了无秘密”嘛),于是参考此代码写了一个windows下的测试程序很顺利地测试通过,测试代码如下:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int
main(int argc, char **argv)
{
HANDLE h_com;
unsigned char addr = 0x01;
unsigned char buf[10];
int i;
DCB dcb;
COMMTIMEOUTS timeouts;
DWORD n;

if (argc != 2) {
printf("%s <comdev>\n", argv[0]);
return -1;
}

h_com = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (h_com == INVALID_HANDLE_VALUE) {
printf("Initialize COM device failed\n");
return -1;
}

GetCommState(h_com, &dcb);
dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = MARKPARITY;
dcb.StopBits = ONESTOPBIT;
SetCommState(h_com, &dcb);

GetCommTimeouts(h_com, &timeouts);
timeouts.ReadTotalTimeoutConstant = 10;
SetCommTimeouts(h_com, &timeouts);

WriteFile(h_com, &addr, sizeof(addr), &n, NULL);

Sleep(1);
dcb.Parity = SPACEPARITY;
SetCommState(h_com, &dcb);
memcpy(buf, "\x02\x85\x83\04", 4);
WriteFile(h_com, buf, 4, &n, NULL);

Sleep(2);
ReadFile(h_com, buf, sizeof(buf), &n, NULL);
printf("read %d bytes\n", n);

for (i = 0; i < n; ++i)
printf("%02x ", buf[i]);
printf("\n");

return 0;
}

由此代码可以看出通信过程大概是这样的:将串口设置为8M1发送地址,然后将串口设置更改为8S1发送请求数据,再然后接收数据,看起来还是很简单的。接下来很快地写好了linux下的测试程序,源代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <comdev>\n", argv[0]);
return -1;
}

int fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
printf("Initialize COM device failed.\n");
return -1;
}

struct termios options;
if (tcgetattr(fd, &options) == -1)
return -1;

cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);

options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8 | CLOCAL | CREAD | CMSPAR | PARODD;
options.c_cflag &= CSTOPB;
options.c_iflag |= INPCK;
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ECHO | ECHOE | ICANON | ISIG);
tcflush(fd, TCIFLUSH);
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSANOW, &options));

char addr = 0x01;
if (write(fd, &addr, 1) == -1)
goto failed;

usleep(1000);

options.c_cflag &= ~PARODD;
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSADRAIN, &options));

usleep(1000);

unsigned char buf[10];
memcpy(buf, "\x02\x85\x83\x04", 4);

if (write(fd, buf, 4) == -1)
goto failed;

usleep(10000);

ssize_t read_n;
int i;
read_n = read(fd, buf, sizeof(buf));
printf("result: %d\n", read_n);
for (i = 0; i < read_n; ++i)
printf("%02X ", buf[i]);
printf("\n");

close(fd);
return 0;

failed:
close(fd);
return -1;
}

虽然Mark Parity和Space Parity在Posix中并未定义,但是在linux下通过CMSPAR仍然可以实现,tcsetattr的man pages可以看到对CMSPAR的描述(),可是反复尝试仍然有问题,后来一个完全不懂linux开发的同事问了我设置串口各行代码的意思,并通过tcsetattr的man pages,一个一个地组合这些标记,居然成功了!结果出人意料,且看如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

int
main(int argc, char **argv)
{
if (argc != 2) {
printf("Usage: %s <comdev>\n", argv[0]);
return -1;
}

int fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
printf("Initialize COM device failed.\n");
return -1;
}

struct termios options;

memset(&options, 0, sizeof(options));

cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);

options.c_cflag = CS8 | CLOCAL | CREAD | CSTOPB | CMSPAR;
options.c_iflag = INPCK;
options.c_oflag = 0;
options.c_lflag = 0;
tcflush(fd, TCIFLUSH);
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSANOW, &options));

char addr = 0x01;
if (write(fd, &addr, 1) == -1)
goto failed;

usleep(1000);

options.c_cflag |= PARENB | PARODD;
printf("tcsetattr result: %d\n", tcsetattr(fd, TCSADRAIN, &options));

usleep(1000);

unsigned char buf[10];
memcpy(buf, "\x02\x85\x83\x04", 4);

if (write(fd, buf, 4) == -1)
goto failed;

usleep(10000);

ssize_t read_n;
int i;
read_n = read(fd, buf, sizeof(buf));
printf("result: %d\n", read_n);
for (i = 0; i < read_n; ++i)
printf("%02X ", buf[i]);
printf("\n");

close(fd);
return 0;

failed:
close(fd);
return -1;
}

想不到抛开CMSPAR不算8M1看起来是8N2,而8S1则看起来是8O2。。。

星期日, 四月 15, 2007

C代码中如何得到python脚本异常时的traceback信息

在软件项目的开发过程中,我们总是试图让程序能够适应更多的应用环境以及业务流程,不管你对于需求的了解如何准确,也不管你做了有多么充分的估计,但有很多情况仍然让你无法应付,比如:需求是会变化的;维护项目的人不一定都能用C/C++写出没有内存问题的代码等等。

让软件系统能够适应更多变化的方法有很多种,高度的抽象、动态链接技术等等都是一直以来被大家采用较广的方法,那么今天我们要讨论的是脚本引擎的嵌入问题。通常我更喜欢c代码加上python脚本引擎的结构,python的好处就不多说了。不过在C中嵌入python脚本引擎调用之后也有一些非常麻烦的地方,比如不便于调试,因为我们的宿主应用程序必然会为python脚本wrap一些module、class、function等等,因此这带来了调试的难度,有时候仅仅依赖print是一件非常低效的事,那么我们如何得到python脚本在异常时的traceback情况呢?比如代码出现异常到底是在哪个脚本文件中,到底是哪行代码出了问题?错误又是什么呢?我们先来看看下面的函数:

#include 
#include
#include
#include

void
process_python_exception(void)
{
char buf[512], *buf_p = buf;
PyObject *type_obj, *value_obj, *traceback_obj;
PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);
if (value_obj == NULL)
return;

if (!PyString_Check(value_obj))
return;

char *value = PyString_AsString(value_obj);
size_t szbuf = sizeof(buf);
int l;
PyCodeObject *codeobj;

l = snprintf(buf_p, szbuf, _("Error Message:\n%s"), value);
buf_p += l;
szbuf -= l;

if (traceback_obj != NULL) {
l = snprintf(buf_p, szbuf, _("\n\nTraceback:\n"));
buf_p += l;
szbuf -= l;

PyTracebackObject *traceback = (PyTracebackObject *)traceback_obj;
for (;traceback && szbuf > 0; traceback = traceback->tb_next) {
codeobj = traceback->tb_frame->f_code;
l = snprintf(buf_p, szbuf, "%s: %s(# %d)\n",
PyString_AsString(codeobj->co_name),
PyString_AsString(codeobj->co_filename),
traceback->tb_lineno);
buf_p += l;
szbuf -= l;
}
}

message_error_dialog_show(buf);

Py_XDECREF(type_obj);
Py_XDECREF(value_obj);
Py_XDECREF(traceback_obj);
}

PyErr_Fetch用来获取异常对象,并且同时可以得到traceback对象,traceback其实是一个PyTracebackObject结构体,可以在python的头文件traceback.h中找到,PyTracebackObject其实也是一个单向链表,可以通过其tb_next成员来枚举,其tb_frame则是一个_frame结构体,在frameobject.h头文件中可以看到,其中f_code就是我们需要的PyCodeObject结构体,PyCodeObject中就可以得到co_name和co_filename这两个关键的描述,一个是错误信息另一个是文件名称,PyTracebackObject的tb_lineno就是出错的行号,有了这些数据我们调试还有困难吗?

星期五, 三月 30, 2007

星期四, 三月 29, 2007

生死边缘的轮回 - reiserfs文件系统的数据恢复

昨天晚上给客户一个应用定制裁减linux系统的时候,发生了一件非常不幸的事,我的data目录被我无意给rm -rf删除掉了,那里面存放着我的所有项目的代码和文档,还有很多其它重要的东西。看见那个目录突然什么都没有的时候那种感觉简直无法形容,由于长期出差一直没有机会给公司保留备份,还好今天恢复了大部分重要的文档和代码。希望跟我一样倒霉的朋友能以此为鉴,删除文件时一定要小心,并且经常备份重要的代码和文档。

网上有两篇文章对reiserfs文件系统的数据恢复做了详细的介绍,ReiserFS undelete/data recovery HOWTOReiserfs filesystem recovery,另外提供几点建议:
  1. 在误删除之后切记不要再进行任何操作,立即关机,做好准备再来做数据恢复!
  2. 准备好一块大硬盘或移动硬盘(最好使用usb 2.0的硬盘盒子),第一时间用dd把误删除数据的分区完整备份一份。恢复也最好针对这个image文件,直到重要的数据完全找到再处理该分区,如果失败了还可以借助专业数据恢复公司来进行数据恢复。
  3. 您在恢复时一定记得给reiserfsck加上-l参数,生成日志文件是非常重要的,如果文件太多reiserfsck并不能100%地恢复您的数据,日志文件里会告诉您哪些文件没有被正确地恢复。再次提醒您经常备份自己重要的数据!!!
顺便赞叹一下reiserfs作者,reiserfs确实是一个非常优秀的文件系统,我54G容量的分区dd到移动硬盘时间不超过1小时,数据恢复时间不超过30分钟,并且最终重要的数据基本上都得以恢复!

星期六, 三月 17, 2007

beryl/compiz的ontop补丁

用beryl/compiz的时候有个比较遗憾的地方,那就是窗口没有了ontop功能,也就是窗口置顶,不过给libwnck打上补丁就可以解决这个问题了。

补丁地址:libwnck补丁

星期一, 三月 12, 2007

GtkScrolledWindow滚动条问题

在使用GtkTreeView时,用GtkScrolledWindow来添加滚动条,但列表(或树)中的数据行在不断得更新(增加),而垂直滚动条默认情况下不能自动往下滚动,以显示最新的数据行,而是始终停在top的位置,针对此问题有什么方法可以解决呢?

每次增加数据行之后,得到最后一行的path,需要手动调用gtk_tree_view_scroll_to_cell,滚动到最后的地方。如果不想手动 调用,可以监听 model 的 row-inserted,在该signal callback中gtk_tree_view_scroll_to_cell,如果大量迅速的增加新行的话,速度会受影响,可以跟延时结合减少scroll的频率。

星期日, 三月 11, 2007

强悍的回复帖

从CSDN主页上偶尔看到了一篇帖子,于是进去看了一下,十年MFC经历认识的Microsoft技术,楼主的行文风格不错,于是耐心地看了下去,一来觉得楼主知识系统全面值得学习,二来楼主的文字功底也颇为深厚,这种散文式的技术帖即使用来消遣也不错。不过没想到的是后来的一个回复帖真的让我捧腹大笑久久不能平静^_^。。。

逐句地看完这个帖子以后,我的心久久不能平静,震撼啊!为什么会有如此好的帖子!我纵横网络bbs多年,自以为再也不会有任何帖子能打动我,没想到今天看到了如此精妙绝伦的这样一篇帖子。楼主,是你让我深深地理解了‘人外有人,天外有天’这句话。谢谢侬!在看完这帖子以后,我没有立即回复,因为我生怕我庸俗不堪的回复会玷污了这网上少有的帖子。但是我还是回复了,因为觉得如果不能在如此精彩的帖子后面留下自己的网名,那我死也不会瞑目的!能够在如此精彩的帖子后面留下自己的网名是多么骄傲的一件事啊!

楼主,请原谅我的自私!我知道无论用多么华丽的辞藻来形容楼主您帖子的精彩程度都是不够的,都是虚伪的,所以我只想说一句:您的帖子太好看了!我愿意一辈子的看下去!这篇帖子构思新颖,题材独具匠心,段落清晰,情节诡异,跌宕起伏,主线分明,引人入胜,平淡中显示出不凡的文学功底,可谓是字字珠玑,句句经典,是我辈应当学习之典范。就小说艺术的角度而言,这篇帖子不算太成功,但它的实验意义却远远大于成功本身。正所谓:“一马奔腾,射雕引弓,天地都在我心中!”楼主真不愧为无厘界新一代的开山怪!

星期五, 三月 09, 2007

如何方便控制笔记本触摸板

笔记本的鼠标触摸板我基本上很少用,比起外接鼠标还是没那么方便灵活,何况写程序的时候基本上很少用鼠标,但是笔记本的触摸板却在写程序的时候经常搞得鼠标指针胡乱飘移,非常影响键盘输入的准确性。

在linux下控制触摸板必须得使用2.6.x内核,大致有几件事要做:正确配置内核、安装synaptics驱动、正确配置xorg.conf。

1、配置内核




Linux Kernel Configuration: Enable synaptics support
Device Drivers --->
   Input Device Support --->
<*> Event Interface
[*] Mouse --->

<*> PS/2 mouse


2、安装synaptics,在gentoo下直接emerge synaptics即可。

3、配置xorg.conf,首先看看你的输入设备信息,我机器上的输出信息如下:

/home/debianl $ cat /proc/bus/input/devices
I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/class/input/input0
H: Handlers=kbd event0
B: EV=120013
B: KEY=4 2000000 3802078 f840d001 feffffdf ffefffff ffffffff fffffffe
B: MSC=10
B: LED=7

I: Bus=0011 Vendor=0002 Product=0007 Version=0000
N: Name="SynPS/2 Synaptics TouchPad"
P: Phys=isa0060/serio4/input0
S: Sysfs=/class/input/input1
H: Handlers=mouse0 event1
B: EV=b
B: KEY=6420 0 70000 0 0 0 0 0 0 0 0
B: ABS=11000003

I: Bus=0003 Vendor=046d Product=c016 Version=0340
N: Name="Logitech Optical USB Mouse"
P: Phys=usb-0000:00:1d.1-1/input0
S: Sysfs=/class/input/input2
H: Handlers=mouse1 event2
B: EV=7
B: KEY=70000 0 0 0 0 0 0 0 0
B: REL=103

然后配置xorg.conf,加入TouchPad的Section内容,并在ServerLayout中加入InputDevice "TouchPad" "AlwaysCore",以下是我的xorg.conf配置:

Section "ServerLayout"
Identifier "X.org Configured"
Screen 0 "Screen0" 0 0
InputDevice "Mouse0" "CorePointer"
InputDevice "Keyboard0" "CoreKeyboard"
InputDevice "TouchPad" "AlwaysCore"
EndSection

Section "Files"
RgbPath "/usr/share/X11/rgb"
ModulePath "/usr/lib/xorg/modules"
FontPath "/data/fonts/"
FontPath "/usr/share/fonts/misc/"
FontPath "/usr/share/fonts/TTF/"
FontPath "/usr/share/fonts/OTF"
FontPath "/usr/share/fonts/Type1/"
FontPath "/usr/share/fonts/CID/"
FontPath "/usr/share/fonts/100dpi/"
FontPath "/usr/share/fonts/75dpi/"
EndSection

Section "Module"
Load "glx"
Load "extmod"
Load "xtrap"
Load "record"
Load "dbe"
Load "dri"
Load "freetype"
Load "type1"
EndSection

Section "InputDevice"
Identifier "Keyboard0"
Driver "kbd"
EndSection

Section "InputDevice"
Identifier "Mouse0"
Driver "mouse"
Option "Protocol" "auto"
Option "Device" "/dev/input/mice"
Option "ZAxisMapping" "4 5 6 7"
EndSection

Section "InputDevice"
Identifier "TouchPad"
Driver "synaptics"
Option "Device" "/dev/input/mouse0"
Option "Protocol" "auto"
Option "SHMConfig" "on"
EndSection

Section "Monitor"
Identifier "Monitor0"
VendorName "Monitor Vendor"
ModelName "Monitor Model"
EndSection

Section "Device"
Identifier "Card0"
Driver "nvidia"
VendorName "nVidia Corporation"
BoardName "Unknown Board"
BusID "PCI:1:0:0"
Option "Accel" "True"
Option "RenderAccel" "True"
EndSection

Section "Screen"
Identifier "Screen0"
Device "Card0"
Monitor "Monitor0"
Option "AddARGBGLXVisuals" "true"
SubSection "Display"
Viewport 0 0
Depth 1
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 4
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 8
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 15
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 16
EndSubSection
SubSection "Display"
Viewport 0 0
Depth 24
EndSubSection
EndSection

Section "Extensions"
Option "Composite" "enable"
EndSection

现在你应该就可以使用synclient来配置各项参数了,也可以禁用触摸板,如果你用gnome的话可以安装一个gsynaptics,图形化的配置界面更易用点。

linux视频转换: mencoder

在网上发现一篇讲解利用mplayer的mencoder转换视频的文章,刚好适用我简陋的mp4转贴一下^_^,原文网址:Linux下的视频转换

制作适合在智能手机和PDA上观看的mpeg4视频。
Linux上有一个很强大的视频音频转换软件,就是Mplayer自带的mencoder (MPlayer's Movie Encoder)。mencoder就象是一台全手动的照相机,可调整的选项非常多,不过这儿只是把最常见的情况说一下。

转换avi文件,并把字幕内嵌到视频中。首先假设一下下列的条件:
文件名:video.avi
字幕文件名:video.srt
目标文件:new.avi
目标文件格式:mpeg4
目标文件视频码率:200
目标文件音频码率:64
目标文件分辨率: 320:240
制作内嵌字幕所需的字体:simsun.ttf
(上面各项都可以根据实际情况进行调整)
转换命令如下:

mencoder -oac mp3lame -lameopts vbr=3:br=64 -ovc lavc -lavcopts vcodec=mpeg4:mbd=1:vbitrate=200 -sub video.srt -o new.avi -font simsun.ttf video.avi -subcp cp936 -subfont-text-scale 4 -vf scale=320:240
各选项的含义:
-oac 编码文件的音频部分。这儿是用lame将音频encode成mp3,即mp3lame。其他可用的选项可以调用mencoder的man page查看。
-lameopts 顾名思义,即lame选项,这儿只需定义一下码率就可以了。
vbr 设定音频码率的方法,格式为vbr=<0-4>

0 cbr average bitrate
1 mt
2 rh constant bitrate Also forces CBR mode encoding on subsequent ABR presets modes.
3 abr
4 mtrh

在这儿用的是3,abr。
br 就是设定我们所需要的码率值,格式为br=<0-1024>,只能在vbr为0和3的情况下才能使用该选项。
-ovc 编码文件的视频部分。主要有以下几个选项

-ovc copy
不进行编码,只是复制视频流
-ovc divx4
编码成DivX4/DivX5
-ovc raw
编码成任意不压缩的格式(用‘-vf format’设定具体的格式)
-ovc lavc
使用libavcodec进行编码

-lavcopts 就是libavcodec的选项。
vcodec=使用指定的视频编码,下面列一下几个主要的值

h264
H.264
h263
H.263
h263p
H.263+
mpeg4
MPEG-4 (DivX 4/5)
msmpeg4
DivX 3
msmpeg4v2
MS MPEG4v2
wmv1
Windows Media Video, version 1 (又称 WMV7)
wmv2
Windows Media Video, version 2 (又称 WMV8)
rv10
旧的RealVideo格式
mpeg1video
MPEG-1 video
mpeg2video
MPEG-2 video

mbd 决定视频宏块的算法,这儿只需要mbd=1即可。其余的可以查看man page。
vbitrate 设定视频的码率(默认为800)。
-sub 设定字幕文件
-o 目标视频文件
-font 制作内嵌字幕所需字体的路径
-subcp 字幕的编码,简体中文就是cp936
-subfont-text-scale 字幕字体的大小
-vf scale 视频的分辨率
如果不需要将字幕内嵌入视频文件,只需要去掉-sub、-font、-subcp、-subfont-text-scale这几个选项。将rmvb转换成mpeg4也是如此。

python扩展与嵌入的入门级FAQ

以下是我在开发一个应用中集成python脚本引擎的过程中所遇到的一些入门级问题,稍后再整理一些比较完整的关于python扩展与嵌入的日志。

1、如何在宿主应用中注册module?可在Py_Initialize之前调用PyImport_AppendInittab来注册,也可在Py_Initialize之后直接调用注册函数。

2、如何添加宿主应用脚本的路径到python搜索路径?在Py_Initialize之后通过PySys_GetObject("path")得到sys.path,然后再通过PyList_Insert加入宿主应用的脚本路径。

3、如何调用python模块中的函数?通过PyImport_ImportModule得到module,然后再通过PyModule_GetDict从module中得到dict,再通过PyDict_GetItemString得到函数对象,最后通过PyObject_CallObject或其它PyObject_Call系列函数调用,当然调用之前最好用PyCallable_Check检查,另外就是dict、func都属于Borrowed reference,因此不需要Py_DECREF或Py_XDECREF。

4、所有的wrapper函数都应该返回PyObject实例指针,即使在python中调用的时候并不需要返回值,但仍需要返回Py_None,相当于C/C++中的void,Py_INCREF(Py_None); return Py_None;当然也可以用宏Py_RETURN_NONE来代替。

星期四, 三月 08, 2007

gtk+开发中的几个小经验

1、在gtk+开发过程中,我们经常需要在event handler中来访问该窗口中的某些控件的信息,通常习惯于整一大堆全局变量,其实还有更好的方式,在glib的实现中我们可以通过g_object_set_data和g_object_get_data给一个GObject对象绑定自定义的数据及属性,g_object_set_data_full还可以在对象释放资源时通知你的回调函数来做资源释放处理,这样我们就可以给event handler传递一个窗口对象指针就行了,其它的数据及控件信息可以通过窗口对象得到我们想要的东西。

2、某些对象并未提供所有属性的存取方法,但只要是通过GObject继承下来的对象均可采用g_object_set、g_object_set_property及g_object_get、g_object_get_property来存取这些属性,如给TreeView中某一列的值要求居右则可以对gtk_cell_renderer_text_new()产生的GtkCellRenderer对象调用g_object_set(renderer, "xalign", 1.0, NULL)来达到居右的效果。

3、使用g_idle_add及g_timeout_add等函数中的时候一定要小心,由于代码延后执行,因此一定要保证其数据的生命周期,如下面的例子是我在开发一个项目应用中遇到的问题(注释掉的代码即为出现问题之后的修正代码),传递的Python对象到代码执行的时候已经被释放掉了,因此出现了比较奇怪的现象:如果连续调用这个函数两次,结果加进去的两行信息全部都是最后一次的数据,当然可能还会有更奇特的现象发生。

static gboolean
_etc_main_win_add_trans(gpointer data)
{
PyObject *args = data;
if (PyTuple_Size(args) != lv_columns)
return FALSE;

GtkWidget *listview;
GtkTreeModel *model;
GtkTreeIter iter;
int i;
GValue value;
PyObject *obj;

listview = GTK_WIDGET(g_object_get_data(G_OBJECT(main_window), "trans_list"));
model = gtk_tree_view_get_model(GTK_TREE_VIEW(listview));
gtk_list_store_append(GTK_LIST_STORE(model), &iter);
for (i = 0; i < lv_columns; ++i) {
memset(&value, 0, sizeof(value));
g_value_init(&value, lv_types[i]);
obj = PyTuple_GetItem(args, i);
switch(lv_types[i]) {
case G_TYPE_INT:
if (PyInt_Check(obj))
g_value_set_int(&value, PyInt_AsLong(obj));
break;

case G_TYPE_LONG:
if (PyLong_Check(obj))
g_value_set_long(&value, PyLong_AsLong(obj));
break;

case G_TYPE_UINT:
if (PyLong_Check(obj))
g_value_set_uint(&value, PyLong_AsUnsignedLong(obj));
break;

case G_TYPE_ULONG:
if (PyLong_Check(obj))
g_value_set_ulong(&value, PyLong_AsUnsignedLong(obj));
break;

case G_TYPE_FLOAT:
if (PyFloat_Check(obj))
g_value_set_float(&value, PyFloat_AsDouble(obj));
break;

case G_TYPE_DOUBLE:
if (PyFloat_Check(obj))
g_value_set_double(&value, PyFloat_AsDouble(obj));
break;

default:
if (PyString_Check(obj))
g_value_set_string(&value, PyString_AsString(obj));
break;
}
gtk_list_store_set_value(GTK_LIST_STORE(model), &iter, i, &value);
}

//Py_XDECREF(args);
return FALSE;
}

void
etc_main_win_add_trans(PyObject *args)
{
//Py_INCREF(args);
g_idle_add(_etc_main_win_add_trans, args);
}

我的mlterm配置

~/.mlterm/main

use_transbg = true
wall_picture =
scrollbar_mode = none
brightness = 100
contrast = 100
gamma = 100
scrollbar_view_name = mozmodern
use_anti_alias = true
use_variable_column_width = false

~/.mlterm/aafont

ISO10646_UCS2_1=Bitstream Vera Sans Mono-iso10646-1;
ISO10646_UCS2_1_BIWIDTH=SimSun-iso10646-1;

~/.mlterm/termcap

xterm|mlterm:kD=\E[3~:kb=^?

程序库的autotools脚本

1、configure.ac的不同

AC_INIT()
AM_INIT_AUTOMAKE(lclibs, 0.1)
AM_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC

# 库与应用程序不一样,需要libtool支持
# 一般不用intltool,所以没有AC_PROG_INTLTOOL
AC_PROG_LIBTOOL

# Checks for libraries.

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([arpa/inet.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/socket.h unistd.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_C_INLINE
AC_TYPE_SIZE_T

# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([atexit bzero gethostbyname inet_ntoa memset socket strcasecmp strchr strdup strpbrk strspn strstr])

AC_OUTPUT([
Makefile
src/Makefile
])


2、src/Makefile.am的不同

lib_LTLIBRARIES = liblclibs.la
liblclibs_la_SOURCES = \
utils.c\
stack.c\
sockets_wrapper.c\
queue.c\
packages.c\
inifiles.c\
hash.c

lclibsincludedir = $(includedir)/lclibs
lclibsinclude_HEADERS = \
lclibs.h\
utils.h\
threads_wrapper.h\
stack.h\
sockets_wrapper.h\
queue.h\
packages.h\
inifiles.h\
hash.h

autotools实践

1、准备好目录树(一般情况下请将源码扔/src目录)

2、运行autoscan && mv configure.scan configure.ac && rm -f autoscan*

修改configure.ac,去掉AC_INIT后面括号里的东西,增加AM_INIT_AUTOMAKE(你的包名, 包版本号),并将AC_CONFIG_HEADER改为AM_CONFIG_HEADER,删除掉AC_CONFIG_FILES,改AC_OUTPUT为AC_OUTPUT(Makefile src/Makefile po/Makefile.in),用AC_CONFIG_FILES在automake时会报错。

3、注意加上你程序中用到的库和头文件检测语句,看起来应如下所示:

AC_INIT()
AM_INIT_AUTOMAKE(dsrc, 0.1)
AM_CONFIG_HEADER(config.h)

# Checks for programs.
AC_PROG_CC
AC_PROG_INTLTOOL

# Checks for libraries.
# PKG_CHECK_MODULES可直接在Makefile.am中引用$(xxx_CFLAGS)和$(xxx_LIBS)
PKG_CHECK_MODULES(xml2, [xml2])
PKG_CHECK_MODULES(openssl, [openssl])

AC_CHECK_LIB([pthread], [main])

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([arpa/inet.h libintl.h locale.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST
AC_TYPE_SIZE_T

# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([inet_ntoa memset setlocale socket strcasecmp strncasecmp])

# gettext
GETTEXT_PACKAGE=dsrc
AC_SUBST(GETTEXT_PACKAGE)

ALL_LINGUAS="zh_CN"
AM_GLIB_GNU_GETTEXT

AC_OUTPUT([
Makefile
src/Makefile
po/Makefile.in
])

4、准备/Makefile.am

SUBDIRS = src po

dsrcdocdir = ${prefix}/doc/dsrc
dsrcdoc_DATA = \
README\
COPYING\
AUTHORS\
ChangeLog\
INSTALL\
NEWS

EXTRA_DIST = $(dsrcdoc_DATA)

其实这个内容非常简单,一看就明白了,上面所列的EXTRA_DIST为需要安装到/usr/share/doc下的东西,如果你还有其它文档可以加到这个列表中

5、准备src/Makefile.am

INCLUDES = -DLOCALEDIR=\""$(prefix)/share/locale"\"
AM_CFLAGS = -DDEBUG -g $(xml2_CFLAGS)
LIBS = $(xml2_LIBS) $(openssl_LIBS) -lpthread
# AM_LDFLAGS =

bin_PROGRAMS = dsrc
dsrc_SOURCES = main.c security.c XML_utils.c DSRC_handler.c DSRC_builder.c
# dsrc_CFLAGS =
# dsrc_LDFLAGS =

如果会生成多个执行文件可为bin_PROGRAMS增加文件名即可,以空格隔开,下面对每个执行文件需要的源文件定义为xxx_SOURCES即可,如果某执行文件编译参数以及链接参数也可以单独设置:xxx_CFLAGS、xxx_LDFLAGS,不过我建议库文件链接参数直接在configure.ac里AC_CHECK_LIB了事,来得方便点

6、创建automake需要的一些文件

touch AUTHORS NEWS README INSTALL ChangeLog

7、心动从现在开始

aclocal
autoconf
intltoolize
autoheader
automake --add-missing --gnu

8、正事要紧

给自己的程序添加gettext支持,关于gettext的详细介绍可参考其它文章,一般就是以下套路

#include <libintl.h>
#include <locale.h>

#define _(string) gettext(string)
#define N_(string) string

void
i18n_init(void)
{
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
textdomain(GETTEXT_PACKAGE);
}

通常程序在入口处就调用i18n_init(),在显示常量字符串的时候一般如fprintf(stdout, _("Hello World\n"));

9、创建po文件

创建并进入/po目录,创建POTFILES.in,内容为你需要提取源码中有需要翻译的字符串的源文件列表,每行一个文件,然后执行intltool-update --pot产生pot文件,如果将来你在维护代码中代码有变化或者在POTFILES.in中新增了源文件,可用intltool-update --maintain更新。

有了pot文件,现在需要产生各种语言的po文件,如msginit --locale=zh_CN产生中文po文件,如将来源文件有变化可以intltool-update zh_CN更新zh_CN.po,接下来翻译的工作就是你自己来做了。

要生成特定语言的mo文件,需要维护configure.ac中的ALL_LINGUAS,以空格分隔就可以了

10、测试

./configure --prefix=/usr
make
sudo make install
LANG="zh_CN.UTF-8" xxx

libxml2尝试

etc项目中实现一个http server,刚好用到大量的XML,每次处理的数据包XML数据相对较小,先尝试了expat,SAX解析方式确实痛苦,需要自己维护状态树。libxml2相对功能比较全,SAX、DOM解析方式都有实现,而且还有XPath、XLink等实现,甚至连HTMLparser都有了,下面是我这次用到的一些API:

1、解析XML文档DOM树(参考parser.h)

xmlDocPtr doc = xmlParseDoc((const xmlChar *)xml_data);
xmlNodePtr node = xmlDocGetRootElement(doc);
然后node就可以在DOM树里面漫游了,当然xmlParseDoc之后理所当然应该检查doc是否为空意即解析是否成功,结束之后记得xmlFreeDoc(doc);如果需要解析xml文件,则使用xmlParseFile(const xmlChar *)"test.xml")。

2、常见操作(参考tree.h)

得到一个节点的名称:node->name^_^
得到一个节点的内容:xmlChar *value = xmlNodeGetContent(node)返回值value应该使用xmlFree(value)释放内存
设置一个节点的内容:xmlNodeSetContent(node, (const xmlChar *)"test")
得到一个节点的某属性值:xmlChar *value = xmlGetProp(node, (const xmlChar *)"prop1"),返回值需要xmlFree(value)释放内存
设置一个节点的某属性值:xmlSetProp(node, (const xmlChar *)"prop1", (const xmlChar *)"v1")

3、新建XML文档(参考tree.h)

xmlDocPtr doc = xmlNewDoc("1.0"),其中1.0是版本号
doc->children = xmlNewDocNode(doc, NULL, (const xmlChar *)"stream", NULL)创建根节点,具体函数原型可参考其API-Manual
xmlNodePtr node = xmlNewChild(doc->children, NULL, (const xmlChar *)"dsrc_frame", NULL),用xmlNewChild可生成某节点的子节点
xmlDocDumpFormatMemory(doc, buf, &result, 1),用来保存生成的xml文档到内存中,此系列函数还包括:xmlDocDump、xmlDocDumpFormatMemoryEnc、xmlDocDumpMemory、xmlDocDumpMemoryEnc、xmlDocFormatDump

4、XPath操作(参考xpath.h)(XPath可参考w3c文档http://www.w3.org/TR/xpath)

有时候对一个XML文档我们可能只关心其中某一个或某几个特定的Element的值或其属性,如果漫游DOM树将是很痛苦也很无聊的事,XPath可以非常方便地得到你想的Element。下面是一个自定义函数:

xmlXPathObjectPtr
get_nodeset(xmlDocPtr doc, const xmlChar *xpath)
{
xmlXPathContextPtr context;
xmlXPathObjectPtr result;

context = xmlXPathNewContext(doc);
if (context == NULL)
return NULL;

result = xmlXPathEvalExpression(xpath, context);
xmlXPathFreeContext(context);
if (result == NULL)
return NULL;
if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
xmlXPathFreeObject(result);
return NULL;
}

return result;
}

下面是调用代码:

xmlXPathObjectPtr app_result = get_nodeset(doc, (const xmlChar *)"/stream/dsrc_frame/tapdu/application[1]/param");
if (app_result == NULL)
return 0;

xmlNodeSetPtr nodeset = app_result->nodesetval;
xmlChar *value = xmlNodeGetContent(nodeset->nodeTab[0]);
if (value == NULL)
goto ret;

printf("%s\n", value);

xmlXPathFreeObject(app_result);
xmlFree(value);

查看导出符号

用nm即可,共享库.so加一个-D参数,其它参数可参考help

wget整站下载

wget -r -p -np -k http://www.gtk.org/tutorial/

-r, --recursive specify recursive download.
-k, --convert-links make links in downloaded HTML point to local files.
-p, --page-requisites get all images, etc. needed to display HTML page.
-np, --no-parent don't ascend to the parent directory.

一个简单的awk应用

有两个文件:
一个文件是两列,第一列是ID,第二列是ID对应的经验植。
第2个文件是等级,3列,第一列是等级,第2列是经验值开始值,第3列是经验值结束值,就是2,3列是等级经验范围。
现在要求查出第一个文件每行对应等级,并输出。

#!/bin/awk -f

BEGIN {
i = 1
while((getline < "level.txt") > 0) {
level[i] = $1
min[i] = $2
max[i] = $3
i++
}
line = i
}

{
for(j = 1; j <= line; j++) {
if($2 >= min[j] && $2 < max[j]) printf "%s,%s\n",$1,level[j]
}
}

linux裁减实践

1、编译busybox,我通常会选择Busybox Settings/General Configuration中的前面6项,Installation Options中的Don't use /usr,下面的Applets我基本上都选,反正也不大,Init Utilities中我去掉了两个与debugging有关的选项,其它也没什么了,看着选择自己需要的就行了。

2、如果你的busybox编译得没什么问题不需要再调整接下来的工作你可以在_install目录里面做,否则我觉得最好还是挂载好你将要使用的设备,将_install下的所有文件和目录复制到设备上去。mkdir -p boot/grub root dev etc/init.d lib proc tmp var/lib/misc var/lock var/log var/tmp usr/bin usr/lib && chmod 1777 tmp var/tmp。

3、裁减kernel,通常裁减主要用于一个特定的应用,设备都是指定的,可以针对相应的硬件配置只选择自己需要的驱动,裁减的linux应用通常也比较单一,因此很多功能都可以不选择。复制kernel文件vmlinuz到boot目录,如果用grub的话可将grub的stage1和stage2两个文件复制boot/grub中,编辑好grub.conf安装grub即可。

4、要复制的/dev下的一些设备文件:console、core、fd0、null、ptmx、pts、ram*、random、stderr、stdin、stdout、rd、fd、tty、tty0~tty9、urandom、vcs、zero、hda*(如果用的是ide接口硬盘)、pty*,如果要使用X还要复制agppart、misc、log、mem、input。复制方法用cp -avp /dev/xxx

5、复制母系统的/etc/group、passwd、shadow到etc目录,删除掉不需要的组和用户即可。

6、配置/etc/fstab,至少有类似以下这样的项:
/dev/hda11 / reiserfs defaults 0 0
proc /proc proc defaults 0 0
none /dev/pts devpts gid=5,mode=620 0 0

7、配置/etc/inittab:
::sysinit:/etc/init.d/rcS

::respawn:/sbin/getty 38400 tty1
tty2::respawn:/sbin/getty 38400 tty2
tty3::respawn:/sbin/getty 38400 tty3
tty4::respawn:/sbin/getty 38400 tty4

::restart:/sbin/init

::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

8、编辑/etc/issue,如Welcome to MyLinux release 0.1 on \l

9、编辑/etc/init.d/rcS:
#!/bin/sh

/bin/mount -a
/bin/mount -o remount,rw /

echo
echo
echo
echo -en "\t\t\tWelcome to \\033[0;32mMyLinux\\033[0;39m release 0.1\n"

hostname MyLinux
ifconfig lo up
ifconfig eth0 192.168.4.240 netmask 255.255.255.0 up
route add default gw 192.168.4.1
syslogd

10、复制必要的库文件,当然在这儿busybox也可以静态编译链接,但你的应用应该也要用到库文件的,都静态编译链接还是挺浪费空间的,所以我一般并不使用静态编译链接。现在复制必要的库文件到lib目录,可以使用ldd查看某个elf文件需要使用哪些共享库。

基本上就这些步骤,X的配置也很简单,推荐使用gentoo,用gentoo裁减linux非常方便,可以简单地用quickpkg打包你想要的,然后解压到目标设备上,然后删掉不需要的东西即可。现在可以启动一下试试了。。。

kernel的LXR页面

1.0以下版本代码:http://www.oldlinux.org/lxr/http_cn/source/

1.0以上版本代码:http://lxr.linux.no/source/

常用的firefox扩展

Fasterfox - 据说可以提速,我倒没有感觉出来。
Tab Mix Plus - 给标签页上加上了关闭按扭,这是我最喜欢的,其它功能对我似乎没用。
Wizz RSS News Reader - 这个也不错,省去了我安装Liferea。
FireFTP - 感觉这个插件比gftp还好用。
JSView - 可以直接从它这儿得到你喜欢的网页的js和css,对web开发挺有用的。
Gmail Manager - 管理gmail邮箱方便,有了它我几乎不用evolution。
Greasemonkey - 一个强大的脚本管理工具,不过我用它几乎只是为了上btchina.net。
Google Browser Sync - 将Firefox中的浏览历史、收藏夹之类的东西同步到你的google帐号去,这个功能很有用,尤其是重装系统把/home下的东西删掉了。
IGoogle Sidebar - 以侧栏方式查看你的google自定义主页上的个性化内容。

Noia - 不算是扩展,是Firefox里我最喜欢用的一个主题。

以link方式管理eclipse扩展

以link方式管理eclipse扩展非常方便,可将安装的第三方插件统一放一个目录,在eclipse目录中创建一个links目录,创建一个.link文件加入path=/your/extensions/path即可,如我的eclipse第三方插件全在/data/soft/eclipse,/usr/lib/eclipse-3.2/links/myeclipse.link内容为:path=/data/soft,eclipse目录在我的gentoo中是/usr/lib/eclipse-3.2,其它发行版可能不一样。

串口设置

给一个应用裁减了4M左右的linux,对于串口的内核配置很简单,内核启动时明明发现了四个串口但是只正确映射了串口一和串口二的设备文件/dev/ttyS0和/dev/ttyS1,琢磨了很久怀疑是因为主板的设计问题,COM3和COM4的地址不是标准的0x2f8和0x2e8,而是0x2f0和0x2e0,最后手动设置了一下大功告成:

setserial /dev/ttyS2 port 0x2f0 irq 7 uart 8250
setserial /dev/ttyS3 port 0x2e0 irq 9 uart 8250

修改MAC地址

ifconfig eth0 down
ifconfig eth0 hw ether 00:0A:EB:2A:88:2D
ifconfig eth0 up

善用backtrace

程序在得到一个Segmentation fault这样的错误信息毫无保留地就跳出来了,遇到这样的问题让人很痛苦,查找问题不亚于你N多天辛苦劳累编写代码的难度。那么有没有更好的方法可以在产生SIGSEGV信号的时候得到调试可用的信息呢?看看下面的例程吧!

sigsegv.h

#ifndef __sigsegv_h__
#define __sigsegv_h__

#ifdef __cplusplus
extern "C" {
#endif

int setup_sigsegv();

#ifdef __cplusplus
}
#endif

#endif /* __sigsegv_h__ */

sigsegv.c

#define _GNU_SOURCE
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <execinfo.h>
#ifndef NO_CPP_DEMANGLE
#include <cxxabi.h>
#endif

#if defined(REG_RIP)
# define SIGSEGV_STACK_IA64
# define REGFORMAT "%016lx"
#elif defined(REG_EIP)
# define SIGSEGV_STACK_X86
# define REGFORMAT "%08x"
#else
# define SIGSEGV_STACK_GENERIC
# define REGFORMAT "%x"
#endif

static void signal_segv(int signum, siginfo_t* info, void*ptr) {
static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};

size_t i;
ucontext_t *ucontext = (ucontext_t*)ptr;

#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
int f = 0;
Dl_info dlinfo;
void **bp = 0;
void *ip = 0;
#else
void *bt[20];
char **strings;
size_t sz;
#endif

fprintf(stderr, "Segmentation Fault!\n");
fprintf(stderr, "info.si_signo = %d\n", signum);
fprintf(stderr, "info.si_errno = %d\n", info->si_errno);
fprintf(stderr, "info.si_code = %d (%s)\n", info->si_code, si_codes[info->si_code]);
fprintf(stderr, "info.si_addr = %p\n", info->si_addr);
for(i = 0; i < NGREG; i++)
fprintf(stderr, "reg[%02d] = 0x" REGFORMAT "\n", i, ucontext->uc_mcontext.gregs[i]);

#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
# if defined(SIGSEGV_STACK_IA64)
ip = (void*)ucontext->uc_mcontext.gregs[REG_RIP];
bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP];
# elif defined(SIGSEGV_STACK_X86)
ip = (void*)ucontext->uc_mcontext.gregs[REG_EIP];
bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP];
# endif

fprintf(stderr, "Stack trace:\n");
while(bp && ip) {
if(!dladdr(ip, &dlinfo))
break;

const char *symname = dlinfo.dli_sname;
#ifndef NO_CPP_DEMANGLE
int status;
char *tmp = __cxa_demangle(symname, NULL, 0, &status);

if(status == 0 && tmp)
symname = tmp;
#endif

fprintf(stderr, "% 2d: %p <%s+%u> (%s)\n",
++f,
ip,
symname,
(unsigned)(ip - dlinfo.dli_saddr),
dlinfo.dli_fname);

#ifndef NO_CPP_DEMANGLE
if(tmp)
free(tmp);
#endif

if(dlinfo.dli_sname && !strcmp(dlinfo.dli_sname, "main"))
break;

ip = bp[1];
bp = (void**)bp[0];
}
#else
fprintf(stderr, "Stack trace (non-dedicated):\n");
sz = backtrace(bt, 20);
strings = backtrace_symbols(bt, sz);

for(i = 0; i < sz; ++i)
fprintf(stderr, "%s\n", strings[i]);
#endif
fprintf(stderr, "End of stack trace\n");
exit (-1);
}

int setup_sigsegv() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_sigaction = signal_segv;
action.sa_flags = SA_SIGINFO;
if(sigaction(SIGSEGV, &action, NULL) < 0) {
perror("sigaction");
return 0;
}

return 1;
}

#ifndef SIGSEGV_NO_AUTO_INIT
static void __attribute((constructor)) init(void) {
setup_sigsegv();
}
#endif

main.c

#include "sigsegv.h"
#include <string.h>

int die() {
char *err = NULL;
strcpy(err, "gonner");
return 0;
}

int main() {
return die();
}

下面来编译上面的main.c程序看看将会产生什么样的信息呢,不过要注意的就是如果要在你的程序里引用sigsegv.h、sigsegv.c得到堆栈信息的话记得加上-rdynamic -ldl参数。

/data/codes/c/test/backtraces $ gcc -o test -rdynamic -ldl -ggdb -g sigsegv.c main.c
/data/codes/c/test/backtraces $ ./test
Segmentation Fault!
info.si_signo = 11
info.si_errno = 0
info.si_code = 1 (SEGV_MAPERR)
info.si_addr = (nil)
reg[00] = 0x00000033
reg[01] = 0x00000000
reg[02] = 0xc010007b
reg[03] = 0x0000007b
reg[04] = 0x00000000
reg[05] = 0xb7fc8ca0
reg[06] = 0xbff04c2c
reg[07] = 0xbff04c1c
reg[08] = 0xb7f8cff4
reg[09] = 0x00000001
reg[10] = 0xbff04c50
reg[11] = 0x00000000
reg[12] = 0x0000000e
reg[13] = 0x00000006
reg[14] = 0x080489ec
reg[15] = 0x00000073
reg[16] = 0x00010282
reg[17] = 0xbff04c1c
reg[18] = 0x0000007b
Stack trace:
1: 0x80489ec <die+16> (/data/codes/c/test/backtraces/test)
2: 0x8048a16 <main+19> (/data/codes/c/test/backtraces/test)
End of stack trace
/data/codes/c/test/backtraces $

下面用gdb来看看出错的地方左右的代码:

/data/codes/c/test/backtraces $ gdb ./test
gdb> disassemble die+16
Dump of assembler code for function die:
0x080489dc <die+0>: push %ebp
0x080489dd <die+1>: mov %esp,%ebp
0x080489df <die+3>: sub $0x10,%esp
0x080489e2 <die+6>: movl $0x0,0xfffffffc(%ebp)
0x080489e9 <die+13>: mov 0xfffffffc(%ebp),%eax
0x080489ec <die+16>: movl $0x6e6e6f67,(%eax)
0x080489f2 <die+22>: movw $0x7265,0x4(%eax)
0x080489f8 <die+28>: movb $0x0,0x6(%eax)
0x080489fc <die+32>: mov $0x0,%eax
0x08048a01 <die+37>: leave
0x08048a02 <die+38>: ret
End of assembler dump.
gdb>

也可以直接break *die+16进行调试,看看在出错之前的堆栈情况,那么下面我们再来看看代码问题到底出在什么地方了。

/data/codes/c/test/backtraces $ gdb ./test
gdb> break *die+16
Breakpoint 1 at 0x80489f2: file main.c, line 6.
gdb> list *die+16
0x80489f2 is in die (main.c:6).
1 #include "sigsegv.h"
2 #include <string.h>
3
4 int die() {
5 char *err = NULL;
6 strcpy(err, "gonner");
7 return 0;
8 }
9
10 int main() {
gdb>

现在看看定位错误将会多么方便,上面的调试指令中list之前break不是必须的,只是让你可以看到break其实就已经指出了哪一行代码导致Segmentation fault了。如果你要发布你的程序你一般会为了减少体积不会附带调试信息的(也就是不加-ggdb -g参数),不过没关系,你一样可以得到上面stack-trace信息,然后你调试之前只要加上调试信息即可。

how to hack gentoo livecd

下午花了半个多小时hack了一把gentoo livecd 2006.1,给一个项目裁减好的linux系统制作安装光盘,省了不少时间,gentoo的livecd里关键的就两个文件:image.squashfs和isolinux/gentoo.igz,前者基本上就是一个完整的gentoo base system,后者是一个initramfs,临时的根文件系统,用于加载光盘并让前者正确地接管系统,下面开始说正题吧。。。

1、挂载光盘文件mount -o loop /data/soft/livecd/livecd.iso /mnt/cdrom,/mnt/cdrom下现在就是livecd光盘的完整内容。另外建立一个目录/mnt/mycd把光盘里的文件全部复制过来用于制作光盘。

2、挂载image.squashfs:mount -o loop /mnt/cdrom/image.squashfs /mnt/gentoo(需要你的内核支持squashfs文件系统),全部复制到另一个目录进行hack吧,复制的时候记得加上-pr参数。这个是重头戏,进去好好研究研究,放手去修改配置添加东西吧,你甚至可以把你的开发环境、portage等等全搞进去。搞好了直接mksquashfs * /mnt/mycd/image.squashfs就可以了,不过在mksquashfs之前你得把原文件删掉。

3、复制isolinux/gentoo.igz到你另外一个目录,mv gentoo.igz gentoo.gz && gunzip gentoo.gz,然后正确地解压gentoo文件,得到一个简单的临时用根文件系统,下面这个脚本可以很方便地进行解压,用下面的脚本./unigz.sh gentoo就生成了gentoo.dir目录,执行脚本之前确认一下你的系统里有没有安装File-Slurp包:

#!/usr/bin/perl -w
#

use strict;

use File::Slurp qw(slurp);

my $ifile = slurp($ARGV[0], binmode=> ':raw');
my $newfile;
my $i=1;
my @newfiles = split(/TRAILER!!!/,$ifile);
`mkdir $ARGV[0].dir`;

foreach $newfile (@newfiles)
{
open F, "> ./$ARGV[0].dir/$i";
print F $newfile;
print F "TRAILER!!!\0";
close F;
`cd $ARGV[0].dir; cpio -i -H newc < $i; rm $i; cd ..`;
$i++;
}

进入gentoo.dir目录主要看看init和etc下两个脚本文件initrd.defaults和initrd.scripts,加上自己想要执行的东西即可,当然如果你想大刀阔斧地hack也可以,完了之后在gentoo.dir目录下执行find . -print | cpio --quiet -o -H newc | gzip -9 > ../gentoo.igz。

4、一切就绪之后制作iso文件:
cd /mnt/mycd
mkisofs -v -R -J -P "cd" -p "cd" -V "xxx_installer" -A "xxx-install-cd" -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -o /data/download/etc.iso .

mysql字符集问题

以前一个web项目采用mysql 4.0.28,公司大部分开发人员用的是windows,java程序里存取数据采用的是GBK编码,当时建库的时候也没有指定字符集编码,因此升级到4.1之后原来的程序取出来的数据中文全成了乱码,mysql 4.1变化比较大,要兼容原来的程序及数据库服务器端设置/etc/mysql/my.cnf须做以下修改:

[mysqld]
default-character-set = gbk
skip-character-set-client-handshake

Makefile奇技淫巧

自从用了autotools很久不曾有过编写Makefile的经历了,这两天维护客户以前让别的公司给做的一个项目中的代码,写了个Makefile感觉挺不错。。。

CC = g++

GTK_CFLAGS = $(shell gdk-pixbuf-config --cflags)
GTK_LIBS = $(shell gdk-pixbuf-config --libs) $(shell pkg-config --libs gthread)

MYSQL_CFLAGS = $(shell mysql_config --cflags)
MYSQL_LIBS = $(shell mysql_config --libmysqld-libs)

CFLAGS = $(GTK_CFLAGS) $(MYSQL_CFLAGS) -I/usr/include/lclibs -DMYSQL_EMBED -DETC_THREAD
LIBS = $(GTK_LIBS) $(MYSQL_LIBS) -L./libs -ldl -rdynamic -llclibs -ldsrc -lgts

SRC_PATH = src
OBJ_PATH = obj

SOURCES = $(wildcard $(SRC_PATH)/*.c)
OBJECTS = $(patsubst $(SRC_PATH)/%.c, $(OBJ_PATH)/%.o, $(SOURCES))
DEPENDS = $(patsubst $(SRC_PATH)/%.c, $(OBJ_PATH)/%.d, $(SOURCES))

all: hwetc

$(DEPENDS): $(OBJ_PATH)/%.d: $(SRC_PATH)/%.c
$(CC) -MM $(CFLAGS) $< > $@; \
sed -i 's,\($*\)\.o[ :]*,$(OBJ_PATH)/\1.o $@ : ,g' $@

include $(DEPENDS)

hwetc: $(OBJECTS)
$(CC) $(OBJECTS) $(LIBS) -o $@

$(OBJECTS):
$(CC) -c $(CFLAGS) $< -o $@

.PHONY: clean
clean:
rm -f hwetc $(OBJECTS) $(DEPENDS)

mplayer使用技巧

在Linux下视频文件我一般都关联使用mplayer打开,~/.mplayer/config配置就显得非常重要了,先看看我的config文件内容吧:

# Write your default config options here!

ao=alsa
vo=xv
stop-xscreensaver=yes
monitoraspect=1280:800
font=/usr/share/fonts/zh_CN/simsun.ttf
subcp=GB18030
subfont-autoscale=2
subfont-text-scale=4
sub-fuzziness=1
cache=8192
ontop=yes

常见的ao和vo参考man pages即可。

如果你在使用宽屏如1280x800、1280x768之类的分辨率时,你会发现按默认配置播放时图像变形了(不过mplayer的GUI版本 gmplayer好象能自适应这个,但我不喜欢gmplayer另外再打开着一个控制窗口且非常难用,因此纯洁的mplayer就成了我的首选), monitoraspect参数配置方法就使用你的分辨率做比率就可以了。

字幕问题又来了,在播放带字幕文件的DVDRip格式的电影时按默认配置是不是又乱码了,那么接下来的几个参数就可以让你舒服地看到汉字,font配置字体文件的路径,字幕文件一般是由国人在windows下制作的,所以一般文件内容编码都是GBK(CP936),所以你可以设置成GBK或CP936,关于字符集编码的名称你可以使用iconv -l看看你的系统都支持些什么

到此为止你应该可以看到汉字了,不过字体有点太大,多行字体可能有点影响你看电影了,subfont-autoscale参数用来设置按什么方法来进行自动缩放(0-不自动缩放,1 -按电影高度缩放,2-按电影宽度缩放,3-按电影对角线缩放(默认值)),subfont-text-scale参数用来设置字幕文本的自动缩放系数 (屏幕尺寸的百分比),值范围为0~100,默认值为5。

mplayer是可以自动加载字幕文件的,但是这只是在字幕文件名跟媒体文件名相同时才起作用(后缀不同)例如这样的:

单刀直入
单刀直入-CD1.avi
单刀直入-CD1.srt
单刀直入-CD2.avi
单刀直入-CD2.srt

这样的就能自动加载字幕,通过j键切换。但像这种的就不行了:

单刀直入
单刀直入-CD1.avi
单刀直入-CD1.Chs.srt
单刀直入-CD1.Eng.srt
单刀直入-CD2.avi
单刀直入-CD2.Chs.srt
单刀直入-CD2.Eng.srt

这种情况下要使用参数具体指定一个字幕文件,其实mplayer还有一个聪明的参数可以使用的,就是
-sub-fuzziness
Adjust matching fuzziness when searching for subtitles:
0 exact match
1 load all subs containing movie name
2 load all subs in the current directory
可以使用-sub-fuzziness 1来让mplayer加载目录下的所有与电影文件相关的字幕文件,播放时可以通过j键切换。

最后这个cache主要是设置播放流媒体文件时使用的缓存大小,可以根据你机器的配置进行调整。