12 2009存档

09年最后一天了,列一下一年看的书,当然,很多是以前或小时候看的(比如丁丁历险记),算到一起吧。

作者:董昊 (要转载的同学帮忙把名字和博客链接http://oldblog.donghao.org/uii/带上,多谢了!)

chinaunix上有人提问:linux下进程异常coredump或被信号杀死,打开的文件会自动flush吗
好问题,我们使用2.6.9的内核源代码,分各种情况讨论。

1. 正常退出

如果你用 strace 跟一下最简单的linux命令,比如ls、lsof等,你会发现这些进程退出都会调用系统调用exit_group,那我们就从这个系统调用开始。

[ kernel/exit.c ]
   871 NORET_TYPE void
   872 do_group_exit(int exit_code)
   873 {
   874     BUG_ON(exit_code & 0x80); /* core dumps don't get here */
   875
   876     if (current->signal->group_exit)
   877         exit_code = current->signal->group_exit_code;
   878     else if (!thread_group_empty(current)) {
   879         struct signal_struct *const sig = current->signal;
   880         struct sighand_struct *const sighand = current->sighand;
   881         read_lock(&tasklist_lock);
   882         spin_lock_irq(&sighand->siglock);
   883         if (sig->group_exit)
   884             /* Another thread got here before we took the lock.  */
   885             exit_code = sig->group_exit_code;
   886         else {
   887             sig->group_exit = 1;
   888             sig->group_exit_code = exit_code;
   889             zap_other_threads(current);
   890         }
   891         spin_unlock_irq(&sighand->siglock);
   892         read_unlock(&tasklist_lock);
   893     }
   894
   895     do_exit(exit_code);
   896     /* NOTREACHED */
   897 }
   898
   899 /*
   900  * this kills every thread in the thread group. Note that any externally
   901  * wait4()-ing process will get the correct exit code - even if this
   902  * thread is not the thread group leader.
   903  */
   904 asmlinkage void sys_exit_group(int error_code)
   905 {
   906     do_group_exit((error_code & 0xff) << 8);
   907 }

上 面的sys_exit_group就是系统统调用exit_group的实现,它调用了do_group_exit,而do_group_exit里当然 处理退出各个group的操作,不过我们不关心,我们关心的是它调用了do_exit。一个进程正常退出就是指exit,其实在内核里的实现就是 do_exit(废话!)。还要注意,2.6.9内核有exit_group系统调用,2.6.32里已经没有了,在哪个版本消失的?这个我们以后再说。

[kernel/exit.c]
   783 asmlinkage NORET_TYPE void do_exit(long code)
   784 {
   785     struct task_struct *tsk = current;
   786
   787     profile_task_exit(tsk);
   788
   789     if (unlikely(in_interrupt()))
   790         panic("Aiee, killing interrupt handler!");
   791     if (unlikely(!tsk->pid))
   792         panic("Attempted to kill the idle task!");
   793     if (unlikely(tsk->pid == 1))
   794         panic("Attempted to kill init!");
   795     if (tsk->io_context)
   796         exit_io_context();
   797     tsk->flags |= PF_EXITING;
   798     del_timer_sync(&tsk->real_timer);
   799
   800     if (unlikely(in_atomic()))
   801         printk(KERN_INFO "note: %s[%d] exited with preempt_count %d\n",
   802                 current->comm, current->pid,
   803                 preempt_count());
   804
   805     if (unlikely(current->ptrace & PT_TRACE_EXIT)) {
   806         current->ptrace_message = code;
   807         ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);
   808     }
   809
   810     acct_process(code);
   811     __exit_mm(tsk);
   812
   813     exit_sem(tsk);
   814     __exit_files(tsk);
   815     __exit_fs(tsk);
   816     exit_namespace(tsk);
   817     exit_thread();

do_exit 做的事情不多,退出mm(每个struct task_struct管辖的内存都在这个mm里),退出namespace,退出thread(主要是关闭TSS段上的IOMAP)等,我们关心的是 __exit_files(),这里就不贴代码了,都是一些短小的函数(功能单一,函数短小,好的编码风格,但这么多的函数,如何起名字?这是个挑 战),__exit_files()调用put_files_struct(),而put_files_struct()调用 close_files(),关闭这个退出进程的所有打开文件,close_files接着调用filp_close():

[fs/open.c]
   989 int filp_close(struct file *filp, fl_owner_t id)
   990 {   
   991     int retval;
   992         
   993     /* Report and clear outstanding errors */
   994     retval = filp->f_error;
   995     if (retval)
   996         filp->f_error = 0;
   997             
   998     if (!file_count(filp)) {
   999         printk(KERN_ERR "VFS: Close: file count is 0\n");
  1000         return retval;
  1001     }       
  1002             
  1003     if (filp->f_op && filp->f_op->flush) {                             
  1004         int err = filp->f_op->flush(filp);                             
  1005         if (!retval)                                                   
  1006             retval = err;                                              
  1007     }
  1008
  1009     dnotify_flush(filp, id);
  1010     locks_remove_posix(filp, id);                                      
  1011     fput(filp);
  1012     return retval;
  1013 }  

上 面是filp_close的实现,再清楚不过了,只要是普通的文件,谁打开谁就得负责关闭,而且关闭之前必须flush。有些程序open了某个文件,没 有调用close就正常退出了,这种情况内核其实也通过do_exit帮这个要死的进程关闭(并flush)了它打开的文件,所以不用担心,没有什么资源 泄漏。

2. 被信号杀死

这涉及到信号,linux的信号机制比BSD的复杂,这里不详述,《linux内核源代码情景分析》上册 已经讲得很清楚。这里要关注的是,内核只在回到用户空间之前处理信号,处理信号的如入口是do_signal:

[arch/i386/kernel/signal.c]

  573 int fastcall do_signal(struct pt_regs *regs, sigset_t *oldset)
  574 {
  575     siginfo_t info;
  576     int signr;
  577     struct k_sigaction ka;
  578
  579     /*
  580      * We want the common case to go fast, which
  581      * is why we may in certain cases get here from
  582      * kernel mode. Just return without doing anything
  583      * if so.
  584      */
  585     if ((regs->xcs & 3) != 3)
  586         return 1;
  587
  588     if (current->flags & PF_FREEZE) {
  589         refrigerator(0);
  590         goto no_signal;
  591     }
  592
  593     if (!oldset)
  594         oldset = &current->blocked;
  595
  596     signr = get_signal_to_deliver(&info, &ka, regs, NULL);

get_signal_to_delive()的代码很多,但主要就是循环调用dequeue_signal,从信号队列里拿出所有待处理的信号,逐一处理之,注释如下。

[kernel/signal.c --> get_signal_to_delive]
  1831 int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
  1832               struct pt_regs *regs, void *cookie)
  1833 {
  1834     sigset_t *mask = &current->blocked;
  1835     int signr = 0;
  1836
  1837 relock:
  1838     spin_lock_irq(&current->sighand->siglock);
  1839     for (;;) {
  1840         struct k_sigaction *ka;
  1841
  1842         if (unlikely(current->signal->group_stop_count > 0) &&
  1843             handle_group_stop())
  1844             goto relock;
  1845
  1846         signr = dequeue_signal(current, mask, info);                          // 从当前进程的信号队列里取出信号
  1847
  1848         if (!signr)
  1849             break; /* will return 0 */
  1850
  1851         if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {      // 如果有strace跟踪当前进程,且无kill信号,则处理之。此代码块内就是strace工作原理。
  1852             ptrace_signal_deliver(regs, cookie);
  1853
  1854             /* Let the debugger run.  */
  1855             ptrace_stop(signr, info);
  1856
  1857             /* We're back.  Did the debugger cancel the sig?  */
  1858             signr = current->exit_code;
  1859             if (signr == 0)
  1860                 continue;
  1861
  1862             current->exit_code = 0;
  1863
  1864             /* Update the siginfo structure if the signal has
  1865                changed.  If the debugger wanted something
  1866                specific in the siginfo structure then it should
  1867                have updated *info via PTRACE_SETSIGINFO.  */
  1868             if (signr != info->si_signo) {
  1869                 info->si_signo = signr;
  1870                 info->si_errno = 0;
  1871                 info->si_code = SI_USER;
  1872                 info->si_pid = current->parent->pid;
  1873                 info->si_uid = current->parent->uid;
  1874             }
  1875
  1876             /* If the (new) signal is now blocked, requeue it.  */
  1877             if (sigismember(&current->blocked, signr)) {
  1878                 specific_send_sig_info(signr, info, current);
  1879                 continue;
  1880             }
  1881         }
  1882
  1883         ka = &current->sighand->action[signr-1];
  1884         if (ka->sa.sa_handler == SIG_IGN) /* Do nothing.  */            // 如果用户要求忽略,那就继续循环,处理下一个信号
  1885             continue;
  1886         if (ka->sa.sa_handler != SIG_DFL) {                                   // 如果用户自己实现了信号处理函数,则执行之
  1887             /* Run the handler.  */
  1888             *return_ka = *ka;
  1889
  1890             if (ka->sa.sa_flags & SA_ONESHOT)
  1891                 ka->sa.sa_handler = SIG_DFL;
  1892
  1893             break; /* will return non-zero "signr" value */
  1894         }
  1895
  1896         /*
  1897          * Now we are doing the default action for this signal.
  1898          */
  1899         if (sig_kernel_ignore(signr)) /* Default is nothing. */
  1900             continue;
  1901
  1902         /* Init gets no signals it doesn't want.  */
  1903         if (current->pid == 1)
  1904             continue;
  1905
  1906         if (sig_kernel_stop(signr)) {                      // 如果是SIGSTOP,SIGSTP,SIGTTIN,SIGTTOU之一,则执行下面的代码块
  1907             /*
  1908              * The default action is to stop all threads in
  1909              * the thread group.  The job control signals
  1910              * do nothing in an orphaned pgrp, but SIGSTOP
  1911              * always works.  Note that siglock needs to be
  1912              * dropped during the call to is_orphaned_pgrp()
  1913              * because of lock ordering with tasklist_lock.
  1914              * This allows an intervening SIGCONT to be posted.
  1915              * We need to check for that and bail out if necessary.
  1916              */
  1917             if (signr == SIGSTOP) {
  1918                 do_signal_stop(signr); /* releases siglock */
  1919                 goto relock;
  1920             }
  1921             spin_unlock_irq(&current->sighand->siglock);
  1922
  1923             /* signals can be posted during this window */
  1924
  1925             if (is_orphaned_pgrp(process_group(current)))
  1926                 goto relock;
  1927
  1928             spin_lock_irq(&current->sighand->siglock);
  1929             if (unlikely(sig_avoid_stop_race())) {
  1930                 /*
  1931                  * Either a SIGCONT or a SIGKILL signal was
  1932                  * posted in the siglock-not-held window.
  1933                  */
  1934                 continue;
  1935             }
  1936
  1937             do_signal_stop(signr); /* releases siglock */
  1938             goto relock;
  1939         }
  1940
  1941         spin_unlock_irq(&current->sighand->siglock);
  1942
  1943         /*
  1944          * Anything else is fatal, maybe with a core dump.
  1945          */
  1946         current->flags |= PF_SIGNALED;
  1947         if (sig_kernel_coredump(signr) &&
  1948             do_coredump((long)signr, signr, regs)) {      // 注意,是SIGSEGV或SIGQUIT或SIGILL等信号,要coredump了!
  1949             /*
  1950              * That killed all other threads in the group and
  1951              * synchronized with their demise, so there can't
  1952              * be any more left to kill now.  The group_exit
  1953              * flags are set by do_coredump.  Note that
  1954              * thread_group_empty won't always be true yet,
  1955              * because those threads were blocked in __exit_mm
  1956              * and we just let them go to finish dying.
  1957              */
  1958             const int code = signr | 0x80;
  1959             BUG_ON(!current->signal->group_exit);
  1960             BUG_ON(current->signal->group_exit_code != code);
  1961             do_exit(code);                                         // 即使coredump,也要调用do_exit的
  1962             /* NOTREACHED */
  1963         }
  1964
  1965         /*
  1966          * Death signals, no core dump.
  1967          */
  1968         do_group_exit(signr);                                    // 上面的都不成立,进程退出
  1969         /* NOTREACHED */
  1970     }
  1971     spin_unlock_irq(&current->sighand->siglock);
  1972     return signr;
  1973 }

看上面代码,coredump如果成功,调用do_exit,要flush的;如果coredump不成功,下面第1968行do_group_exit里也要调用do_exit,还是要flush的。

总结

linux身为操作系统,对进程死掉这种情况必须处理的干干净净,因为这是经常经常发生的事,所以进程只要退出,哪怕是被kill信号杀死,哪怕是coredump,都是要调用flush的
所以yahoo利用linux下的这一特性,做了一个虚拟设备,进程启动时打开此设备,这之后,只要进程一死,不管是怎么死的,这个虚拟设备都会知道(因 为设备可以截获flush),然后干些重要的事情......不能再说了,梅坚说了,今后不准顺便透露友公司的技术......所以就此打住,我可不想被 fire。

====== 2010.08.10 ======
进程不管是怎么死的,最终会调用flush,这是肯定的,但调用flush不等于说数据就一定会写往硬盘:http://linux.chinaunix.net/bbs/thread-1168706-1-1.html
      linux下的tc可以操纵网络,比如分配带宽给不同的应用、模拟网络时延、模拟糟糕网络环境下的丢包等。

      但在实际使用模拟丢包时,我们 发现了问题:两台服务器,一台跑tcp的server,一台跑tcp的client,client只send,server只recv(并打印出来),在 网络环境正常时,发送100万条消息只要40秒,但如果我们在两台服务器的任一台设上 " tc qdisc add dev eth0 root netem loss 1% " (在eth0设上1%的丢包率),消息的收发就戛然而止,strace发现时client阻塞在sendto上了(并没有发生我们送想象的“重传”机 制),把丢包率改回来(即恢复成正常网络),程序还是阻塞在sendto上。我们程序没写好?仔细检查,程序没问题(代码),而且我们发现,scp也会受同样的影响,表现和我们自己写的程序一样。难道tcp不可靠?

      结果发现时tc的用法不对。tc不是这样用的。中间需要用一个路由器。

      于是我们拿一台linux机当router,单网卡,两个虚拟ip,让它连接两台服务器再试。更滑稽了,tc不起作用,两台服务器间的流量刷刷的走,tc却显示没有多少packet经过。tc不能用于router吗?
后来看了tc的详细手册(注意4.1节),终于知道了:tc标准用法是两台服务器中间一个双网卡的router,在router上用tc。

       最后测试成功。丢包率越高,tcp传输的速度越慢;如果丢包率很高,tcp可能会顿住,但是只要改回去(去掉tc的netem配置),传输就会恢复。
       TCP很可靠。
       中午吃饭突然想到了王在之,于是想到了毕业后8个人挤在40平米房子里讨生活的日子。

       两室一厨,没有厅。墙壁挂满斑驳的泥灰。每当楼上用水的时候,我们的厨房就成水帘洞。我们一共摆了三个上下铺和两个沙发,全部睡满,合计8个人。
       这就是03年我们毕业以后合租的地方。大家都没工作。
       我住了8个月,然后去打零工了。其他人住了一年。

       我和周济远是物理奥林匹克竞赛保送的,王在之和赵汗青是数学奥林匹克竞赛保送的,陈弈龙是化学奥林匹克竞赛保送的。
      在这40平米,8个可怜虫里,就有5个是当年高中奥林匹克竞赛的赢家。

       奥林匹克精神万岁!
malloc不是系统调用,它是glibc的库函数,它实际调用了brk,然后管理了一个内存池。

sleep和usleep都不是系统调用,它们是信号加nanosleep实现的,也是glibc的库函数。

gethostname在intel机器上,即x86_64和i386上也不是系统调用,是用系统调用uname实现的,依然是glibc的库函数。

不信的话,在x86机器上编译运行这段程序 test.c:

  #include 
  #include 
 
  int main(int argc, char* argv[])
  {
      char    name[1024];
      int     i;
      char*   p;
 
      for(i=0; i<10; i++)
      {
          gethostname(name, sizeof(name));
          usleep(1);
          p = malloc(100);
      }
 
      printf("hostname:%s\n", name);
  }

gcc test.c -o test
nm test 你会看到

gethostname@@GLIBC_X.X.X
usleep@@GLIBC_X.X.X
malloc@@GLIBC_X.X.X

strace ./test 跑一跑,你会看到uname,nanosleep有10次循环执行,brk没有10次那么多,因为它是内存池,一次就要个很多。

gethostname 在linux内核代码中确实有实现(kernel/sys.c函数sys_gethostname),但是这个调用只对MIPS和Alpha架构的机器有 效,对x86_64和i386架构的机器,内核连系统调用号都没有提供,也就没有这个系统调用了。

另外,strace跟踪程序时还会发现“restart_syscall”,这是用于signal的系统调用,是为了保证信号的可靠性。

====== 2009.12.23 分割线 ======

至于为什么MIPS和Alpha才把gethostname当system call,我发邮件问了 Alan Cox ,老兄还真是负责,回了邮件:
Because it doesn't need to be kernel supported. The MIPS/Alpha have it to
support compatibility with MIPS and OSF Unix apps.

很简洁的Hack风格,MIPS/Alpha把gethostname当system call是为了支持很多在它们上面跑的老unix程序。
我们都是凡人,都是胆小怕事却又爱贪便宜的老百姓。
我们辛辛苦苦的工作,我们精打细算的过活,我们不想可怜巴巴的过一辈子,却又没胆量捞大钱。
我们是“蚁族”,可怜而卑微,但也是“群氓”,贪婪而邪恶。

如果一个人挡住了诱惑,那是因为诱惑还不够。如果一个人还没有良心发现,那是因为出现在他身上的悲剧还不够多。

好久没看新出的港片了,《暗花》之后,终于又有一部我看得上的。
能用普通文本就别用word,那点内容实在不值得调那么臃肿的office程序。

能用google docs共享的文档就不要做为附件满邮件组的群发,浪费带宽和邮箱空间。

能用twiki就不要用sharepoint。twiki可以在线编辑文档,而且文档格式非常轻量级;而sharepoint每次编辑都要打开office,浪费带宽和CPU,不环保。

能用普通的用户名密码就不要用域账号。如果网络有安全保障,普通用户名也安全;如果网络有漏洞,域也一样危险。

能用文本做配置文件就不要用XML,白费CPU和眼球。

做软件要做功能精体积小的,不要做功能全体积臃肿的。

最重要的一条:
能建生态系统就尽量建竞争者众多的生态系统,不要老想着把自己建成一个啥都做的帝国。
代码如下:

void accept()
{
   ....
   Connection* client = new Connection();
}

gcc编译时报了 warning,说client变量没有用到,于是我把这一行删了,结果程序错误。后来才发现Connection构造函数是做了事情的,这样直接删不对。正确的改法是直接改为:
new Connection();

看来修复编译器的warning也得留神。

一定要省油

| | Comments (0) | TrackBacks (0)
      在克服了年初的TP400-D6发动机软件bug以后,A400M试飞成功。做为军用运输机,自身的耗油不能太多,否则做为使用者的后勤部门就要抓狂了。 在乌克兰的桨扇发动机研发完成以前,涡桨发动机是最省油的选择,虽然噪音大些,但运输机又不怕吵。TP400可是欧洲历史上研发的最大功率的涡桨发动机啊,所以我非常看好A400M。
       由于中国的要求而改名的波音787也首次试飞成功。07年底,波音号称首架787下线,但其实根本没完工,很多零件都是拿胶水(不知道什么牌子)粘在一起 的,完全是为了露个脸。毕竟采用了很多新的技术,发布时间自然容易延后,在这一点上,软件和飞机是一样的。那为什么要采用那么多新材料、新技术、新设计 呢?为了省20%的油。

       其实说来说去,油价越来越高的今天航空业现在就一个目标——省油。

       所以,一定要省油
      不断的省油
      不要买车
       买了也不要开......

       另外,炒菜的时候也少放点油,省省更健康   -_-b

豆瓣认证

| | Comments (0) | TrackBacks (0)
doubanclaim21ef3bfb305a9716
doubanclaim218a36ac7c2bd687

豆瓣是个好地方,帮我推荐了不少好书,而且上面的评价参考价值很高,让我少看了许多烂片,节约了时间。

今天试用了一下新的豆瓣电台,不好用,全是外国歌曲,而且都是小资类的,不爱听。

病倒了

| | Comments (12) | TrackBacks (0)
      上个月底,晚上睡觉突然觉得胸闷气短,心悸,心跳又快又重,十分难受,根本睡不着,于是去医院看病,检查了心脏,没有任何问题,只好回家。
      难受的感觉却依旧,好几天睡不好,有一天晚上居然变得十分严重,感觉有人用粗木棍在我胸口使劲的捣、捣、捣...... 由于太困睡过去了,但不一会儿又被活活闷醒,睡也不能,醒也不能,当时真是感觉无处可逃,万念俱灰。
      最后闷得我简直喘不过气,只好半夜零点叫醒老婆陪我去看急诊,心动过速,于是吸氧、输液,一直输到凌晨6点。可怜老婆一夜未眠,在输液室陪着我到天亮。
      第二天再去挂号看了神经内科,这次住院了。确诊是焦虑症。我说最近没什么可焦虑的啊,医生说未必是发生什么大事,休息不好、性子急躁、甚至感冒发烧都可能引起。
      这下顿了,到北京10年来,这是第一次住院。
      在医院住了两星期,针灸、输液、还有盐酸帕诺西丁,总算好转了,虽然睡眠还比较浅,但不再闷得难受了。谢天谢地。

      想起胸闷的那天晚上,脊背还是一阵寒意,疾病是最可怕的东西,比bug、比不能按时完成项目、比半夜起来处理服务器故障还要可怕百倍、难受百倍,所以......宁可放弃工作,也要保护好健康啊。

      工作4年,经常是11点、12点睡觉,周末有时甚至是1点、2点,长期透支身体,肝火旺所以火气大精神好(中医所谓的“阴虚”),还以为自己年轻精力无比充沛呢,唉!我的确感觉最近一年性子越来越急,想来是肝生气,气伤肝,最后病倒。

      教训啊!我这次是学乖了,放老实了,坏习惯一定要改了。以后吃饭再也不狼吞虎咽了(肠胃压力过大,会引起胸闷心悸),学老婆,细嚼慢咽。再也不因一点小事生气,再也不生闷气了。想起那晚,我是吓得不敢乱生气了。再也不敢晚睡了,10点前我就乖乖躺倒。

      也提醒各位程序员朋友注意身体,别太玩命,关键时刻,钱买不来健康啊。

====== 2009.12.23 分割线 ======

今天医保报销,我花了一个小时才把这一个多月来的看病资料整理完,共花了四千多块,幸好有医保。
要不过来人都说嘛,健康无价啊。

关于存档

This page is an archive of entries from 12 2009 listed from newest to oldest.

11 2009 is the previous archive.

01 2010 is the next archive.

Find recent content on the main index or look in the 存档 to find all content.