2019年8月18日 星期日

Linux 信號signal處理機制

Source: http://myblog-maurice.blogspot.com/2011/12/linux-signal.html

信號機制是進程之間相互傳遞消息的一種方法,信號全稱為軟中斷信號,也有人稱作軟中斷。從它的命名可以看出,它的實質和使用很象中斷。所以,信號可以說是進程控制的一部分。
        一、信號的基本概念
            1、基本概念
        軟中斷信號(signal,又簡稱為信號)用來通知進程發生了非同步事件。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因為內部事件而給進程發送信號,通知進程發生了某個事件。注意,信號只是用來通知某進程發生了什麼事件,並不給該進程傳遞任何資料。
        收 到信號的進程對各種信號有不同的處理方法。處理方法可以分為三類:第一種是類似中斷的處理常式,對於需要處理的信號,進程可以指定處理函數,由該函數來處 理。第二種方法是,忽略某個信號,對該信號不做任何處理,就象未發生過一樣。第三種方法是,對該信號的處理保留系統的預設值,這種缺省操作,對大部分的信 號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行為。
        在進程表的表項中有一個軟中斷信號域,該域中每一位元對應一個信號,當有信號發送給進程時,對應位置位元。由此可以看出,進程對不同的信號可以同時保留,但對於同一個信號,進程並不知道在處理之前來過多少個。
        2、信號的類型
        發出信號的原因很多,這裡按發出信號的原因簡單分類,以瞭解各種信號:
        (1) 與進程終止相關的信號。當進程退出,或者子進程終止時,發出這類信號。
        (2) 與進程例外事件相關的信號。如進程越界,或企圖寫一個唯讀的記憶體區域(如程式正文區),或執行一個特權指令及其他各種硬體錯誤。
        (3) 與在系統調用期間遇到不可恢復條件相關的信號。如執行系統調用exec時,原有資源已經釋放,而目前系統資源又已經耗盡。
        (4) 與執行系統調用時遇到非預測錯誤條件相關的信號。如執行一個並不存在的系統調用。
        (5) 在使用者態下的進程發出的信號。如進程調用系統調用kill向其他進程發送信號。
        (6) 與終端交互相關的信號。如使用者關閉一個終端,或按下break鍵等情況。
        (7) 跟蹤進程執行的信號。
      

值
名 字
說明
01
SIGHUP
掛起(hangup)
02
SIGINT
中斷,當使用者從鍵盤按ctrl+c鍵
03
SIGQUIT
退出,當使用者從鍵盤按quit鍵時
04
SIGILL
非法指令
05
SIGTRAP
跟蹤陷阱(trace trap),啟動進程,跟蹤代碼的執行
06
SIGIOT
IOT指令
07
SIGEMT
EMT指令
08
SIGFPE
浮點運算溢出
09
SIGKILL
殺死、終止進程
10
SIGBUS
匯流排錯誤
11
SIGSEGV
段違例(segmentation? violation),進程試圖去訪問其虛位址空間以外的位置
12
SIGSYS
系統調用中參數錯,如系統調用號非法
13
SIGPIPE
向某個非讀管道中寫入資料
14
SIGALRM
鬧鐘。當某進程希望在某時間後接收信號時發此信號
15
SIGTERM
軟體終止(software? termination)
16
SIGUSR1
使用者自訂信號1
17
SIGUSR2
使用者自訂信號2
18
SIGCLD
某個子進程死
19
SIGPWR
電源故障

        注意 信號SIGKILL和SIGSTOP既不能被捕捉,也不能被忽略。信號SIGIOT與SIGABRT是一個信號。可以看出,同一個信號在不同的系統中值可能不一樣,所以建議最好使用為信號定義的名字,而不要直接使用信號的值。
二、有關信號的系統調用
     系統調用signal是進程用來設定某個信號的處理方法,系統調用kill是用來發送信號給指定進程的。這 兩個調用可以形成信號的基本操作。後兩個調用pause和alarm是通過信號實現的進程暫停和計時器,調用alarm是通過信號通知進程計時器到時。所 以在這裡,我們還要介紹這兩個調用。
        1、signal 系統調用
        系統調用signal用來設定某個信號的處理方法。該調用聲明的格式如下:
        void (*signal(int signum, void (*handler)(int)))(int);
        在使用該調用的進程中加入以下頭檔:
        #include
        上述聲明格式比較複雜,如果不清楚如何使用,也可以通過下面這種類型定義的格式來使用(POSIX的定義):
        typedef void (*sighandler_t)(int);
        sighandler_t signal(int signum, sighandler_t handler);
        但這種格式在不同的系統中有不同的類型定義,所以要使用這種格式,最好還是參考一下連線手冊。
        在調用中,參數signum指出要設置處理方法的信號。第二個參數handler是一個處理函數,或者是
        SIG_IGN:忽略參數signum所指的信號。
        SIG_DFL:恢復參數signum所指信號的處理方法為預設值。
        傳遞給信號處理常式的整數參數是信號值,這樣可以使得一個信號處理常式處理多個信號。系統調用signal返回值是指定信號signum前一次的處理常式或者錯誤時返回錯誤代碼SIG_ERR。下面來看一個簡單的例子:
        #include
        #include
        #include
        void sigroutine(int dunno) { /* 信號處理常式,其中dunno將會得到信號的值 */
        switch (dunno) {
        case 1:
        printf("Get a signal -- SIGHUP ");
        break;
        case 2:
        printf("Get a signal -- SIGINT ");
        break;
        case 3:
        printf("Get a signal -- SIGQUIT ");
        break;
        }
        return;
        }
        int main() {
        printf("process id is %d ",getpid());
        signal(SIGHUP, sigroutine); //* 下面設置三個信號的處理方法
        signal(SIGINT, sigroutine);
        signal(SIGQUIT, sigroutine);
        for (;;) ;
        }
        其中信號SIGINT由按下Ctrl-C發出,信號SIGQUIT由按下Ctrl-發出。該程式執行的結果如下:
        localhost:~$ ./sig_test
        process id is 463
        Get a signal -SIGINT //按下Ctrl-C得到的結果
        Get a signal -SIGQUIT //按下Ctrl-得到的結果
        //按下Ctrl-z將進程置於後臺
        [1]+ Stopped ./sig_test
        localhost:~$ bg
        [1]+ ./sig_test &
        localhost:~$ kill -HUP 463 //向進程發送SIGHUP信號
        localhost:~$ Get a signal – SIGHUP
        kill -9 463 //向進程發送SIGKILL信號,終止進程
        localhost:~$
        2、kill 系統調用
        系統調用kill用來向進程發送一個信號。該調用聲明的格式如下:
        int kill(pid_t pid, int sig);
        在使用該調用的進程中加入以下頭檔:
        #include
        #include
        該 系統調用可以用來向任何進程或進程組發送任何信號。
如果參數pid是正數,那麼該調用將信號sig發送到進程號為pid的進程。
如果pid等於0,那麼信 號sig將發送給當前進程所屬進程組裡的所有進程。
如果參數pid等於-1,信號sig將發送給除了進程1和自身以外的所有進程。
如果參數pid小於- 1,信號sig將發送給屬於進程組-pid的所有進程。如果參數sig為0,將不發送信號。該調用執行成功時,返回值為0;錯誤時,返回-1,並設置相應 的錯誤代碼errno。下面是一些可能返回的錯誤代碼:
        EINVAL:指定的信號sig無效。
        ESRCH:參數pid指定的進程或進程組不存在。注意,在進程表項中存在的進程,可能是一個還沒有被wait收回,但已經終止執行的僵死進程。
        EPERM: 進程沒有權力將這個信號發送到指定接收信號的進程。因為,一個進程被允許將信號發送到進程pid時,必須擁有root權力,或者是發出調用的進程的UID 或EUID與指定接收的進程的UID或保存使用者ID(savedset-user-ID)相同。如果參數pid小於-1,即該信號發送給一個組,則該錯誤 表示組中有成員進程不能接收該信號。
        3、pause系統調用
        系統調用pause的作用是等待一個信號。該調用的聲明格式如下:
        int pause(void);
        在使用該調用的進程中加入以下頭檔:
        #include
        該調用使得發出調用的進程進入睡眠,直到接收到一個信號為止。該調用總是返回-1,並設置錯誤代碼為EINTR(接收到一個信號)。下面是一個簡單的範例:
        #include
        #include
        #include
        void sigroutine(int unused) {
        printf("Catch a signal SIGINT ");
        }
        int main() {
        signal(SIGINT, sigroutine);
        pause();
        printf("receive a signal ");
        }
        在這個例子中,程式開始執行,就象進入了閉環一樣,這是因為進程正在等待信號,當我們按下Ctrl-C時,信號被捕捉,並且使得pause退出等候狀態。
4、alarm和 setitimer系統調用
        系統調用alarm的功能是設置一個計時器,當計時器計時到達時,將發出一個信號給進程。該調用的聲明格式如下:
        unsigned int alarm(unsigned int seconds);
        在使用該調用的進程中加入以下頭檔:
        #include
        系 統調用alarm安排內核為調用進程在指定的seconds秒後發出一個SIGALRM的信號。如果指定的參數seconds為0,則不再發送 SIGALRM信號。後一次設定將取消前一次的設定。該調用返回值為上次定時調用到發送之間剩餘的時間,或者因為沒有前一次定時調用而返回0。
        注意,在使用時,alarm只設定為發送一次信號,如果要多次發送,就要多次使用alarm調用。
        對於alarm,這裡不再舉例。現在的系統中很多程式不再使用alarm調用,而是使用setitimer調用來設置計時器,用getitimer來得到計時器的狀態,這兩個調用的聲明格式如下:
        int getitimer(int which, struct itimerval *value);
        int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
        在使用這兩個調用的進程中加入以下頭檔:
        #include
        該系統調用給進程提供了三個計時器,它們各自有其獨有的計時域,當其中任何一個到達,就發送一個相應的信號給進程,並使得計時器重新開始。三個計時器由參數which指定,如下所示:
        TIMER_REAL:按實際時間計時,計時到達將給進程發送SIGALRM信號。
        ITIMER_VIRTUAL:僅當進程執行時才進行計時。計時到達將發送SIGVTALRM信號給進程。
        ITIMER_PROF:當進程執行時和系統為該進程執行動作時都計時。與ITIMER_VIR-TUAL是一對,該計時器經常用來統計進程在使用者態和內核態花費的時間。計時到達將發送SIGPROF信號給進程。
        計時器中的參數value用來指明計時器的時間,其結構如下:
        struct itimerval {
        struct timeval it_interval; /* 下一次的取值 */
        struct timeval it_value; /* 本次的設定值 */
        };
        該結構中timeval結構定義如下:
        struct timeval {
        long tv_sec; /* 秒 */
        long tv_usec; /* 微秒,1秒 = 1000000 微秒*/
        };
        在setitimer 調用中,參數ovalue如果不為空,則其中保留的是上次調用設定的值。計時器將it_value遞減到0時,產生一個信號,並將it_value的值設 定為it_interval的值,然後重新開始計時,如此往復。當it_value設定為0時,計時器停止,或者當它計時到期,而it_interval 為0時停止。調用成功時,返回0;錯誤時,返回-1,並設置相應的錯誤代碼errno:
        EFAULT:參數value或ovalue是無效的指針。
        EINVAL:參數which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一個。
        下面是關於setitimer調用的一個簡單示範,在該例子中,每隔一秒發出一個SIGALRM,每隔0.5秒發出一個SIGVTALRM信號:
        #include
        #include
        #include
        #include
        int sec;
        void sigroutine(int signo) {
        switch (signo) {
        case SIGALRM:
        printf("Catch a signal -- SIGALRM ");
        break;
        case SIGVTALRM:
        printf("Catch a signal -- SIGVTALRM ");
        break;
        }
        return;
        }
        int main() {
        struct itimerval value,ovalue,value2;
        sec = 5;
        printf("process id is %d ",getpid());
        signal(SIGALRM, sigroutine);
        signal(SIGVTALRM, sigroutine);
        value.it_value.tv_sec = 1;
        value.it_value.tv_usec = 0;
        value.it_interval.tv_sec = 1;
        value.it_interval.tv_usec = 0;
        setitimer(ITIMER_REAL, &value, &ovalue);
        value2.it_value.tv_sec = 0;
        value2.it_value.tv_usec = 500000;
        value2.it_interval.tv_sec = 0;
        value2.it_interval.tv_usec = 500000;
        setitimer(ITIMER_VIRTUAL, &value2, &ovalue);
        for (;;) ;
        }
        該例子的螢幕拷貝如下:
        localhost:~$ ./timer_test
        process id is 579
        Catch a signal – SIGVTALRM
        Catch a signal – SIGALRM
        Catch a signal – SIGVTALRM
        Catch a signal – SIGVTALRM
        Catch a signal – SIGALRM
        Catch a signal –GVTALRM
三、參考代碼
參考 1.
#include

char buf[]={"check lock!\n"};
main()
{
        int i,p1,p2,fd;
        fd=creat("lock.dat",0644);
        write(fd,buf,20);
        while((p1=fork())==-1);
        if(p1==0)
        {
                lockf(fd,1,0);
                for (i=1;i<=3;i++)
                        printf("child1!\n");
                lockf(fd,0,0);
        }
        else{while((p2=fork())==-1);
        if (p2==0)
        {
                lockf(fd,1,0);
                for (i=1;i<=4;i++)
                        printf("child2!\n");
                lockf(fd,0,0);
        }
        else printf("parrent!\n");
        }
        close(fd);
}

參考 2
#include
#include
#include

int waite;

static void start(){
   waite=0;
}

//自訂中斷調用函數
static void waiting(){
   while(waite==1);
}

main(){
   int pid1,pid2;
   while((pid1=fork())==-1);           
   if(pid1>0){
      printf("chilld process 1 is %d\n",pid1);
      while((pid2=fork())==-1);
      if(pid2>0){
         printf("child process 2 is %d\n",pid2);
         printf("please press 'delete'\n");
         waite=1;
         if(signal(SIGUSR1,start)==SIG_ERR);
         else{
            alarm(5);
            signal(SIGALRM,start);//alarm函數使用的信號
         }
         waiting();
         kill(pid1,16);//向child 1 子進程發送16號中斷
         kill(pid2,17);//向child 2 子進程發送17號中斷
         wait(0);//等待兩個子進程結束
         wait(0);
         printf("parent process is killed\n");
         exit(0);
      }
      else{
         waite=1;
         signal(17,start);//接受父進程發送的17號中斷,調用信號中斷函數start()
         waiting();
         printf("child 2 is killed\n");
         exit(0);
      }
   }
   else{
     waite=1;
     signal(16,start);//接受父進程發送的16號中斷,調用信號中斷函數start()
     waiting();
     printf("child 1 is killed\n");
     exit(0);
   }
}

參考三
#include
#include

int main()
{
int i,j,stop();
signal(SIGINT,stop);
if(i=fork())
{
        if(j=fork())
        {
                //signal(SIGINT,SIG_IGN);
                sleep(10);
                kill(i,15);
                kill(j,16);
                wait(0);
                wait(0);
                printf("Parent process is killed!\n");
        }
        else {
                signal(16,SIG_IGN);
                sleep(10);
                //signal(16,stop);
                printf("Child process 3 is killed!\n");
                exit(0);
        }     
}
else {
        signal(15,SIG_IGN);
        sleep(10);
        //signal(15,stop);
        printf("Child process 1 is killed!\n");
        exit(0);
}
}

stop()
{
//printf("del key is got!\n");
}


參考四
#include
#include
#include
#include

int pid1,pid2;

main()
{
int fd[2];
char OutPipe[100],InPipe[100];
pipe(fd);
for(;;)
{
        while((pid1=fork())==-1);
        if(pid1 == 0)
        {
                lockf(fd[1],1,0);
                sprintf(OutPipe,"Child process 1 is sending message!\n");
                write(fd[1],OutPipe,50);
                sleep(3);
                lockf(fd[1],0,0);
                exit(0);
        }
        else {
                while((pid2=fork())==-1);
                if(pid2 == 0)
                {
                        lockf(fd[1],1,0);
                        sprintf(OutPipe,"Child process 2 is sending message!\n");
                        write(fd[1],OutPipe,50);
                        sleep(3);
                        lockf(fd[1],0,0);
                }
                else {
                        wait(0);
                        read(fd[0],InPipe,50);
                        printf("%s\n",InPipe);
                }
        }
}
}

pthread 多執行緒平行化程式設計入門教學與範例

Source: https://blog.gtwang.org/programming/pthread-multithreading-programming-in-c-tutorial/

這裡介紹如何在 C 語言中使用 pthread 開發多執行緒的平行化程式,用多顆 CPU 加速計算。

現在電腦的 CPU 都具備多顆核心,因此在使用 C 語言撰寫計算用的程式時,若能夠善用多核新的 CPU 進行平行運算,可以讓計算速度大幅提昇。

若要將 C 語言的程式平行化,最基本的方式就是使用 POSIX 執行緒(簡稱 pthread)來實做多執行緒的程式,以下是 pthread 函式庫的用法教學,以及實際的範例程式碼。


Pthread 多執行緒

pthread 的 pthread_create 函數可以用來建立新的執行緒,並以函數指標指定子執行緒所要執行的函數,子執行緒在建立之後,就會以平行的方式執行,在子執行緒的執行期間,主執行緒還是可以正常執行自己的工作,最後主執行緒再以 pthread_join 函數等待子執行緒執行結束,處理後續收尾的動作。
以下是一個 pthread 的 hello world 範例程式碼:
#include 
#include 
#include 

// 子執行緒函數
void* child(void* data) {
  char *str = (char*) data; // 取得輸入資料
  for(int i = 0;i < 3;++i) {
    printf("%s\n", str); // 每秒輸出文字
    sleep(1);
  }
  pthread_exit(NULL); // 離開子執行緒
}

// 主程式
int main() {
  pthread_t t; // 宣告 pthread 變數
  pthread_create(&t, NULL, child, "Child"); // 建立子執行緒

  // 主執行緒工作
  for(int i = 0;i < 3;++i) {
    printf("Master\n"); // 每秒輸出文字
    sleep(1);
  }

  pthread_join(t, NULL); // 等待子執行緒執行完成
  return 0;
}
此程式在主執行緒中建立一個子執行緒,並將 "Child" 這個字串傳遞給子執行緒,然後讓兩個執行緒同時輸出文字。
使用 gcc 編譯時,要加上 -lpthread 參數:
gcc hello.c -lpthread -o hello
編譯好之後,執行之:
./hello
Master
Child
Master
Child
Master
Child

資料傳遞

在許多的平行化應用程式中,我們都會需要傳遞一些資料給子執行緒進行計算,而在計算完之後再將結果傳回來,而子執行緒在傳回資料時通常都會以 malloc 配置記憶體空間來存放傳回的資料,以下是一個典型的範例:
#include 
#include 
#include 

// 子執行緒函數
void *child(void *arg) {
   int *input = (int *) arg; // 取得資料
   int *result = malloc(sizeof(int) * 1); // 配置記憶體
   result[0] = input[0] + input[1]; // 進行計算
   pthread_exit((void *) result); // 傳回結果
}

// 主程式
int main() {
   pthread_t t;
   void *ret; // 子執行緒傳回值
   int input[2] = {1, 2}; // 輸入的資料

   // 建立子執行緒,傳入 input 進行計算
   pthread_create(&t, NULL, child, (void*) input);

   // 等待子執行緒計算完畢
   pthread_join(t, &ret);

   // 取得計算結果
   int *result = (int *) ret;

   // 輸出計算結果
   printf("%d + %d = %d\n", input[0], input[1], result[0]);

   // 釋放記憶體
   free(result);

   return 0;
}
執行的輸出為:
1 + 2 = 3
這個程式中,子執行緒呼叫 malloc 配置了記憶體空間,而主執行緒在使用完該記憶體空間之後,負責釋放掉不再使用的記憶體。

由主執行緒管理記憶體

多執行緒之間的記憶體管理其實很不方便,也很容易不小心寫錯,造成記憶體流失(memory leak)問題,若想避免這個問題,可以統一由主執行緒來管理記憶體,以下是一個範例:
#include 
#include 

// 自己定義的資料結構
typedef struct my_data {
   int a;
   int b;
   int result;
} my_data;

// 子執行緒函數
void *child(void *arg) {
   my_data *data=(my_data *)arg; // 取得資料

   int a = data->a;
   int b = data->b;
   int result = a + b; // 進行計算

   data->result = result; // 將結果放進 data 中
   pthread_exit(NULL);
}

// 主程式
int main() {
   pthread_t t;
   my_data data;

   data.a = 1;
   data.b = 2;

   // 建立子執行緒,傳入 data 進行計算
   pthread_create(&t, NULL, child, (void*) &data);

   // 等待子執行緒計算完畢
   pthread_join(t, NULL);

   // 從 data.result 取回計算結果
   printf("%d + %d = %d\n", data.a, data.b, data.result);

   return 0;
}
這個例子中,我們使用自己定義的資料結構(struct),將所有的輸入資料與輸出結果欄位都包裝在一個 my_data 中,以指標的方式傳入子執行緒中,讓子執行緒在計算完成後,將結果直接寫入 my_data 的 result 欄位,這樣就不需要另外配置記憶體空間,而主執行緒也可以直接取得計算結果。

互斥鎖(Mutex)

在平行化的程式中,如果發生多個執行緒需要同時存取同一個位置的資料時,就有可能會因為同時存取而產生錯誤,在下面這個例子中,我們定義一個全域變數 counter,用來紀錄某個量的總和,而我們希望在多個執行緒中同時計算,然後統一將加總的結果放在其中。
#include 
#include 
#include 

// 計數器
int counter = 0;

// 子執行緒函數
void* child() {
  for(int i = 0;i < 3;++i) {
    int tmp = counter;
    sleep(1); // 故意讓它延遲一下
    counter = tmp + 1;
    printf("Counter = %d\n", counter);
  }
  pthread_exit(NULL);
}

// 主程式
int main() {
  pthread_t t1, t2;
  pthread_create(&t1, NULL, child, NULL);
  pthread_create(&t2, NULL, child, NULL);
  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  return 0;
}
在這段程式碼中,我們放了兩個子執行緒,每個子執行緒用迴圈跑了三次計算,所以最後的 counter 預期應該是 6,但由於我們將 counter 的值取出來,計算出新的值之後在放回去,兩個子執行緒同時都這樣做的話,計算結果就會不如預期:
Counter = 1
Counter = 1
Counter = 2
Counter = 2
Counter = 3
Counter = 3
這個問題的解決方法就是加入一個互斥鎖(mutex),將那些不可以被多個執行緒同時執行的程式碼片段,用互斥鎖包起來,當一個執行緒執行到該處時,就會先上鎖,避免其他的執行緒進入,若其他的執行緒同時也要執行該處的程式碼時,就必須等待先前的執行緒執行完之後,才能接著進入(也就是排隊輪流使用的概念),這樣就可以避免多個執行緒混雜執行,讓結果出錯的問題。
#include 
#include 
#include 

// 計數器
int counter = 0;

// 加入 Mutex
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;

// 子執行緒函數
void* child() {
  for(int i = 0;i < 3;++i) {
    pthread_mutex_lock( &mutex1 ); // 上鎖
    int tmp = counter;
    sleep(1);
    counter = tmp + 1;
    pthread_mutex_unlock( &mutex1 ); // 解鎖
    printf("Counter = %d\n", counter);
  }
  pthread_exit(NULL);
}

// 主程式
int main() {
  pthread_t t1, t2;
  pthread_create(&t1, NULL, child, NULL);
  pthread_create(&t2, NULL, child, NULL);
  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  return 0;
}
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
在 pthread_mutex_lock 與 pthread_mutex_unlock 之間的程式碼就是一次只容許一個執行緒執行的部份,也就是說雖然是平行化的程式,但是被包住的這部份只能以單一執行緒來執行,所以在設計程式時,要盡可能減少被互斥鎖包住的程式碼,才能讓程式執行效能更好。

旗標(Semaphore)

如果我們現在有兩個執行緒,分別負責一份工作的前半段與後半段,也就是說第一個執行緒會把它處理好的資料,發包給第二個執行緒繼續處理,而兩個執行緒的處理速度有可能不同,這種狀況我們就可以使用旗標(Semaphore)的方式來串接。
旗標本身就是一個計數器,也就是紀錄目前尚未處理的工作數量,我們可以使用 sem_wait 來判斷是否有尚未處理的工作,當工作數量大於 0 時,sem_wait 就會讓執行緒進入處理,並且把工作數量遞減 1,而如果工作數量為 0 的時候,則會讓執行緒等待,直到有新的工作來臨時,才讓執行緒進入。
另外在產生工作的執行緒中,可以使用 sem_post 放入新的工作(也就讓將計數器遞增 1),這樣就可以將多個執行緒串接起來處理大型的工作流程。
以下是一個簡單的範例:
#include 
#include 
#include 
#include 

sem_t semaphore; // 旗標
int counter = 0;

// 子執行緒函數
void* child() {
  for(int i = 0;i < 5;++i) {
    sem_wait(&semaphore); // 等待工作
    printf("Counter = %d\n", ++counter);
    sleep(1);
  }
  pthread_exit(NULL);
}

// 主程式
int main(void) {

  // 初始化旗標,僅用於本行程,初始值為 0
  sem_init(&semaphore, 0, 0);

  pthread_t t;
  pthread_create(&t, NULL, child, NULL);

  // 送出兩個工作
  printf("Post 2 jobs.\n");
  sem_post(&semaphore);
  sem_post(&semaphore);
  sleep(4);

  // 送出三個工作
  printf("Post 3 jobs.\n");
  sem_post(&semaphore);
  sem_post(&semaphore);
  sem_post(&semaphore);

  pthread_join(t, NULL);

  return 0;
}
Post 2 jobs.
Counter = 1
Counter = 2
Post 3 jobs.
Counter = 3
Counter = 4
Counter = 5
在這個程式中,主執行緒負責派送工作,工作有時候多、有時候少,而子執行緒則是以每秒處理一個工作的速度,消化接收到的工作。
旗標在使用前要先以 sem_init 初始化,其第二個參數是指定是否要讓其他的行程(process)共用旗標,這裡我們是單一行程、多執行緒的程式,所以第二個參數設定為 0 即可;第三個參數則是設定旗標的初始值。
旗標本身只是紀錄工作的數量,並且控制執行緒的執行,並沒有負責資料的配送,通常我們可以自己實做一個資料佇列(queue),配合旗標來計算索引,讓子執行緒從佇列中取得資料進行處理。

Source: 

Pragma

#print a message from the C preprocessor?

Source: https://stackoverflow.com/questions/3826832/is-there-a-portable-way-to-print-a-message-from-the-c-preprocessor

The warning directive is probably the closest you'll get, but it's not entirely platform-independent:
#warning "C Preprocessor got here!"
AFAIK this works on most compilers except MSVC, on which you'll have to use a pragma directive:
#pragma message ( "C Preprocessor got here!" )

BASH array

Source: https://www.playworld.com.tw/%E3%80%8Ebash-shell%E3%80%8F%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E9%99%A3%E5%88%97-array-%E7%B4%A2%E5%BC%95%E5%BC%8F-indexed-%E9%97%9C%E8%81%AF%E5%BC%8F-associative-%E7%A8%8B%E5%BC%8F%E7%AF%84/


Bash 支援兩種陣列 (Array) 的型態

1. Indexed array

2. Associative array

第一種 Indexed array 是以數字做陣列的索引,從 0 開始
範例:
#!/bin/sh
#
# 底下有兩種指定陣列成員的方式
# 選一種使用
#
# 方式一
#
HTC_phone_list[0]="HTC U11"
HTC_phone_list[1]="HTC U Ultra"
HTC_phone_list[2]="HTC U Play"
HTC_phone_list[3]="HTC One A9s"

#
# 方式二
#
HTC_phone_list=("HTC U11" "HTC U Ultra" "HTC U Play" "HTC One A9s")

#
# 印出陣列中的成員
#
# ${#HTC_phone_list[@]}: 加上 "#" 表示陣列的長度
#
for((i=0; i<${#HTC_phone_list[@]}; i++))
do
    echo ${HTC_phone_list[i]}
done


第二種 Associative array 是以字串做陣列的索引

範例:
#!/bin/sh
#
# 底下有兩種指定陣列成員的方式
# 選一種使用
# 注意: 請勿省略 declare -A
#

#
# 方式一
#
declare -A Score
Score[bob]=85
Score[john]=70
Score[andy]=90

#
# 方式二
#
declare -A Score=([bob]=85 [john]=70 [andy]=90)

# 輸入要查詢的名字
read -p "Please input a name: " name
found=0

#
# 搜尋 Score 陣列的索引是否有此名字
#
# ${!Score[@]}: 加上 "!" 表示陣列的索引
#
for key in ${!Score[@]}
do
    if [ "$key" == "$name" ]; then
        found=1
        break
    fi
done

#
# 印出搜尋結果
#
if [ "$found" -eq "1" ]; then
    echo "The score of $name is ${Score[$name]}"
else
    echo "The name of \"$name\" cannot be found."
fi


Source: https://go-linux.blogspot.com/2007/03/basharray.html

bash下array的幾種使用方法

#!/bin/bash
#一舉將變數設定到陣列中
array=(Redhat Novell MicroSoft Sun IBM HP Dell)

#利用for loop將陣列中的變數印出
for i in 0 1 2 3 4 5 6
do
echo "array[$i]=${array[$i]}"
done

#設定間隔符號為: 搭配$*將陣列的值一口氣輸出
IFS=:
echo "${array[*]}"

#設定間隔符號為換行,搭配$*將陣列的值一口氣輸出
IFS=$'\n'
echo "${array[*]}"

#將陣列中的值利用$@一口氣輸出與$*不同的是,不會將值合併成單一字串
echo "${array[@]}"

#印出陣列中有幾筆資料
echo "${#array[@]}"

執行結果:
array[0]=Redhat
array[1]=Novell
array[2]=MicroSoft
array[3]=Sun
array[4]=IBM
array[5]=HP
array[6]=Dell
Redhat:Novell:MicroSoft:Sun:IBM:HP:Dell
Redhat
Novell
MicroSoft
Sun
IBM
HP
Dell
Redhat Novell MicroSoft Sun IBM HP Dell
7

2019年8月2日 星期五

ADB TCPIP

[8150]
  • 1. adb tcpip 5555
    2. adb connect 192.168.43.10
    3. adb -s 192.168.43.10 shell​
    adb_tcpip.png