Linux下的console和terminal

consoleterminal是很容易让人迷惑的两个概念。根据wikipedia上的定义,小型计算机的console应该就是键盘加显示器;而terminal则是输入数据进去,和显示数据来源的设备,通常是一个计算机系统。

Linux下的console除了真实的硬件设备外,还有virtual console,也就是你按alt+Fn或者alt+ctrl+Fn切换到的东西。所谓虚拟就是这些console共享同一个真实的设备,只有一个活动的console才显示在前面。这些console对应的设备是:/dev/ttyN,其中1 ≤ N ≤ 63。而/dev/tty0则是指向当前的terminal;/dev/console是指向当前console,但它现在并不是对/dev/tty0的符号链接。更多可参考console(4)。

/dev/tty是另一个特殊设备,它指向控制终端(controlling terminal)。如果某个进程的控制终端是/dev/tty3,那么/dev/tty就指向/dev/tty3了。控制终端是什么概念?它是一个进程的某个属性,是依附带该进程上的终端。比如我们在某个终端下输入ctrl+C,那么它控制的前台进程就会收到SIGINT,而后台进程会收到SIGTTIN或SIGTTOU ,如果它们读写该终端的话。被同一个终端控制的所有进程被称为一个会话(session),会话的领导就是创建改会话的进程,其子进程也会被该终端控制。所以,1) 需要交互的命令行程序通常会从/dev/tty这个设备进行读写;2) Unix后台进程都需要在fork之后调用setsid(2),3) 需要加O_NOCTTY,当你open一个可能是终端的文件时。

另外,想要确定/dev/tty究竟是指向哪个设备,可以调用TIOCCONS ioctl。参考tty(4)。

下面是另外一个概念——伪终端(pseudo-terminal),根据pty(7)的介绍,伪终端一对虚拟设备,提供端到端双向通信的通路,一端称为master,另一端称为slave。在slave那端看到的和在真实终端看到的效果一样。所以伪终端一般被ssh等网络登录程序使用。历史上,有两套伪终端接口,一个是Unix 98伪终端,另一个是BSD伪终端。

BSD提供的接口很简单:/dev/pty[p-za-e][0-9a-f] 是master; /dev/tty[p-za-e][0-9a-f] 是slave,它们都是配好对的。这样看起来很简单,但对程序员来说不容易,要找到一个合适的终端需要一个个从头尝试。所以这种方式已经被遗弃。而Unix 98伪终端则完全不同,它始终使用/dev/ptmx作为master复制设备,然后在每次打开它的时候才得到一个master设备的fd,同时在/dev/pts/目录下得到一个slave设备。这样编程就相对容易了,根据pts(4)介绍,需要三个新的API: ptsname(3),grantpt(3)和unlockpt(3)。我们可以通过一个实例看一下如何使用:

(以下代码摘自netvirt)
[c]
char mptname = “/dev/ptmx”; / master pseudo-tty device */
//…
void
getmaster()
{
struct stat stb;

if ((master = open(mptname, O_RDWR)) >= 0) { /* a pseudo-tty is free */
    (void) ioctl(0, TCGETS, (char *)&b);
    (void) ioctl(0, TIOCGWINSZ, (char *)&size);
    return;
} else {                /* out of pseudo-tty's */
    perror(mptname);
    fprintf(stderr, gettext("Out of pseudo-tty'sn"));
    fail();
}

}

void
getslave()
{
char slavename; / name of slave pseudo-tty */

grantpt(master);        /* change permissions of slave */
unlockpt(master);            /* unlock slave */
slavename = ptsname(master);        /* get name of slave */
slave = open(slavename, O_RDWR);    /* open slave */
if (slave < 0) {            /* error on opening slave */
    perror(slavename);
    fail();
}
ioctl(slave, I_PUSH, "ptem");    /* push pt hw emulation module */
ioctl(slave, I_PUSH, "ldterm");        /* push line discipline */

(void) ioctl(slave, TCSETSF, (char *)&b);
(void) ioctl(slave, TIOCSWINSZ, (char *)&size);

}
[/c]
然后我们再来看一下glibc中对ptsname(3)的实现:

(源文件sysdeps/unix/sysv/linux/ptsname.c)
[c]

define _PATH_DEVPTS "/dev/pts/"

char *
ptsname (int fd)
{
return __ptsname_r (fd, buffer, sizeof (buffer)) != 0 ? NULL : buffer;
}

int
__ptsname_r (int fd, char *buf, size_t buflen)
{

if (ioctl (fd, TIOCGPTN, &ptyno) == 0)

numbuf[sizeof (numbuf) - 1] = '';
p = _itoa_word (ptyno, &numbuf[sizeof (numbuf) - 1], 10, 0);

memcpy (
stpcpy (buf, devpts), p, &numbuf[sizeof (numbuf)] - p);
[/c]

我们可以看出,实际上是调用ioctl TIOCGPTN,通过内核,而Linux内核又是通过devpts这种文件系统实现了这一切:

$ mount | grep devpts
devpts on /dev/pts type devpts (rw,gid=5,mode=620)

这样我们终于把一切搞清楚了。:-)

Make pthread suck less

pthread的API一直让我感到头疼,种类多而且名字又长,今天下决心把它们理清楚。

仅从名字上来看,pthread的API可以分为这么几类:

pthread_XXX:
此类API一般是对thread本身进行管理的。共包括如下API:

pthread_atfork()
pthread_create()
pthread_exit()
pthread_cancel()
pthread_join()
pthread_once()
pthread_self()
pthread_equal()
pthread_kill()
pthread_detach()
pthread_yeild()
pthread_sigmask()
pthread_key_create()
pthread_key_delete()
pthread_cleanup_push()
pthread_cleanup_pop()
pthread_testcancel()

它还可以分出两个子类,包括pthread_setWWW和pthread_getWWW,其中有:

pthread_getconcurrency()
pthread_getcpuclockid()
pthread_getschedparam()
pthread_getspecific()

pthread_setcancelstate()
pthread_setconcurrency()
pthread_setschedparam()
pthread_setschedprio()
pthread_setspecific()

pthread_attr_YYY:
YYY一般包括:init, destory, setZZZ, getZZZ。此类API是对thread本身的属性进行管理的。共包括如下API:

pthread_attr_destroy()
pthread_attr_getinheritsched()
pthread_attr_getschedparam()
pthread_attr_getschedpolicy()
pthread_attr_getscope()
pthread_attr_getstackaddr()
pthread_attr_getstack()
pthread_attr_init()
pthread_attr_setdetachstate()
pthread_attr_setguardsize()
pthread_attr_setinheritsched()
pthread_attr_setschedparam()
pthread_attr_setschedpolicy()
pthread_attr_setscope()
pthread_attr_setstackaddr()
pthread_attr_setstack()
pthread_attr_setstacksize()

pthread_MMM_XXX:
XXX一般是上面和那个XXX集合类似的操作,但MMM一般是thread的一个工具,比如:mutex,cond等。此类API是对thread的MMM工具进行操作。共包括如下API:

mutex类:
pthread_mutex_init()
pthread_mutex_destroy()
pthread_mutex_lock()
pthread_mutex_unlock()
pthread_mutex_trylock()
pthread_mutex_setprioceiling()
pthread_mutex_getprioceiling()

cond类:
pthread_cond_init()
pthread_cond_destroy()
pthread_cond_signal()
pthread_cond_broadcast()
pthread_cond_wait()
pthread_cond_timedwait()

rwlock类:
pthread_rwlock_destroy()
pthread_rwlock_init()
pthread_rwlock_rdlock()
pthread_rwlock_timedrdlock()
pthread_rwlock_timedwrlock()
pthread_rwlock_tryrdlock()
pthread_rwlock_trywrlock()
pthread_rwlock_unlock()
pthread_rwlock_wrlock()

spin类:
pthread_spin_destroy()
pthread_spin_init()
pthread_spin_lock()
pthread_spin_trylock()
pthread_spin_unlock()

barrier类:
pthread_barrier_destroy()
pthread_barrier_init()
pthread_barrier_wait()

pthread_MMMattr_YYY:
MMM和YYY同上(spin除外),此类API是对MMM工具的属性进行操作。和上面有着密切的关系。共包括如下API:

mutex类:
pthread_mutexattr_destroy()
pthread_mutexattr_getprioceiling()
pthread_mutexattr_getprotocol()
pthread_mutexattr_getpshared()
pthread_mutexattr_gettype()
pthread_mutexattr_init()
pthread_mutexattr_setprioceiling()
pthread_mutexattr_setprotocol()
pthread_mutexattr_setpshared()
pthread_mutexattr_settype()

cond类:
pthread_condattr_destroy()
pthread_condattr_getclock()
pthread_condattr_getpshared()
pthread_condattr_init()
pthread_condattr_setclock()
pthread_condattr_setpshared()

rwlock类:
pthread_rwlockattr_destroy()
pthread_rwlockattr_getpshared()
pthread_rwlockattr_init()
pthread_rwlockattr_setpshared()

barrier类:
pthread_barrierattr_destroy()
pthread_barrierattr_getpshared()
pthread_barrierattr_init()
pthread_barrierattr_setpshared()

这还没完,还有N多的新类型,比如pthread_t,pthread_attr_t,pthread_once_t,以及随之而来的宏。。。我在这就不总结了。

最后,介绍pthread的书籍有:

“PThreads Primer”. Lewis, Bill and Daniel J. Berg. California: Prentice Hall.
“Pthreads Programming”. B. Nichols et al. O’Reilly and Associates.
“Programming With POSIX Threads”. D. Butenhof. Addison Wesley
“Programming With Threads”. S. Kleiman et al. Prentice Hall

查询SNMP OID的程序

网上有个查询的网站,可惜什么结果都查不出来!靠!我实在看不下去了,动手写一个python程序来搞定,不过仍有局限性,那就是只能查询.iso.org子树。。。啥也不说了,上代码!

[python]

!/usr/bin/env python

import os,sys
import string
import re
import urllib2

if name == ‘main‘:

if len(sys.argv) != 2:
    sys.stderr.write("Please provide one OID number or string to lookup.n");
    sys.exit(1)

flag = 0
found = False

r = re.compile('^[0-9\.]+$')
if r.match(sys.argv[1]):
    r = re.compile('^1\.3')
    if r.match(sys.argv[1]):
        flag = 1
    else:
        sys.stderr.write("Please provide the full OID number under .iso.org!n")
        sys.exit(1)

try:
    req = urllib2.Request('http://www.kix.in/plan9/mirror/sources/contrib/gabidiaz/root/lib/ndb/snmp')
    resp = urllib2.urlopen(req)
    oid = resp.readline()
    name = resp.readline()
    while oid and name:
        if flag == 1:
            if oid.find(sys.argv[1]) != -1:
                print name
                found = True
                break
        else:
            n = name.lower().find(sys.argv[1].lower())
            if n != -1:
                print oid
                found = True
                if n+len(sys.argv[1]) < len(name)-1:
                    print name
        oid = resp.readline()
        name = resp.readline()
    if not found:
        print "Not found!"
    sys.exit(0)
except IOError:
    sys.stderr.write("Probably you don't have Internet.n")
    sys.exit(1)

[/python]

ASN.1 octet string转化小脚本

可参考ASN.1关于octet string的介绍。

[bash]

!/bin/bash

i=1;
while(($i<=${#1}))
do
printf "%d" "'$(expr substr $1 $i 1)"
if (($i != ${#1}))
then
echo -n '.'
fi
i=$((i+1))
done
echo
exit 0
[/bash]

上面使用了一个鲜为人知的小技巧,而且不仔细看也不太容易察觉。

此脚本可以这么用(假设此脚本被存为to_string.sh):

$ snmpwalk -v2c -c test 192.168.90.72 .1.3.6.1.4.1.8072.1.3.2.4.1.2.${#STRING}.$(./to_string.sh $STRING).1

最牛的解答

这是我见过的最牛的解答:

我昨天晚上做梦的时候好像把这个事情想清楚了。现在又不记得了。
你简直是太有才了!!!!!!!!!!!!!!!!

这次真的疯了

上个周末真的玩疯了。

因为bruno要去瑞士了,上个周五晚上就算是为他送行了。先是在一个餐馆一起吃饭,critcial links来了很多人。那个餐馆的菜并不是很好,不过谁有在乎呢,大家在一起聊得开心就好。最后,我被灌了四杯葡萄酒,加上我自己那瓶啤酒,结果下来真的有点醉了,不过还好能撑得住。吃完都十二点多了,本以为这就完了,没想到疯狂这才开始……

然后又跟着他们去了另一个酒吧去接着喝,这次我比较聪明,没跟着他们喝那种带酒精的饮料,我叫了两瓶红牛,没想到它效果那么好,喝了一瓶就已经完全清醒了。marco说我喝两瓶忒多了,整个晚上都会睡不着了,我不信。大约坐了一个多小时后我们就去另一个地方,我还在想怎么还非得换另一个地方继续喝,在这里不是一样么?没想到这次去的是舞厅……这时又来一些人,包括几个HR的美女。进了舞厅才发现那真是一个彻底疯狂的地方,里面响着疯狂的音乐,而且挤满了年轻人,闪烁的彩灯,DJ师,调酒师,各种各样的美女……管它呢,我们这些人围成一圈也跳了起来。说实话,跳舞其实并不难,简单地来说就是扭扭屁股,甩甩胳膊,不过想要跳好可真就不容易了。我跟着另一个bruno和marco慢慢地学着跳,感觉真的很疯,不过也感觉真的很轻松!!好久没能这么释放自己了,真的很开心。我们就这样一直玩到舞厅关门,都快凌晨五点了……

“送君千里终需一别”。最后我们不得不和bruno真的道别了,我以后再也听不到我对面这个bruno对着我另一边叫另一个bruno了,再也不能看着他那熟练卷烟的动作说他是cigarette machine了……希望他在瑞士过得更好,一路顺风!

有时候,唯一保持清醒的办法就是发点儿疯。:-)

又见汇编技巧

在 arch/x86/include/asm/uaccess.h 中有这么一段代码:

define __range_not_ok(addr, size)

({
unsigned long flag, roksum;
__chk_user_ptr(addr);
asm(“add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0”
: “=&r” (flag), “=r” (roksum)
: “1” (addr), “g” ((long)(size)),
“rm” (current_thread_info()->addr_limit.seg));
flag;
})

这段汇编写得很有技巧性,充分利用了sbb指令carry flag,值得你仔细体会一番。还有一个关于sbb的技巧:

sbb eax,eax
sbb eax,0FFFFFFFFh

读机器人系列

机器人系列完全可以作为基地系列的一部分。机器人系列的时间跨度虽然大概能够达到1700多年,但故事情节主要集中在中间的不到一百年的时间中,这一点和基地系列截然不同。而且在机器人系列的第一部《我,机器人》中,阿西莫夫使用了他少有的一种叙述方式——通过一个机器人学家的叙述来描述整个故事,通过若干个小故事讲述了机器人的发展情况。当然了,在今天看来,阿西莫夫当时过于乐观了。:-)

毫无疑问,整个机器人系列的主人公是贝莱——这个地球人侦探,他三次出手破解悬案,三次挽救了地球的命运。这正和阿西莫夫的手法:看似不起眼的小故事却可以直接影响大局。而在这三个故事中,机器人和人类的关系也展现得淋漓尽致,机器人如何从被人依赖到被人抛弃,以及人类如何从地球走向整个宇宙。机器人和人类的关系错综复杂,但毫无疑问,有个机器人改变了整个人类的命运,他就是丹尼尔,这个协助贝莱破案的类人机器人,同时,如果你读完基地系列的话你也会知道,他也是后来基地系列中的帝国宰相——丹莫尼茨,他活了上万年,他是连接机器人系列和基地系列唯一的角色。如果我们把机器人系列和基地系列之间的故事也写出来的话,那他注定会是主角。他之所以能够影响整个人类的命运是因为两个原因:一是他领悟出了机器人第零定律,并把它放到第一定律之前来遵守;二是他从另一个机器人——吉斯卡特——那里得到了一个强大的能力——读心术。那个机器人是最早拥有读心术本领的机器人之一(未必是第一个),他其实也影响了整个人类的命运,而且他在临终前留给丹尼尔的话暗示了基地系列的故事发展。

如果说谢顿计划是基地系列的核心,那么机器人三定律则是机器人系列的核心,在机器人系列中,这三大定律一次又一次的被演绎着,多次成为决定故事结局的关键。而到了最后,三大定律被机器人“推翻”,演变成了四大定律,并从此以后开始改变整个人类和宇宙的命运……

纵观整个机器人和基地系列,故事从地球起到地球终,似乎画了一个完美的圆圈。不得不感叹阿西莫夫的本领!

最后值得一提的是:

1) 其实心理史学并不是最先由谢顿提出来的,早在人类开发宇宙的前期,奥罗拉的机器人学家法斯托尔弗就提到了心理历史学(见《曙光中的机器人》的最后);
2)《基地与地球》中提到地球已经因为辐射而被人类遗弃,《机器人与帝国》中很好的解释了这个原因。索拉里亚人的离开成为机器人系列的疑点,吉斯卡特成功地领悟到了这个事件的重要性,这将在最后的《基地与地球》中提到;
3) 阿西莫夫的《苍穹微石》也应该被囊括进系列中来,它像是连接机器人系列和基地系列的纽带,故事恰好发生在它们之间,其中进一步解释了地球辐射的问题。
4) 想读《基地的胜利》,虽然不是阿西莫夫本人写的,但其最后很好地按时间先后顺序总结了整个系列的重大事件。从其介绍来看,此书叙述的情节恰好是弥补阿西莫夫系列中缺少的部分。

(机器人系列的阅读顺序见我上一篇关于基地的介绍。)

整数除法

C似乎没多少疑问,不过当有负数时还是需要特别注意的,因为标准要求是要向0对齐的。所以-5/2会是-2而不是-3。

6.5.5

When integers are divided, the result of the / operator is the algebraic quotient
with any fractional part discarded [Footnote].

Footnote: This is often called “truncation toward zero”
在C89的时候,这个问题还是交由编译器决定的。C99加强了这一点。C为什么会选择这一点?其实原因很简单,因为这么做节省时间:直接舍去小数部分。

Python在这一点上和C不同,因为它还保留了一个int(),这个和C在取整方向上是一致的:Python中的整数除法向负无穷对齐;而int()才是向0对齐的。见PEP238

Note that classic division (and floor division) round towards negative infinity,
while int() rounds towards zero, giving different answers for negative numbers.
不要被迷惑了,在Python里int(-5/2)依旧是-3而不是-2,你知道为什么。:-) 在PEP 238中,Python引入了一个新的运算符://,它始终会向下截取除法的结果(数学上的取整,英文谓之floor),看下面的例子:

>>> -5//2
-3
>>> -5.1/2.0
-2.5499999999999998
>>> -5.1//2.0
-3.0

Perl更不同,它只能通过int()来进行取整,因为普通除法在Perl里得到的结果是浮点数。《Programming Perl》中提到:

You should not use this function for generic rounding, because it truncates towards 0 and because machine representations of floating-point numbers can sometimes produce counterintuitive results.
所以这个也是向0对齐的。

再看ruby,这个似乎很简单,一律是向负无穷对齐的:

$ ruby -e ‘puts -5/2; puts -5.div(2); puts Kernel.Integer(-5/2);’

-3
-3
-3

Bash和C一致:

$ echo $[5/2] && echo $[-5/2]
2
-2

Java似乎也和C一致,不过我未验证。:-)

这里还有一个问题值得思考:为什么这些编程语言要把整数除法和浮点数除法区别对待?对于C,这个问题很简单,因为很久很久以前C并没有浮点数,只有整数,而其它语言似乎是因为受到了这一影响,当然Perl例外。不过,在很多情况下整数除法已经够用,无须打扰浮点数。