DongHao: 12 2009存档
作者:董昊 (要转载的同学帮忙把名字和博客链接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 = ¤t->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 = ¤t->blocked;
1835 int signr = 0;
1836
1837 relock:
1838 spin_lock_irq(¤t->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(¤t->blocked, signr)) {
1878 specific_send_sig_info(signr, info, current);
1879 continue;
1880 }
1881 }
1882
1883 ka = ¤t->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(¤t->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(¤t->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(¤t->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(¤t->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。
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 = ¤t->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 = ¤t->blocked;
1835 int signr = 0;
1836
1837 relock:
1838 spin_lock_irq(¤t->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(¤t->blocked, signr)) {
1878 specific_send_sig_info(signr, info, current);
1879 continue;
1880 }
1881 }
1882
1883 ka = ¤t->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(¤t->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(¤t->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(¤t->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(¤t->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很可靠。
但在实际使用模拟丢包时,我们 发现了问题:两台服务器,一台跑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个是当年高中奥林匹克竞赛的赢家。
奥林匹克精神万岁!
两室一厨,没有厅。墙壁挂满斑驳的泥灰。每当楼上用水的时候,我们的厨房就成水帘洞。我们一共摆了三个上下铺和两个沙发,全部睡满,合计8个人。
这就是03年我们毕业以后合租的地方。大家都没工作。
我住了8个月,然后去打零工了。其他人住了一年。
我和周济远是物理奥林匹克竞赛保送的,王在之和赵汗青是数学奥林匹克竞赛保送的,陈弈龙是化学奥林匹克竞赛保送的。
在这40平米,8个可怜虫里,就有5个是当年高中奥林匹克竞赛的赢家。
奥林匹克精神万岁!
malloc不是系统调用,它是glibc的库函数,它实际调用了brk,然后管理了一个内存池。
sleep和usleep都不是系统调用,它们是信号加nanosleep实现的,也是glibc的库函数。
gethostname在intel机器上,即x86_64和i386上也不是系统调用,是用系统调用uname实现的,依然是glibc的库函数。
不信的话,在x86机器上编译运行这段程序 test.c:
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程序。
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 testnm 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和眼球。
做软件要做功能精体积小的,不要做功能全体积臃肿的。
最重要的一条:
能建生态系统就尽量建竞争者众多的生态系统,不要老想着把自己建成一个啥都做的帝国。
能用google docs共享的文档就不要做为附件满邮件组的群发,浪费带宽和邮箱空间。
能用twiki就不要用sharepoint。twiki可以在线编辑文档,而且文档格式非常轻量级;而sharepoint每次编辑都要打开office,浪费带宽和CPU,不环保。
能用普通的用户名密码就不要用域账号。如果网络有安全保障,普通用户名也安全;如果网络有漏洞,域也一样危险。
能用文本做配置文件就不要用XML,白费CPU和眼球。
做软件要做功能精体积小的,不要做功能全体积臃肿的。
最重要的一条:
能建生态系统就尽量建竞争者众多的生态系统,不要老想着把自己建成一个啥都做的帝国。
代码如下:
void accept()
{
....
Connection* client = new Connection();
}
gcc编译时报了 warning,说client变量没有用到,于是我把这一行删了,结果程序错误。后来才发现Connection构造函数是做了事情的,这样直接删不对。正确的改法是直接改为:
new Connection();
看来修复编译器的warning也得留神。
void accept()
{
....
Connection* client = new Connection();
}
gcc编译时报了 warning,说client变量没有用到,于是我把这一行删了,结果程序错误。后来才发现Connection构造函数是做了事情的,这样直接删不对。正确的改法是直接改为:
new Connection();
看来修复编译器的warning也得留神。
在克服了年初的TP400-D6发动机软件bug以后,A400M试飞成功。做为军用运输机,自身的耗油不能太多,否则做为使用者的后勤部门就要抓狂了。
在乌克兰的桨扇发动机研发完成以前,涡桨发动机是最省油的选择,虽然噪音大些,但运输机又不怕吵。TP400可是欧洲历史上研发的最大功率的涡桨发动机啊,所以我非常看好A400M。
由于中国的要求而改名的波音787也首次试飞成功。07年底,波音号称首架787下线,但其实根本没完工,很多零件都是拿胶水(不知道什么牌子)粘在一起 的,完全是为了露个脸。毕竟采用了很多新的技术,发布时间自然容易延后,在这一点上,软件和飞机是一样的。那为什么要采用那么多新材料、新技术、新设计 呢?为了省20%的油。
其实说来说去,油价越来越高的今天航空业现在就一个目标——省油。
所以,一定要省油
不断的省油
不要买车
买了也不要开......
另外,炒菜的时候也少放点油,省省更健康 -_-b
由于中国的要求而改名的波音787也首次试飞成功。07年底,波音号称首架787下线,但其实根本没完工,很多零件都是拿胶水(不知道什么牌子)粘在一起 的,完全是为了露个脸。毕竟采用了很多新的技术,发布时间自然容易延后,在这一点上,软件和飞机是一样的。那为什么要采用那么多新材料、新技术、新设计 呢?为了省20%的油。
其实说来说去,油价越来越高的今天航空业现在就一个目标——省油。
所以,一定要省油
不断的省油
不要买车
买了也不要开......
另外,炒菜的时候也少放点油,省省更健康 -_-b
doubanclaim21ef3bfb305a9716
doubanclaim218a36ac7c2bd687
豆瓣是个好地方,帮我推荐了不少好书,而且上面的评价参考价值很高,让我少看了许多烂片,节约了时间。
今天试用了一下新的豆瓣电台,不好用,全是外国歌曲,而且都是小资类的,不爱听。
doubanclaim218a36ac7c2bd687
豆瓣是个好地方,帮我推荐了不少好书,而且上面的评价参考价值很高,让我少看了许多烂片,节约了时间。
今天试用了一下新的豆瓣电台,不好用,全是外国歌曲,而且都是小资类的,不爱听。
上个月底,晚上睡觉突然觉得胸闷气短,心悸,心跳又快又重,十分难受,根本睡不着,于是去医院看病,检查了心脏,没有任何问题,只好回家。
难受的感觉却依旧,好几天睡不好,有一天晚上居然变得十分严重,感觉有人用粗木棍在我胸口使劲的捣、捣、捣...... 由于太困睡过去了,但不一会儿又被活活闷醒,睡也不能,醒也不能,当时真是感觉无处可逃,万念俱灰。
最后闷得我简直喘不过气,只好半夜零点叫醒老婆陪我去看急诊,心动过速,于是吸氧、输液,一直输到凌晨6点。可怜老婆一夜未眠,在输液室陪着我到天亮。
第二天再去挂号看了神经内科,这次住院了。确诊是焦虑症。我说最近没什么可焦虑的啊,医生说未必是发生什么大事,休息不好、性子急躁、甚至感冒发烧都可能引起。
这下顿了,到北京10年来,这是第一次住院。
在医院住了两星期,针灸、输液、还有盐酸帕诺西丁,总算好转了,虽然睡眠还比较浅,但不再闷得难受了。谢天谢地。
想起胸闷的那天晚上,脊背还是一阵寒意,疾病是最可怕的东西,比bug、比不能按时完成项目、比半夜起来处理服务器故障还要可怕百倍、难受百倍,所以......宁可放弃工作,也要保护好健康啊。
工作4年,经常是11点、12点睡觉,周末有时甚至是1点、2点,长期透支身体,肝火旺所以火气大精神好(中医所谓的“阴虚”),还以为自己年轻精力无比充沛呢,唉!我的确感觉最近一年性子越来越急,想来是肝生气,气伤肝,最后病倒。
教训啊!我这次是学乖了,放老实了,坏习惯一定要改了。以后吃饭再也不狼吞虎咽了(肠胃压力过大,会引起胸闷心悸),学老婆,细嚼慢咽。再也不因一点小事生气,再也不生闷气了。想起那晚,我是吓得不敢乱生气了。再也不敢晚睡了,10点前我就乖乖躺倒。
也提醒各位程序员朋友注意身体,别太玩命,关键时刻,钱买不来健康啊。
====== 2009.12.23 分割线 ======
今天医保报销,我花了一个小时才把这一个多月来的看病资料整理完,共花了四千多块,幸好有医保。
要不过来人都说嘛,健康无价啊。
难受的感觉却依旧,好几天睡不好,有一天晚上居然变得十分严重,感觉有人用粗木棍在我胸口使劲的捣、捣、捣...... 由于太困睡过去了,但不一会儿又被活活闷醒,睡也不能,醒也不能,当时真是感觉无处可逃,万念俱灰。
最后闷得我简直喘不过气,只好半夜零点叫醒老婆陪我去看急诊,心动过速,于是吸氧、输液,一直输到凌晨6点。可怜老婆一夜未眠,在输液室陪着我到天亮。
第二天再去挂号看了神经内科,这次住院了。确诊是焦虑症。我说最近没什么可焦虑的啊,医生说未必是发生什么大事,休息不好、性子急躁、甚至感冒发烧都可能引起。
这下顿了,到北京10年来,这是第一次住院。
在医院住了两星期,针灸、输液、还有盐酸帕诺西丁,总算好转了,虽然睡眠还比较浅,但不再闷得难受了。谢天谢地。
想起胸闷的那天晚上,脊背还是一阵寒意,疾病是最可怕的东西,比bug、比不能按时完成项目、比半夜起来处理服务器故障还要可怕百倍、难受百倍,所以......宁可放弃工作,也要保护好健康啊。
工作4年,经常是11点、12点睡觉,周末有时甚至是1点、2点,长期透支身体,肝火旺所以火气大精神好(中医所谓的“阴虚”),还以为自己年轻精力无比充沛呢,唉!我的确感觉最近一年性子越来越急,想来是肝生气,气伤肝,最后病倒。
教训啊!我这次是学乖了,放老实了,坏习惯一定要改了。以后吃饭再也不狼吞虎咽了(肠胃压力过大,会引起胸闷心悸),学老婆,细嚼慢咽。再也不因一点小事生气,再也不生闷气了。想起那晚,我是吓得不敢乱生气了。再也不敢晚睡了,10点前我就乖乖躺倒。
也提醒各位程序员朋友注意身体,别太玩命,关键时刻,钱买不来健康啊。
====== 2009.12.23 分割线 ======
今天医保报销,我花了一个小时才把这一个多月来的看病资料整理完,共花了四千多块,幸好有医保。
要不过来人都说嘛,健康无价啊。