msgbartop
DoneVII CET & CPPLITE
msgbarbottom

30 Jun 09 网上闻名已久的“操帝”

太逗了

Tags: ,

30 Jun 09 真人示范 罩杯大小尺寸[图片]

A cup — Airport

B cup — Barely there

C cup — Can do

D cup—Damn good

E cup—Ecstasy

F cup—Fake

G cup—God

H cup—Horrible

I cup—I can’t believe it

J cup—Joke

K cup—Kidding

L cup—Large

M cup—Monster

N cup—Nothing like that

28 Jun 09 上海13层大厦整体倒塌后的网友经典汇总贴

1、今天早上那声巨响难道不是雷 ,是这个房子?
    
    2、再把它扶起来又可以卖了…..
    
    3、扶起来,洗洗干净继续卖
    
    4、这是开发商的行为艺术吗!
    
    5、现在变成只有天窗的联体别墅了~
    
    6、我国多了两个支柱产业:倒塌楼房预测业、扶楼业(扶大厦于已倾)。
    
    7、新闻上说“除了”有个工人死亡外,“无”人员伤亡
    
    8、支持保留现场,以飨后代
    
    9、大楼说是它自己不小心摔倒的,它还说从哪里跌倒了,还会再从哪里爬起来.放心住吧.
    
    10、2009年流行词汇:一座楼说:“我倒!!!”于是他真的倒了
    
  11、一小撮不明争相的 混凝土 这么不和血!!
    
  12、房子没有倒,它只是在做腑卧撑而已!
  
  13、变形金刚到上海了。
  
  14、还好楼距还凑和,要不然成骨牌了。
  
  15、由于都不买房,没有入住,重心偏高,等居民都欢欣鼓舞的入住进去就能压住了。。。。
  
  16、房子离河边太近,
    会不会是河里的螃蟹钻地打洞破坏土层结构
    导致楼房整体倒塌呢?
  
  17、站起来是楼房 倒下去是绿坝
  
  18、第一次见到房倒得这么有性格的
  
  19、高层不流行了,规划局同意改成躺好死卖。
  
  20、站着商品房,躺下骨灰盒!

Tags:

28 Jun 09 (原創) 如何让一个thread在背景不断的执行? (使用semaphore)

要让一个thread在背景不断的执行,最简单的方式就是在该thread执行无穷回圈,如while(1) {},这种写法虽可行,却会让CPU飙高到100%,因为CPU一直死死的等,其实比较好的方法是,背景平时在Sleep状态,当前景呼叫背景时,背景马上被唤醒,执行该做的事,做完马上Sleep,等待前景呼叫。当背景sem_wait()时,就是马上处于Sleep状态,当前景sem_post()时,会马上换起背景执行,如此就可避免CPU 100%的情形了。
 1/**//*
 2(C) OOMusou 2006 http://oomusou.cnblogs.com
 3
 4Filename    : pthread_create_semaphore.cpp
 5Compiler    : gcc 4.10 on Fedora 5 / gcc 3.4 on Cygwin 1.5.21
 6Description : Demo how to create thread with semaphore in Linux.
 7Release     : 12/03/2006
 8Compile     : g++ -lpthread pthread_create_semaphore.cpp
 9*/
10#include <stdio.h>     // printf(),
11#include <stdlib.h>    // exit(), EXIT_SUCCESS
12#include <pthread.h>   // pthread_create(), pthread_join()
13#include <semaphore.h> // sem_init()
14
15sem_t binSem;
16
17void* helloWorld(void* arg);
18
19int main() {
20  // Result for System call
21  int res = 0;
22
23  // Initialize semaphore
24  res = sem_init(&binSem, 0, 0);
25  if (res) {
26    printf(”Semaphore initialization failed!!\n”);
27    exit(EXIT_FAILURE);
28  }
29
30  // Create thread
31  pthread_t thdHelloWorld; 
32  res = pthread_create(&thdHelloWorld, NULL, helloWorld, NULL);
33  if (res) {
34    printf(”Thread creation failed!!\n”);
35    exit(EXIT_FAILURE);
36  }
37
38  while(1) {
39    // Post semaphore
40    sem_post(&binSem);
41  }
42
43  // Wait for thread synchronization
44  void *threadResult;
45  res = pthread_join(thdHelloWorld, &threadResult);
46  if (res) {
47    printf(”Thread join failed!!\n”);
48    exit(EXIT_FAILURE);
49  }
50
51  exit(EXIT_SUCCESS);
52}
53
54void* helloWorld(void* arg) {
55  while(1) {
56    // Wait semaphore
57    sem_wait(&binSem);
58    printf(”Hello World\n”);
59  }
60}

28 Jun 09 Introduction to CuRT_v1

前言

對大多數的人而言,一個作業系統和天書可能差別不大。然而事實是如此嗎? 世界上有一大堆小的快不像樣的 OS,jserv 的 CuRT 就是一個例子。像這樣的 OS 理論上只要是資工系統的學生都應該要寫的出來,否則大家花了那麼多的時間學程式語言和作業系統是做什麼用的? 然而如果我們做個調查,我想大多數的人都認為自己寫一個 OS 是不可能的事。這份文件就是用來解答大家的疑惑,讓大家不要再覺得這是不可能的事情了。寫一個 OS 是多麼美好的事,在有限的生命中千萬不要遺漏了它。

OS 是什麼?

回去看看教科書吧! 或是我們的好朋友 wikipedia。在 wikipedia 中的架構圖中你可以看到作業系統大致由

  • Device driver
  • kernel
  • system call
  • shell

所組成。除此之外,由於作業系統是硬體上第一個被執行的程式。通常它還需要做一些初始化的動作,讓硬體知道如何和作業系統配合。這個初始化的動作可以被視為是 CPU 的 device driver。

所以我們就從初始化開始吧!

系統初始化

一個 CPU 需要做那些初始化的動作呢?

設定中斷表

不管在那一個 CPU 中,這通常是第一件要做的事。不過是通常也不必執行任何程式,大多數的 CPU 會要求中斷表被放在記憶空間中固定的地方。例如  ARM,中斷表就在 位址 0-31處。所以我們只要在編譯程式時確定它們一定會在最前面就可以了。在 CuRT 中,arch/arm/mach-pxa/start.S 的最前面就在做這件事。

        .text        .text
        /* exception handler vector table */
_start:
        b reset_handler
        b und_handler
        b swi_handler
        b abt_pref_handler
        b abt_data_handler
        b not_used
        b irq_handler
        b fiq_handler

請注意,在 ARM 中,中斷向量表中儲存的是指令而不是位址。

動腦時間: 想一想,這樣有什麼限制呢?(ex1)

設定 CPU 運作參數

現代的 CPU 通常都非常複雜。一般都提供了非常多不同的選項,例如 CPU clock或是 記憶體模式(segment/flat)等等。如果我們不使用預值,那就必須改變它。在 CuRT 中,在 start.S 中的

  • set_misc: 初始化所有 coprocessor並且清掉所有 cache 中的資料。
  • init_clock_reg: 改變 CPU clock
  • set_os_timer: 設定 system timer 的速度
  • init_gpio: 大多數 CPU 的 GPIO 都是多用途的,它們可以做為 IO pin 或是其它特別功能,如 UART,PWM,….。根據硬體的設計,我們必須適當的設定它。

通常 GPIO 是最需要注意的部份,其它的部份只要用  vendor 的  sample code 通常就差不了太多了。GPIO 的部份通常需要看一個系統的電路圖來決定。每一個 GPIO pin 都可能是下列其中一個

  • Input: 用來輸入一個 0 或是 1。
  • Output: 用來輸出一個 0 或是 1。
  • High impedance: 斷路
  • Alternate function: 用來做為一個特別功能,如 USB/UART/PCI/PWM 等的接腳。

一般 CPU 都會為每一個 GPIO 提供 2-3 個 control bit 來控制

  • input/ouput
  • gpio/alternative function
  • high impedance or not

porting OS 最重要的事通常就是把每一個 bit 都設對。看電路圖的能力在這裡是非常重要的,一定要了解什麼是 pull high/pull low/ open drain/high impedance 等基本的電路的定義。

設定記憶體管理單元

CPU 設定好後,下一個當然就是記憶體了。記憶體有很多種,不過大致可能分成二種。一種是可能直接接在 memory bus 上的,如 SDRAM/DDR/DDR2/NOR 等。這一類的記憶體可以直接存放程式碼,CPU 可以透過 load/store 指令直接存取它們。因此 CPU 的 instrcution/data cache 可以直接和它們溝通。

另一類是不能直接被 CPU 存取的,如 NAND /SPI /SSD 等。它們都必須透過特殊的控制器來存取,這些控制器多半也沒有內建在 CPU 之中。所以這些記憶體是不能用來儲存程式的。這裡的記憶體管理單元通常指的是前者。

在 embeded system 中的記憶體通常不是模組,也就是說我們沒有辦法像 PC 的記憶體一樣從模組內讀到記憶體的參數。因為這些參數必須被直接寫在 OS 中。在 CuRT 中 init_mem_ctrl 就是用來設定 PXA 的記憶體控制器。難這裡要注意的是動態記憶體是需要被啟動的,也就是說當我們改變了參數後,必須做一個重新啟動的動作使得記憶體控制器和記憶體達到同步的情況。至於怎麼做就要視平台而定了。請看 CPU 的 datasheet。

另一件更重要的事。請不要假設記憶的內容在參數改變後還會存在。如果你想要在 warm boot 時讀到之前的內容,一定要在設定記憶體管理單元之前做。

設定 timer

所有的作業系統都需要 timer interrupt。作業系統會在每次 timer 中斷來時做 scheduling 的動作。一般而言,timer 的設定是很簡單的。Timer 通常會有

  • 一個 counter register
  • 一個 match register 或初始值 register。Timer 多久中斷一次就看這個 register 而定。

設定堆疊

接下來就要為 CPU 安排堆疊了。幾乎全部的 CPU 者是堆疊架構。當中斷發生時 CPU 會把目前狀態儲存在 stack pointer register(SP) 所指的位址。在ARM 中就更特別了。ARM CPU 在不同的中斷會用不同的堆疊。所以我們必需一 設定。

首先是 FIQ 模式。FIQ 是代表 fast interrupt。在這個模式下 ARM 會將 r8-r15 切換到 banked register。也就是說我們不必儲存這些暫器的內容。如此一來,我們只要不要重 r0-r7 的內容,那就不必做context saving 的動作了。詳細的說明可以看 http://stenlyho.blogspot.com/2008/08/arm.html

set_stack_pointer:
        /* FIQ mode */
        mrs r0, cpsr            /* move CPSR to r0 */
        bic r0, r0, #0×1f       /* clear all mode bits */
        orr r0, r0, #0xd1       /* set FIQ mode bits */
        msr CPSR_c, r0          /* move back to CPSR */
        ldr sp, =(fiq_stack + FIQ_STACK_SIZE – 4)       /* initialize the stack ptr */

CPSR 的定義可以在 http://hi.baidu.com/flfxt/blog/item/697320248b9a4d34c9955961.html 找到。CPSR 的 0-4 bits 是 代表了 CPU 目前的模式。FIQ 模式的值為 10001。上面的程式把 CPSR 改成 d1

        1   1   0   1   0   0   0   1
        I    F   T   Modes

然後把 stack 的尾端設到 SP 之中。下面的程式重復同樣的程序將不同的 stack 設到不同的模式之中。請注意一點,雖然都是 SP 但在不同樣式下它們其實是不同的暫存器。請參考上面的連結,不同的模式有些暫存器會不一樣會使用不同的暫存器。

        /* IRQ mode */
        mrs r0, cpsr            /* move CPSR to r0 */
        bic r0, r0, #0×1f       /* clear all mode bits */
        orr r0, r0, #0xd2       /* set IRQ mode bits */
        msr CPSR_c, r0          /* move back to CPSR */
        ldr sp, =(irq_stack + IRQ_STACK_SIZE – 4)       /* initialize the stack ptr */
        /* Abort mode */
        mrs r0, cpsr            /* move CPSR to r0 */
        bic r0, r0, #0×1f       /* clear all mode bits */
        orr r0, r0, #0xd7       /* set Abort mode bits */
        msr CPSR_c, r0          /* move back to CPSR */
        ldr sp, =(abt_stack + ABT_STACK_SIZE – 4)       /* initialize the stack ptr */
        /* Undef mode */
        mrs r0, cpsr            /* move CPSR to r0 */
        bic r0, r0, #0×1f       /* clear all mode bits */
        orr r0, r0, #0xdb       /* set Undef mode bits */
        msr CPSR_c, r0          /* move back to CPSR */
        ldr sp, =(und_stack + UND_STACK_SIZE – 4)       /* initialize the stack ptr */
        /* System mode */
        mrs r0, cpsr            /* move CPSR to r0 */
        bic r0, r0, #0×1f       /* clear all mode bits */
        orr r0, r0, #0xdf       /* set System mode bits */
        msr CPSR_c, r0          /* move back to CPSR */
        ldr sp, =(sys_stack + SYS_STACK_SIZE – 4)       /* initialize the stack ptr */

準備進入 OS

最後,我們準備進入 OS 了。到目前為止,除了 BootRom外,我們實際上根本沒有使用到 CPU 以外的資源。CuRT 被設計成會將程式載入到動態記憶體中執行。這一步其實是可以不要做的,這我們後面再來討論。先看一下 CuRT 是怎樣將 OS 載入到記憶體執行。

首先將程式的啟始位置載入 r0 之中

relocate:
        adr r0, _start

CuRT 的做法很簡單,直接載入 1MB。這麼小的 OS 應該不會超過 1MB 吧! 這種做法實在太偷懶了一點………

        // relocate the second stage loader
        add r2, r0, #(1024 * 1024)

a0000000 是動態記憶體的位置。友情的提醒大家一點,這個位址不見得在所有 ARM 都是對的。Porting 時要注意。

        ldr r1, =0xa0000000

        /* r0 = source address
         * r1 = target address
         * r2 = source end address
         */
copy_loop:
        ldmia   r0!, {r3-r10}
        stmia   r1!, {r3-r10}
        cmp     r0, r2

        ble     copy_loop

看了上面的程式,應該很佩服ARM 組合語言的強大吧! ldmia 一般會載入 r3-r10 一共 32 個位元,然後將 r0 加 32。 stmia 會將 r3-r10 存入記憶體,並將 r1 加 32。這在其它的 CPU 可是要用多好幾倍的指令才能完成的。

好了,目前整個 CuRT 己經在 0xa0000000 上了。 最後來個

       bl main

這樣就進入 C 之中了。

有沒有人覺得怪怪的,bl main 到底是跳到記憶體還是 BootRom 中了呢? 想到這一點,你有些高段了。如果你連答案都知道,那你應該不要繼續浪費時間看這份文件了。至於答案,讓我賣個關子吧!

進入  C  的世界

和一般程式一樣 CuRT 也用 main 做為 C 的進入點。不過這個 main 可不是一般應用程式。它的工作是為系統做初始化的工作。

不過 CuRT 的功能實在少到一個程度,它是由下面三個函式組成。

        SerialInit();
        init_interrupt_control();
        init_curt();

SerialInit 是用來初始化 serial port。在大多數的 OS 中,serial port 都是最早被建立的子系統。有了他之後,我們就等於多了一對眼睛。可以比較容易的了解系統的狀況。可以這樣說,在 serial port 初始化之前,debug 是一門藝術。在它之後,則變成一門科學。

init_interrupt_control 的功能很簡單,就是把中斷打開。在這一點之後,軟硬體的中斷就開始生效了。這包括 timer 在內。

最後是 init_curt,這是三者之中最重要的。它的主要工作就是初始化 scheduler。CuRT 實作了一個有優先權的 round-robin scheduler。這和 Linux 中的 SCHED_RR 有點像。整個  scheduler 的狀態機由下面幾個串列組成

        for (i = 0; i < MAX_PRIO; i++) {
                prio_exist_flag[i] = false;
                init_list(&ready_list[i]);
        }
        init_list(&delayed_list);
        init_list(&blocked_list);
        init_list(&termination_wait_list);

  • ready_list[0..31]: 在優先權 0..31 的並在作用中的 theads。
  • delayed_list: 這是 timer list,每一個 trhead 可以讓自己進入睡覺狀態一段時間,此時thread 會被移入 delayed_list。在每一次 timer 時CuRT 都會檢查是否要喚醒這個 thread。
  • blocked_list:目前不在作用狀態的 thread,通常在等待 IO 或是 semaphore。
  • termination_wait_list: 包括己經宣告結束的 thread,等待其它行程回收。

CuRT 在每次 timer 時會根據選出優先權最高的 thread 執行。後面我們會再回來看這個scheduler。

在這個函數的最後,idle_thread 被初始化。這個 thread 的目的在消秏系統的時間,它也可以用來執行一個不重要的工作。目前它負責回收在 termination_wait_list 中己結束的行程。

        thread_create(&idle_thread,
                        &idle_thread_stk[THREAD_STACK_SIZE-1],
                        idle_thread_func,
                        “idle-thread”,
                        IDLE_THREAD_PRIO,
                        NULL);

在 RTOS 中,並沒有 kernel mode /user mode 的存在。到底是 kernel thread 或是應用程式 thread 只是雖呼叫 thread_create 的差別而己。

Thread Thread Thread

所有東西都是 thread。在 RTOS 中一定要認和這一點。不管是 kernel, driver, application 都是 thread。system call 實際上也必須是一個 thread+semaphore 的組合。在目前的 CuRT 中,有下列的 thread

  • shell thread
  • ps thread
  • stat thread
  • help thread
  • hello thread
  • hello2 thread

當我們輸入一個命令,shell 會喚醒適當的 thread 來執行所需的功能。以 ps 為例做法是

                else if (!strcmp(buf, “ps”)) {
                        thread_resume(info_tid);
                        thread_delay(1);
                }
 
thread_resume 會喚醒處於沉睡狀態的 thread。thread_delay(1) 則會將目前的 thread 放入沈睡狀態 1 個 tick。實際上就是把 thread 放入 delayed_list 之中。這個做法有些偷懶,應該要用 semaphore 來做才正確,這樣做如果 ps thread 執行太慢就可能出問題。

動腦時間: 想想看會出什麼問題?(ex1)

如果你要為 CuRT 加入一個應用程式,那就由 heelo 或 hello2 開始吧。

動腦時間: 寫個打地鼠吧!

中斷處理程式

在 OS 中,中斷處理程式伴演了火車頭的角色,幾乎所有的動作都是由一個中斷開始。由上面的討論,我們了解CuRT 的中斷由 irq_handler 開始,而這個函數會直接呼叫 irq_service_routine。

動腦時間: 想想看為什麼不直接呼叫 irq_service_routine 就好了呢? 提示,RISC 的指令長度是固定的。(ex1)

解釋一下 interrupt service routine 如何在不同模式間切換。

Scheduler

終於到我的專長了,我們來好好看一下 CuRT 的 scheduler。一般而言,scheduler 會在下面幾種狀況被執行。

  • thread 自己釋收 CPU
  • thread 被分配的時間用完了
  • thread 需要等待某一個資源

在 CuRT 中,第一個會呼叫 scheduler(SCHED_TIME_EXPIRE)。第二個會呼叫 schedule(SCHED_THREAD_REQUEST)。

本節所討論的程式都在 kernel/kernel.c 之中。

先看看第一種情況,這種情況基本上是由 exit_interrupt 呼叫進來的

void void exit_interrupt()
{
        if (interrupt_nesting > 0)
                interrupt_nesting–;

        if (current_thread->time_quantum <= 0) {
                schedule(SCHED_TIME_EXPIRE);
        }
}

在每一次 timer 中斷進來時,advanced_time_tick 會被呼叫一次。它會做二個事,第一件是檢查 delayed_list 中的 thread 是否需要喚醒。

        if (!is_empty_list(&delayed_list)) {
                for (pnode = begin_list(&delayed_list);
                     pnode != end_list(&delayed_list);
                     pnode = next_list(pnode) ) {
                        pthread = entry_list(pnode, thread_struct, node);
                        pthread->delayed_time–;
                        /* ready to change the status */
                        if (readyed_thread != NULL) {
                                delete_list(&readyed_thread->node);
                                readyed_thread->state = READY;
                                readyed_thread->time_quantum = TIME_QUANTUM;
                                insert_back_list(
                                        &ready_list[readyed_thread->prio],
                                        &readyed_thread->node);
                                prio_exist_flag[readyed_thread->prio] = true;
                                readyed_thread = NULL;
                        }
                        if (pthread->delayed_time <= 0) {
                                readyed_thread = pthread;
                        }
                }
                if (readyed_thread != NULL) {
                        delete_list(&readyed_thread->node);
                        readyed_thread->state = READY;
                        readyed_thread->time_quantum = TIME_QUANTUM;
                        insert_back_list(
                                &ready_list[readyed_thread->prio],
                                &readyed_thread->node);
                        prio_exist_flag[readyed_thread->prio] = true;
                }
        }

第二件事就是把目前 thread 被分配的時間減一。

        current_thread->time_quantum–;

當 time_quantim 變成 0 時 exit_interrupt 就會呼叫  scheduler。首先我們先取得目前優先權最高的 thread

        top_prio = get_top_prio();

get_top_prio()  這個函數會傳回除目目前 thread 外最高的優先權。假如沒有人至少和它一樣高,那就繼續執行目前的 thread。
 
               if (current_thread->prio < top_prio) {
                        current_thread->time_quantum = TIME_QUANTUM;
                        restore_cpu_sr(cpu_sr);
                        return;
                }
 
否則就取出目前最高的 thread

                pnode = delete_front_list(&ready_list[top_prio]);
                if (is_empty_list(&ready_list[top_prio]))
                        prio_exist_flag[top_prio] = false;

並將它設成執行狀態。

                next_thread = entry_list(pnode, thread_struct, node);
                next_thread->state = RUNNING;
                next_thread->time_quantum = TIME_QUANTUM;

並將原先的行程放回 ready_list 中。

                current_thread->state = READY;
                insert_back_list(&ready_list[current_thread->prio],
                                 &current_thread->node);
                prio_exist_flag[current_thread->prio] = true;
 
請注意 insert_back_list 這個動作。這就是為什麼這是一個 priority-based round-robin 的原因。每次 thread 都會被放回同一優先權串列最後面,所以在同一優先級中它就變成是最後一名了。

動腦時間: 怎麼樣把  scheduler 改成 FIFO 而不是 round-robin 呢?只要改一行,試試看吧。(ex1)

驅動程式

到這裡,整個 CuRT 己經幾乎看完了。

那有人可能問,driver 呢?

OS 是不一定要有 driver 的,CuRT 嚴格檢說是沒有一般定義的 driver 架構的。和其它的小型 OS 一樣,driver 只不過是一個應用程式,或是一個可以由應用程式呼叫的程式庫而己。用另一個角度來看,每一個應用程式也都可以拌演 driver 的角色。

目前為止,CuRT 唯一的 driver  是 serial driver。它被定義在 device/serial.c 之中。它太簡單了,就自己看吧。基本上它就是一個簡單的 UART 讀寫程式庫。

驅動程式另一個可能性是用一個 thread 完成。在這種情況下一個驅動程式通常是由一個 thread 和一個程式庫共同完成。thread 負責由硬體讀回或送出資料,程式庫則負責和應用程式溝通。舉個例說以 IDE 為例,我們可以產生一個 thread 負責將資料在硬體和 IDEInBlockList 和 IDEOutBlockList 中搬移。而應用程式可以始用程式庫內的函數在它自己的 context 中操作 IDEInBlockList 和 IDEOutBlockList 中的項目。當我們要謮一個 block 時

  • AP
    • read 函數會在 將IDEInBlockListLock 鎖住
    • 在IDEInBlockList中加入一個新的項目
    • IDEInBlockList 解鎖
    • 喚醒IDE thread 
  • IDE thread
    • 將IDEInBlockListLock 鎖住
    • 由 IDEInBlockList 中謮出一個項目
    • 將它傳換成適當的 IDE 命令送出
    • IDEInBlockList 解鎖
    • 等待 interrupt service routine 把它喚醒
  • Interrupt service routine
    • 如果是 IDE interrupt,喚醒 IDE thread
  • IDE thread
    • 將資料放入 IDEInBlockList 的適當項目中
    • 喚醒當初發出這個項目的 thread
  • AP thread
    • 得到一個由 IDE 中而來的資料

目前 CuRT 的 interrupt service routine 很簡單只有檢查 timer interrupt,

void interrupt_handler()
{
        if (INT_REG(INT_ICIP) & BIT26) {
                TMR_REG(TMR_OSCR) = 0×00;
                advance_time_tick();
                TMR_REG(TMR_OSSR) = BIT0;
        } else if (INT_REG(INTI_CIP) &BITIDE) {
                 thread_resume(IDE_thread);
        }
}

為了 IDE 我們必須加入一個新的檢查。在一般的作業系統我們會加入一個叫 register_irq 的函數讓驅動程式註冊自己的 interrupt service routine。CuRT 太簡單了,所以直接寫可能還更清楚一些。

動腦時間: 為 CuRT 加一個 IDE driver 如何?

IPC

[TBC....]

CuRT 還缺什麼

CuRT 雖然簡單但其實己經很有用,只要再加入下面的功能,基本上就是一個完整的 RTOS 了。至少,那來做 bootloader 是不錯的。

  • memory allocator
  • I2C/SPI driver
  • driver
  • IDE driver
  • simple C library

動腦時間: 為 CuRT 加入這些功能如何?

25 Jun 09 湖南红头文件雷死人鸟:女公务员需双乳对称无包块

朝廷女公务员的乳房,都是由张太监亲自鉴定过的

200906251607.jpg

湖南省在录用公务员的体检标准中要求女性乳房对称,在当年掀起铺天盖地的讨论。根据湖南省人事厅、湖南省卫生厅发布的红头文件《湖南省国家公务员录用体检项目和标准》,这项要求的全部内容为:第二性征发育正常,乳房对称、无包块,外阴无炎症、溃疡、肿瘤,无子宫脱垂,为合格。

近日有媒体报道称,济南一位湖南籍高校女毕业生小璐来电反映,湖南省在录用公务员的体检标准中,要求女性”第二性征发育正常,乳房对称无包块”等,这位女生认为这条规定明显带有”歧视性”。报道登出后,引起社会上一片非议。

对此,湖南省人事厅公务员管理处处长陈放民向记者证实,湖南录用公务员体检标准中的确有这样的规定,但这几年招录过程中并没有接到一例因为这个条件而被淘汰的反映,所以”原先也没觉得这是个问题”。

据透露,今年湖南公务员录用体检规定将做大的修改,目前省人事厅和卫生厅联同有关专家正在开展这项工作。陈放民说,虽然有些需改动的规定内容还未最终确定,但”可以肯定的是,‘女性双乳对称’这条规定将被废除”。

陈放民表示,做这个决定并非出于舆论的压力,而是由于以往招录公务员时已经感到这条规定没有什么存在的必要。”这次修改放宽了很多条件,关键是使公务员招录工作体现得更加人性化,从而有利于让真正的人才进入到公务员的队伍中来。”

Tags:

25 Jun 09 关于SIGPIPE导致的程序退出

收集一些网上的资料,以便参考:

http://.chinaunix.net/u2/69143/showart_1087349.

当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个信号给进程,告诉进程这个连接已经断开了,不要再写了。
根据信号的默认处理规则信号的默认执行动作是terminate(终止、退出),所以client会退出。若不想客户端退出可以把设为SIG_IGN

如: signal(,SIG_IGN);
这时交给了系统处理。

服务器采用了fork的话,要收集垃圾进程,防止僵尸进程的产生,可以这样处理:
signal(SIGCHLD,SIG_IGN); 交给系统init去回收。
这里子进程就不会产生僵尸进程了。

http://www.cublog.cn/u/31357/showart_242605.

好久没做过C开发了,最近重操旧业。
听说另外一个项目组开发遇到问题,发送端和接受端数据大小不一致。建议他们采用writen的重发机制,以避免信号中断错误。采用后还是有问题。PM让我帮忙研究下。
UNP n年以前看过,很久没做过底层开发,手边也没有UNP vol1这本书,所以做了个测试程序,研究下实际可能发生的情况了。

测试环境:AS3和redhat 9(缺省没有nc)

先下载unp源码:
wget http://www.unpbook.com/unpv13e.tar.gz
tar xzvf *.tar.gz;
configure;make lib.
然后参考str_cli.c和tcpcli01.c,写了测试代码client.c

#include “unp.h”

#define MAXBUF 40960
void processSignal(int signo)
{
printf(”Signal is %d\n”, signo);
signal(signo, processSignal);
}
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXBUF], recvline[MAXBUF];

while (1) {

memset(sendline, ‘a’, sizeof(sendline));
printf(”Begin send %d data\n”, MAXBUF);
Writen(sockfd, sendline, sizeof(sendline));
sleep(5);

}
}

int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;

signal(, SIG_IGN);
//signal(, processSignal);

if (argc != 2)
err_quit(”usage: tcpcli [port]“);

sockfd = (AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[1]));
Inet_pton(AF_INET, “127.0.0.1″, &servaddr.sin_addr);

Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

str_cli(stdin, sockfd); /* do it all */

exit(0);
}

为了方便观察错误输出,lib/writen.c也做了修改,加了些日志:

/* include writen */
#include “unp.h”

ssize_t /* Write “n” bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;

ptr = vptr;
nleft = n;
while (nleft > 0) {
printf(”Begin Writen %d\n”, nleft);
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR) {
printf(”intterupt\n”);
nwritten = 0; /* and call write() again */
}
else
return(-1); /* error */
}

nleft -= nwritten;
ptr += nwritten;
printf(”Already write %d, left %d, errno=%d\n”, nwritten, nleft, errno);
}
return(n);
}
/* end writen */

void
Writen(int fd, void *ptr, size_t nbytes)
{
if (writen(fd, ptr, nbytes) != nbytes)
err_sys(”writen error”);
}

client.c放在tcpclieserv目录下,修改了Makefile,增加了client.c的编译目标

client: client.c
${CC} ${CFLAGS} -o $@ $< ${LIBS}

接着就可以开始测试了。

测试1 忽略信号,writen之前,对方关闭接受进程

本机服务端:

nc -l -p 30000

本机客户端:
./client 30000
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
执行到上步停止服务端,client会继续显示:
Begin send 40960 data
Begin Writen 40960
writen error: Broken pipe(32)
结论:可见write之前,对方中断,发送端write会返回-1,errno号为EPIPE(32)
测试2 catch 信号,writen之前,对方关闭接受进程

修改客户端代码,catch 信号

//signal(, SIG_IGN);

signal(, processSignal);
本机服务端:

nc -l -p 30000

本机客户端:
make client
./client 30000
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
Begin send 40960 data
Begin Writen 40960
Already write 40960, left 0, errno=0
执行到上步停止服务端,client会继续显示:
Begin send 40960 data
Begin Writen 40960
Signal is 13
writen error: Broken pipe(32)
结论:可见write之前,对方中断,发送端write时,会先调用响应函数,然后write返回-1,errno号为EPIPE(32)

测试3 writen过程中,对方关闭接受进程

为了方便操作,加大1次write的数据量,修改MAXBUF为4096000

本机服务端:

nc -l -p 30000

本机客户端:
make client
./client 30000
Begin send 4096000 data
Begin Writen 4096000
执行到上步停止服务端,client会继续显示:
Already write 589821, left 3506179, errno=0
Begin Writen 3506179
writen error: Connection reset by peer(104)

结论:可见 write中,对方中断,发送端write会先返回已经发送的字节数,再次write时返回-1,errno号为ECONNRESET(104)
为什么以上测试,都是对方已经中断后,发送端再次write,结果会有所不同呢。从后来找到的UNP5.12,5.13能找到答案

The client’s call to readline may happen before the server’s RST is received by the client, or it may happen after. If the readline happens before the RST is received, as we’ve shown in our example, the result is an unexpected EOF in the client. But if the RST arrives first, the result is an ECONNRESET (”Connection reset by peer”) error return from readline.
以上解释了测试3的现象,write时,收到RST.

What happens if the client ignores the error return from readline and writes more data to the server? This can happen, for example, if the client needs to perform two writes to the server before reading anything back, with the first write eliciting the RST.
The rule that applies is: When a process writes to a that has received an RST, the signal is sent to the process. The default action of this signal is to terminate the process, so the process must catch the signal to avoid being involuntarily terminated.
If the process either catches the signal and returns from the signal handler, or ignores the signal, the write operation returns EPIPE.
以上解释了测试1,2的现象,write一个已经接受到RST的,系统内核会发送给发送进程,如果进程catch/ignore这个信号,write都返回EPIPE错误.

因此,UNP建议应用根据需要处理信号,至少不要用系统缺省的处理方式处理这个信号,系统缺省的处理方式是退出进程,这样你的应用就很难查处处理进程为什么退出。

http://.csdn.net/shcyd/archive/2006/10/28/1354577.aspx

在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个信号,进程对该信号的默认处理是进程终止。
在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个信号,进程对该信号的默认处理是进程终止。

处理方法:
在初始化时调用signal(,SIG_IGN)忽略该信号(只需一次)
其时send或recv函数将返回-1,errno为EPIPE,可视情况关闭或其他处理

gdb:
gdb默认收到时中断程序,可调用handle nostop print

相关

(1)SIG_DFL信号专用的默认动作:
(a)如果默认动作是暂停线程,则该线程的执行被暂时挂起。当线程暂停期间,发送给线程的任何附加信号都不交付,直到该线程开始执行,但是SIGKILL除外。
(b)把挂起信号的信号动作设置成SIG_DFL,且其默认动作是忽略信号 (SIGCHLD)。

(2)SIG_IGN忽略信号
(a)该信号的交付对线程没有影响
(b)系统不允许把SIGKILL或SIGTOP信号的动作设置为SIG_DFL

(3)指向函数的指针–捕获信号
(a)信号一经交付,接收线程就在指定地址上执行信号捕获程序。在信号捕 获函数返回后,接受线程必须在被中断点恢复执行。
(b)用C语言函数调用的方法进入信号捕捉程序:
void func (signo)
int signo;
func( )是指定的信号捕捉函数,signo是正被交付信号的编码
(c)如果SIGFPE,SIGILL或SIGSEGV信号不是由C标准定义的kill( )或raise( )函数所生成,则从信号SIGFPE,SIGILL,SIGSEGV的信号捕获函数正常返回后线程的行为是未定义的。
(d)系统不允许线程捕获SIGKILL和SIGSTOP信号。
(e)如果线程为SIGCHLD信号建立信号捕获函数,而该线程有未被等待的以终止的子线程时,没有规定是否要生成SIGCHLD信号来指明那个子线程。

每一种信号都被OSKit给予了一个符号名,对于32位的i386平台而言,一个字32位,因而信号有32种。下面的表给出了常用的符号名、描述和它们的信号值。

符号名  信号值 描述                是否符合POSIX
SIGHUP  1   在控制终端上检测到挂断或控制线程死亡  是
SIGINT  2   交互注意信号              是
SIGQUIT  3   交互中止信号              是
SIGILL  4   检测到非法硬件的指令          是
SIGTRAP  5   从陷阱中回朔              否
SIGABRT  6   异常终止信号              是
SIGEMT  7   EMT 指令                否
SIGFPE  8   不正确的算术操作信号          是
SIGKILL  9   终止信号                是
SIGBUS  10   总线错误                否
SIGSEGV  11   检测到非法的内存调用          是
SIGSYS  12   系统call的错误参数           否
  13   在无读者的管道上写           是
SIGALRM  14   报时信号                是
SIGTERM  15   终止信号                是
SIGURG  16   IO信道紧急信号             否
SIGSTOP  17   暂停信号                是
SIGTSTP  18   交互暂停信号              是
SIGCONT  19   如果暂停则继续             是
SIGCHLD  20   子线程终止或暂停            是
SIGTTIN  21   后台线程组一成员试图从控制终端上读出  是
SIGTTOU  22   后台线程组的成员试图写到控制终端上   是
SIGIO   23   允许I/O信号               否
SIGXCPU  24   超出CPU时限               否
SIGXFSZ  25   超出文件大小限制            否
SIGVTALRM 26   虚时间警报器              否
SIGPROF  27   侧面时间警报器             否
SIGWINCH 28   窗口大小的更改             否
SIGINFO  29   消息请求                否
SIGUSR1  30   保留作为用户自定义的信号1        是
SIGUSR2  31   保留作为用户自定义的信号        是

注意:Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做”不可靠信号”,信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。这就是”不可靠信号”的来源。它的主要问题是:进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。

另外,我再做一些补充,产生RST响应以至于系统发出信号,应该分为两种情况:

1. 客户端到服务端之间网络断掉,或者服务端断电等,物理连接断掉了,这种情况下客户端不会退出,send函数正常执行,不会感觉到自己出错。因为由于物理网络断开,服务端不会给客户端回应错误消息,没有RST响应,自然也不会产生信号。但是当服务端再恢复正常的时候,对客户端send来的消息会产生RST响应,客户端就收到信号了,程序退出,但是这时send函数是能够返回 -1的。可以进行异常处理。

2.客户端到服务端的网络能通,服务程序挂掉,客户端程序会马上退出,因为服务端能正常返回错误消息,客户端收到,信号就产生了。不过我不确定此时服务端返回是的RST响应,抓包来看没有RST标志。水平有限,只写到这了。

Tags: ,

25 Jun 09 你最讨厌听到上司/下属对你说

收藏在此,用来提醒自己。

转自:http://www.jobsdigg.com/story/631

下属最反感上司这么说

1 遇到问题,老板说:“怎么做我不管,我就要看到结果,具体的自己想办法。”(支持率:29%)

2 老板不发钱,说:“钱不重要,前途很重要。”(支持率:21%)

3 老板不知道自己在忙,还说:“这事儿你去做吧,反正闲着也是闲着。”(支持率:15%)

4 事情做完以后,老板说:“这事先放放。”(支持率:11%)

5 在拖欠工资的情况下,老板说,“希望大家都高风亮节一点。”(支持率:10%)

6 家在外地,工作在北京。老板说:“既然家那么远,干吗还要回家过年?”(支持率:9%)

7 总在快下班时下新任务:这个事很急,今天要辛苦一下。(支持率:5%)

8. 遇到经济危机,老板说,“你干得最好,觉悟最好,你带头降薪吧!”

9. 点灯熬油做出来的东西,领导只是随便看了一眼就说:“你做的这些有什么意义呢?”

10.“家就在本市,还年休干吗?有事请假一天,回家办完事就回来上班好了。”

11. 领导自己啃着饼干,然后对我们说:“今晚加班!我和你们一起饿着。”

12. 下班了,领导让去离公司十公里远的地方送文件,问他:“那我下班可以不打卡了吧?”回答说:“你送完文件还有事吗?”我说:“没事,但是家在那边。”领导也没抬头:“既然没事,那就回来打一下卡再回去,不打卡不行。”

13.“我说你听,不准反驳,对错好坏都照着做,公司倒了跟你没关系。”

14. 总不加工资,领导的理由是:“在我们公司能学到很多东西,是不能用钱计算的。”

15. 不给加班费也就忍了,还说:“多加会儿班又死不了人!”

16. 头一天已经告诉我不用办的事情,第二天领导说起这件事后却说:“怎么告诉你的事情还没有办好,办什么事情都办不好!”

17. 想当初,我们那会儿工作多努力、多积极……

18.领导天天开公车去接自己家孩子。却跟有了小孩的员工说:“你怎么还不把孩子送回老家去?”

19. 遇到问题,领导说自己想办法,事情做完了,他又说“谁让你那么做的”,然后劈头盖脸地一顿训斥。

20. 最恨领导不拿我们这些销售当人,说:“哪怕你们去卖身也要完成任务。”

21. 活儿真的太多了,筋疲力尽,结果领导说:“八宝山(墓地)里没有累死的人。”

22. 我作为工程师但只拿着技术员的薪水。一次领导在训人的时候说:“你们这些工程师,拿着这么多薪水,只做技术员的事。”

23. 这家公司糟糕到不能给员工一张好一点的凳子,领导却还振振有词:“只要你有能力,公司是不会亏待你的!”

24.“没事星期天就不要休息了。”

25. “你跟别人的差别就是天上地下,你活着有什么用?”

26.“去把我这个衣服洗洗,要做保姆式的行政。”

27. 领导这天让我包一个礼品,正在进行中,他过了一会突然说:“你做这个干什么?谁让你做的,去把资料拿来。”

28.“出了事你负得起责任么? 狗也比你听话。”

29. 长期拖欠工资,老板却还说:“你们把钱存银行有什么用。”

30.“你们没权利跟我谈条件,你有本事你可以不来。”

31. 领导不需要脑子,只需要一双干活儿的手,他总说:“别问为什么,没什么为什么。”

32.“你的工资最高,但你的工作是最好做的。”

33. 领导总认为要加班才是好员工,我最讨厌他说:“你怎么一下班就走?”

34. 真是费了九牛二虎之力完成了领导的难题。领导说:“看,学到不少东西,得到锻炼了吧!”

35. 有个任务需要花钱,去找领导,他说:“钱没有,有钱这事情还用你们办?”

36. 事情稍微没做好,领导就说:“就你这点事,随便找个人都可以顶替你。”

37.“为什么不来加班?这么好的工作机会你竟然不珍惜?!”

38.“你创造的价值不够国家培养你的价值。”

39.“你不要以为自己了不起,又不是在造原子弹尖尖。”

40. 在要求兑现领导之前说过的年终奖问题时他说,“我以前是说过,以前是以前,现在是现在,以前错了现在还不是要改过来?”

上司最反感下属这么说

1 他分内的事情,过问了几句,都是一个回答:“我不知道。”(支持率:31%)

2 出了问题后说:“这个事情不是我的问题,是他的问题。” (支持率:30%)

3 拿来一个和讨论结果不一样的方案,说:“我做的时候觉得这样更好,所以就这么做了。”(支持率:10%)

4 事情办坏了,只会说:“这个事情我也不知道怎么变成这样了。” (支持率:9%)

5 遇到所有问题,都问老板,“这个问题怎么办?” (支持率:7%)

6 老板说,“我最怕员工不跟我说话,坐在那里一直沉默。其实只要说话就好办,什么话最讨厌倒是没想过。” (支持率:7%)

7 “我以为能搞定这事儿,所以就一直没说,想搞定了后再告诉你的。”(支持率:6%)

8.“这事儿是我一个人做错的么?”

9. 错误已经摆在眼前了,还在说:“我认为我做的是对的。”

10. 最雷人的是这种:“MD,老子不想干了,老子走了哈,爷不伺候了。”

11. 事儿没做好,还反问我:“谁让你不说清楚咧。”

12. 事情做错了,所以说了几句,员工说,“既然嫌这嫌那,要不您再找个明白人吧。”

13.“你没跟我说,所以我没做。”那我也没跟你说每天几点要吃饭,怎么就知道什么时候是饭点儿?

14. 当一件事与我预期的发展不符,我去找员工探讨应该如何改进的时候,下属的第一个反应是:“我以前就这么做的,没看到有什么不对啊。”

15. 明明事情没做对,却要说,“你是这么跟我说的,所以我才这么做的。”

16. 听过员工最讨厌的一句话是:“有本事自己弄。”简直……

17. 拿着不是还当理说,跟我说:“这件事不需要做得那么细。”

18.“老板你给我们加工资吧。”最烦这种,只会索取,不会奉献。

19. 员工就应该尽员工的义务,却无意听见他们私下的抱怨:“什么事情都交给我们做。”

20. 只会嘴上功夫,一口一个“知道了”,但是就是不去执行。

21. 任务执行上出了问题,员工说:“这个事情,我交代过他的。”

22.“反正我就这样,大不了我走人!”

23. 事情没有按照既定方案走,员工说,“这事儿我本来是想这样做的,但是合作方又说要那样做,所以就……”

24. 两个人承担一项工作,出了错误,员工说,“是他没有做好,所以我才没有做好。”

25.“为什么又是我去做,怎么不叫他们去?”

26. 太拿领导不当外人儿,说:“老板我明天不想上班了,让别人替吧。”

27.“就给这点钱,还想让我干这干那。”

28. 捅了娄子,所以说了他几句,没想到他说,“说白了你不也是一个打工的么,有什么了不起。”

29. 布置了一个任务,的确不容易,但正是这样,才需要大家一条心,振奋精神,可是员工还没做就说,“这件事情不可能完成,肯定不行。”

30. 推卸责任推卸到领导头上来了,说,“这个我不懂,你也不懂。如果你懂事情不会发生。”

31. 事情办坏了,有很多的理由:“这个事情是因为……我本来是想……结果变成……要是如果……”真想和这样的人说一句:“要是如果没有你该多好啊!”

32. 问下属事办得怎么样了?(实际还没办呢)他的回复是:“快了,快办好了!”

33. 某件该做的事情没做,跟他说后,犹如恍然大悟一般:“原来这也是我的工作啊,怎么这么多啊。”

34.“这个问题我不知道,是前任遗留的。”

35.“我只是个员工,很多事情是需要主管去做的,我做不了。”

36. 事情办坏了,我正好有事不在,我的上级(老总)问我的下级(主管)是什么原因?下级说:“是按经理的要求做的。”

37. 最不喜欢员工把“个人矛盾”转化为“群体矛盾”,遇到问题就说:“大家都是这样做的啊。”

38. 有个员工特别振振有词地跟我说,“我为公司带来了利益,但我不明白公司为我带来了什么。”

39. 不会在事后思索一下怎么在下次把事情做得更好,而是说,“换作别人做可能会比我做得更差。”

40. 刚毕业来我们公司应聘工作的一个人说:“不要我是你们公司的损失。”

Tags:

25 Jun 09 用数据说话,看Google 怎样被陷害[转载]

转自:http://.sina.com.cn/s/blog_60676a3f0100e0xk.

近日,央视爆出谷歌搜索出现大量黄色词条的信息。一个引起舆论强烈反响的例子是,在谷歌搜索“儿子”竟然也能搜索到黄色词条。那么,事情是怎么发生的呢?


下面我们来看谷歌是如何被陷害的:众所周知,谷歌关键词提醒是计算机自动摘取最近最流行的关键词来生成的。于是某些人利用这一点,大量在谷歌上搜索黄色词汇,陷害了谷歌。

在谷歌搜索趋势图,Google Insights for Search,以及一些第三方的统计数据中,可以看到:

在央视曝光谷歌之前7天:

1.有人故意在谷歌大量搜索黄色词汇,使单日黄色词汇搜索量同比猛增 5950% ,单月
搜索总量与上月相比增幅达数千倍
2.
这些搜索量100%来自北京
3.
这些搜索量几乎呈线性急剧上升,理论上这些瞬时搜索量应该服从正态分布并是突发性
的,换句话说,这是有人故意为之。

以下再附上几张类似图表,请注意峰值全部在6月17日,即CCTV节目(6月18日)播出的前一天。

(全年统计)

(本月统计)

为做对比,说明搜索引擎的统计应该是什么样子,我来附上一张对关键词“天气预报”的搜索统计图表,从图中我们可以看到,全年搜索量应该大致呈均匀分布,考虑到搜索引擎的普及使用,会有一个逐渐升高的趋势,但绝不可能出现在某个月份呈直线上升的情况。

那么,还有一种可能,是不是北京的人们在6月份,由于夏天到来,荷尔蒙分泌过多,导致对“儿子母亲不正当关系”这样的黄色词汇搜索过多呢?我们且来看这张对关键词“日本女优”的搜索统计图表,

可以看到,对关键词“日本女优”的搜索量全年大致呈均匀分布,甚至在近期有下降的趋势。那么,这种近期全民荷尔蒙分泌过多的情况也应该被排除了。并不是说对所有黄色信息都有大量的搜索需求。搜索数量呈急剧上升的关键词,只局限在媒体大书特书的几个词汇之中,特别要注意的是其急剧上升阶段和峰值都在媒体报道之前,显然,这不是自然的结果,那么,答案是什么呢?是谁让谷歌如此低俗?

作者语:根据上图,只有北京有这种情况,那么 ok ,把北京高压管制,何必去干扰其它不相关的城市。CCAV习惯造假,“高也事件”,“Google涉黄事件”,继政府之后,媒体也不可信了。

Tags: , ,

24 Jun 09 传说神农尝百草,传说牛人尝试剂

  HCl 稀:比较酸,感觉嘴里滑溜溜的,典型的呕吐物感,微辣
  浓:极度的酸,吐掉以后回味苦,然后整个嘴里发凉,10分钟后好转
  
  H2SO4 稀:淡淡的酸味,回味感觉油腻,微热,甜,无任何不适感
  较浓的(40%左右的):超烫,感觉喝烫稀饭了,然后微甜感和痛感并存,
  持续2天才退(98%的纯正浓硫酸不敢喝)
  
  HNO3 稀:先是苦,然后整条舌头麻了,然后痛,起了白斑,持续疼痛,3-4天后消退,
  同时嘴里感觉大吸了一口汽车尾气
  浓:不敢喝 (猜测是浓硫酸的加强版)
  
  NaOH 稀:基本上同浓的Na2CO3(我尝过,咸的),多一些辣感(对蛋白质腐蚀性强的都
  会有辣感 )
  浓:含在嘴里十分的辣(可能是已经反应起来了) 然后舌头烧坏,呈黄色,肉腐
  烂,1个月不能说话,口里有赤痛感而且舌头麻木 有辛辣感半年后出院,说话变得不准,味
  觉几乎消失,嘴部留下疤痕(这东西对蛋白质的反应不是闹着玩的……)
  
  CuSO4 一开始没味道,吐出后回味淡淡的苦涩(我的确尝过)
  
  BaCl2:极苦咸,大约相当于MgCl2的加强版
  
  CCl4:这个最恐怖了,整个嘴里感到烧塑料的味道,极浓郁,吐掉以后出现说不出的怪
  异甜味,直感觉全身松软 (的确,闻起来还可以,尝起来就郁闷了)
  
  Na2O2:一般的咸 (Na盐基本都这个味道)
  
  无水酒精:嘴里完全没味道,之后花露水的味道在鼻子里挥之不去
  
  FeCl3:凉,然后酸,与硬币放嘴里感觉差不多(Fe盐都这味道)
  
  AgNO3:没味道。。。
  
  稀Br2水溶液:极其浓重味道,感觉像汽车尾气与松节油混合的味(只能如此形容)
  
  Hg(NO3)2:很淡的味道,有点像味精和醋混合了
  
  H2O2:特辣,赶紧吐了,之后就没什么事情了