小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Linux signal 那些事兒(2)

 wsglibrary 2016-04-14
    上一篇博文,基本算是給glibc的signal函數(shù)翻了個身?,F(xiàn)在glibc的signal基本修正了傳統(tǒng)的UNIX的一些弊端,我們說signal并沒有我們想象的那么不堪。但是signal也有不盡人意的地方。比如信號處理期間,我們期望屏蔽某些信號,而不僅僅是屏蔽自身,這時候signal就不行了。信號既然是進程間通信IPC的一種機制,我們期望獲取更多的信息,而不僅僅是signo,這時候signal/kill這個機制就基本不行了。
    上面所說的都是signal的一些毛病,但是這些都不是致命的,致命的問題在于老的signal機制的不可靠。信號分成可靠性信號和非可靠性信號,并不是說用sigaction安裝,用sigqueue發(fā)送的信號就是可靠性性信號,用signal安裝,kill/tkill發(fā)送的信號就是非可靠性信號。這種理解是錯誤的。這在Linux環(huán)境進程間通信(二):信號(上)一文中講的非常清楚了。
    信號值位于[SIGRTMIN,SIGRTMAX] 之間的信號,就是可靠信號,位于[SIGHUP,SIGSYS]之間信號,都是非可靠性信號,與安裝函數(shù)是signal還是sigaction無關,與發(fā)送函數(shù)是kill還是sigqueue無關。

    1~31之間的所有信號都稱為不可靠信號,原因就在于信號不可排隊,如果kernel發(fā)現(xiàn)同一個信號已經(jīng)有掛起信號,當前信號就會被丟棄,就好象從來沒有被發(fā)送過一樣,無法引起信號的傳遞,也無法讓進程執(zhí)行信號處理函數(shù)。這種實現(xiàn)的機理,造成了這些信號的不可靠。這正所謂:我本將心向明月,奈何明月照溝渠。
    為了解決這個問題,Linux引入了實時信號,信號值在[32~64]區(qū)間內(nèi),或者稱之為可靠信號。這種信號,kernel不會ignore,哪怕已經(jīng)有了好多同一個信號,kernel會把新收到信號放入queue之中,等待被傳遞出去。
    空口說白話,不是我們的風格,我現(xiàn)在用代碼證明之。我參考了Linux Programming Interface 一書的例子,寫了兩個程序,一個是signal_receiver ,一個是signal_sender.
    先看signal_receiver的code:    
  1. manu@manu-hacks:~/code/c/self/signal$ cat signal_receiver.c
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <signal.h>
  6. #include <string.h>
  7. #include <errno.h>


  8. static int sig_cnt[NSIG];
  9. static volatile sig_atomic_t get_SIGINT = 0;

  10. void handler(int signo)
  11. {
  12.     if(signo == SIGINT)
  13.         get_SIGINT = 1;
  14.     else
  15.         sig_cnt[signo]++;
  16. }

  17. int main(int argc,char* argv[])
  18. {
  19.     int i = 0;
  20.     sigset_t blockall_mask ;
  21.     sigset_t pending_mask ;
  22.     sigset_t empty_mask ;
  23.     printf("%s:PID is %ld\n",argv[0],getpid());

  24.     
  25.     for(i = 1; i < NSIG; i++)
  26.     {
  27.         if(i == SIGKILL || i == SIGSTOP)
  28.             continue;

  29.         if(signal(i,&handler) == SIG_ERR)
  30.         {
  31.             fprintf(stderr,"signal for signo(%d) failed (%s)\n",i,strerror(errno));
  32. //            return -1;
  33.         }
  34.     }

  35.     if(argc > 1)
  36.     {
  37.         int sleep_time = atoi(argv[1]);
  38.         sigfillset(&blockall_mask);

  39.         if(sigprocmask(SIG_SETMASK,&blockall_mask,NULL) == -1)
  40.         {
  41.             fprintf(stderr,"setprocmask to block all signal failed(%s)\n",strerror(errno));
  42.             return -2;
  43.         }

  44.         printf("I will sleep %d second\n",sleep_time);

  45.         sleep(sleep_time);
  46.         if(sigpending(&pending_mask) == -1)
  47.         {
  48.             fprintf(stderr,"sigpending failed(%s)\n",strerror(errno));
  49.             return -2;
  50.         }

  51.         for(i = 1 ; i < NSIG ; i++)
  52.         {
  53.             if(sigismember(&pending_mask,i))
  54.             printf("signo(%d) :%s\n",i,strsignal(i));
  55.         }

  56.         sigemptyset(&empty_mask);
  57.         if(sigprocmask(SIG_SETMASK,&empty_mask,NULL) == -1)
  58.         {
  59.             fprintf(stderr,"setprocmask to release all signal failed(%s)\n",strerror(errno));
  60.             return -3;
  61.         }
  62.         
  63.     }

  64.     while(!get_SIGINT)
  65.         continue ; //why not use pause ? I will explain later

  66.     for(i = 1; i < NSIG ; i++)
  67.     {
  68.         if(sig_cnt[i] != 0 )
  69.         {
  70.             printf("%s:signal %d caught %d time%s\n",
  71.                     argv[0],i,sig_cnt[i],(sig_cnt[i] >1)?"s":"");
  72.         }
  73.     }

  74.     return 0;

  75. }
     因為我們知道,SIGKILL和SIGSTOP這兩個信號是不能夠定制自己的信號處理函數(shù)的,當然也不能block,原因很簡單,OS或者說root才是final boss,必須有穩(wěn)定終結進程的辦法。假如所有的信號,進程都能ignore,OS如何終結進程?
    這個signal_receiver會等待所有的信號,接收到某信號后,該信號的捕捉到的次數(shù)++,SIGINT會終結進程,進程退出前,會打印信號的捕捉統(tǒng)計。
    如果進程有參數(shù),表示sleep時間,signal_receiver會先屏蔽所有信號(當然,SIGKILL和SIGSTOP并不能被真正屏蔽)。然后sleep 一段時間后,取消信號屏蔽。我們可以想象,在信號屏蔽期間,我們收到的信號,都會在kernel記錄下來,但是并不能delivery,這種信號稱之掛起信號。如果在sleep期間或者說信號屏蔽期間,我收到SIGUSR1 這個信號1次和10000次,對內(nèi)核來說,都是沒差別的,因為后面的9999次都會被ignore掉。SIGUSR1屬于不可靠信號,位圖表示有沒有掛起信號,有的話,直接ignore,沒有的話,則記錄在kernel。
    然后我們看下,signal_sender: 
  1. manu@manu-hacks:~/code/c/self/signal$ cat signal_sender.c
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <getopt.h>
  5. #include <signal.h>
  6. #include <string.h>
  7. #include <errno.h>

  8. void usage()
  9. {
  10.     fprintf(stderr,"USAGE:\n");
  11.     fprintf(stderr,"--------------------------------\n");
  12.     fprintf(stderr,"signal_sender pid signo times\n");
  13. }

  14. int main(int argc,char* argv[])
  15. {
  16.     pid_t pid = -1 ;
  17.     int signo = -1;
  18.     int times = -1;
  19.     int i ;


  20.     if(argc < 4 )
  21.     {
  22.         usage();
  23.         return -1;
  24.     }
  25.     
  26.     pid = atol(argv[1]);
  27.     signo = atoi(argv[2]);
  28.     times = atoi(argv[3]);

  29.     if(pid <= 0 || times < 0 || signo <1 ||signo >=64 ||signo == 32 || signo ==33)
  30.     {
  31.         usage();
  32.         return -1;
  33.     }

  34.     printf("pid = %ld,signo = %d,times = %d\n",pid,signo,times);

  35.     for( i = 0 ; i < times ; i++)
  36.     {
  37.         if(kill(pid,signo) == -1)
  38.         {
  39.             fprintf(stderr, "send signo(%d) to pid(%ld) failed,reason(%s)\n",signo,pid,strerror(errno));
  40.             return -2;
  41.         }
  42.     }
  43.     fprintf(stdout,"done\n");
  44.     return 0;

  45. }
     signal_sender需要三個參數(shù),pid signo times,就是向拿個進程發(fā)送什么信號多少次的意思。如 signal_sender 1234 10 10000,含義是向pid=1234的 進程發(fā)送10號信號(SIGUSR1),連續(xù)發(fā)送10000次。
    有這兩個進程,我們就可以實驗了  。 
  1. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver &
  2. [1] 23416
  3. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver:PID is 23416
  4. signal for signo(32) failed (Invalid argument)
  5. signal for signo(33) failed (Invalid argument)

  6. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 23416 10 10000
  7. pid = 23416,signo = 10,times = 10000
  8. done
  9. manu@manu-hacks:~/code/c/self/signal$ sleep 20 ; ./signal_sender 23416 2 1
  10. pid = 23416,signo = 2,times = 1
  11. done
  12. ./signal_receiver:signal 10 caught 2507 times
  13. [1]+ Done ./signal_receiver
    signal_receiver等待signal的來臨,singal_sender向其發(fā)送SIGUSR1 10000次,然后sleep 20秒,確保sig_receiver處理完成。但是我們發(fā)現(xiàn),其實一共才caught信號SIGUSR1  2507次,7000多次的發(fā)送都丟失了,所以我們稱SIGUSR1 是非可靠信號,存在丟信號的問題。
    俗話說不怕不識貨,就怕貨比貨 ,我們讓可靠信號參戰(zhàn),看下效果:
  1. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver &
  2. [1] 26067
  3. ./signal_receiver:PID is 26067
  4. signal for signo(32) failed (Invalid argument)
  5. signal for signo(33) failed (Invalid argument)
  6. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 26067 10 10000
  7. pid = 26067,signo = 10,times = 10000
  8. done
  9. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 26067 36 10000
  10. pid = 26067,signo = 36,times = 10000
  11. done
  12. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 26067 2 1
  13. pid = 26067,signo = 2,times = 1
  14. done
  15. ./signal_receiver:signal 10 caught 2879 times
  16. ./signal_receiver:signal 36 caught 10000 times
  17. [1]+ Done ./signal_receiver
    可靠性信號36,發(fā)送10000次,signal_receiver全部收到,不可靠性信號10,共收到2879次。這個數(shù)字是不可預期的,取決于內(nèi)核進程的調(diào)度。
    這個如果還不夠直觀,我們在比較一次,讓signal_receiver先屏蔽所有信號一段時間,如30s,然后解除屏蔽。
  1. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver 30 &
  2. [1] 27639
  3. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver:PID is 27639
  4. signal for signo(32) failed (Invalid argument)
  5. signal for signo(33) failed (Invalid argument)
  6. I will sleep 30 second

  7. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 27639 10 10000
  8. pid = 27639,signo = 10,times = 10000
  9. done
  10. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 27639 36 10000
  11. pid = 27639,signo = 36,times = 10000
  12. done
  13. manu@manu-hacks:~/code/c/self/signal$
  14. manu@manu-hacks:~/code/c/self/signal$ signo(10) :User defined signal 1
  15. signo(36) :Real-time signal 2

  16. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 27639 2 1
  17. pid = 27639,signo = 2,times = 1
  18. done
  19. ./signal_receiver:signal 10 caught 1 time
  20. ./signal_receiver:signal 36 caught 10000 times
  21. [1]+ Done ./signal_receiver 30
      這個比較反差比較大,不可靠signal10 共收到1次,可靠性信號36 共caught到10000次。原因就在于sigprocmask將所有的信號都屏蔽了,造成所有的信號都不能delivery。對1~31的信號,內(nèi)核發(fā)現(xiàn)已經(jīng)有相應的掛起信號,則ignore到新來的信號。但是可靠性信號則不同,會添加隊列中去,盡管已經(jīng)有了相同的信號。需要注意的是,signal pending有上限,并不能無限制的發(fā):
  1. manu@manu-hacks:~/code/c/self/signal$ ulimit -a
  2. core file size (blocks, -c) 0
  3. data seg size (kbytes, -d) unlimited
  4. scheduling priority (-e) 0
  5. file size (blocks, -f) unlimited
  6. pending signals (-i) 15408
  7. max locked memory (kbytes, -l) 64
  8. max memory size (kbytes, -m) unlimited
  9. open files (-n) 1024
  10. pipe size (512 bytes, -p) 8
  11. POSIX message queues (bytes, -q) 819200
  12. real-time priority (-r) 0
  13. stack size (kbytes, -s) 8192
  14. cpu time (seconds, -t) unlimited
  15. max user processes (-u) 15408
  16. virtual memory (kbytes, -v) unlimited
  17. file locks (-x) unlimited
    我發(fā)送100萬,最終會收到15408個可靠信號:
  1. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver 30 &
  2. [1] 16488
  3. manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver:PID is 16488
  4. signal for signo(32) failed (Invalid argument)
  5. signal for signo(33) failed (Invalid argument)
  6. I will sleep 30 second

  7. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 16488 36 1000000
  8. pid = 16488,signo = 36,times = 1000000
  9. done
  10. manu@manu-hacks:~/code/c/self/signal$ signo(36) :Real-time signal 2

  11. manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 16488 2 1
  12. pid = 16488,signo = 2,times = 1
  13. done
  14. ./signal_receiver:signal 36 caught 15408 times
  15. [1]+ Done ./signal_receiver 30
      內(nèi)核是怎么做到的?
    
    上圖是內(nèi)核中signal相關的數(shù)據(jù)結構。其中task_struct中有sigpending類型的成員變量pending
    
  1. struct task_struct {
  2.     volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
  3.     void *stack;
  4.     atomic_t usage;
  5.     unsigned int flags; /* per process flags, defined below */
  6.     unsigned int ptrace;
  7.         ...
  8.         ...
  9. /* signal handlers */
  10.     struct signal_struct *signal;
  11.     struct sighand_struct *sighand;

  12.     sigset_t blocked, real_blocked;
  13.     sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */
  14.     struct sigpending pending;

  15.        ...
  16. }

  17. struct signal_struct {
  18.       atomic_t     sigcnt;
  19.       atomic_t     live;
  20.       int     nr_threads;
  21.       ...
  22.       ...
  23.       /* shared signal handling: */
  24.       struct sigpending    shared_pending;
  25.       ...
  26. }

  27. struct sigpending {
  28.     struct list_head list;
  29.     sigset_t signal;
  30. };

  31. #define _NSIG        64

  32. #ifdef __i386__
  33. # define _NSIG_BPW    32
  34. #else
  35. # define _NSIG_BPW    64
  36. #endif

  37. #define _NSIG_WORDS    (_NSIG / _NSIG_BPW)

  38. typedef unsigned long old_sigset_t;        /* at least 32 bits */

  39. typedef struct {
  40.     unsigned long sig[_NSIG_WORDS];
  41. } sigset_t;
      task_struct中的pending,和signal->shared_pending都是記錄掛起信號的數(shù)據(jù)結構,讀到此處,你可能會迷惑,為何有兩個這樣的結構。這牽扯到thread與信號的一些問題,我們此處簡化,就認為是一個就好,后面講述線程與信號關系的時候,再展開。
      
    我們看到了,kill也好,tkill也罷,最終都走到了_send_signal.當然了kill系統(tǒng)調(diào)用根據(jù)pid的情況會分成多個分支如pid >0 pid = 0 pid=-1;pid < 0&pid !=-1,總之了,我的圖只繪制了pid >0 的分支。tkill也有類似情況。
    那么kernel是怎么做到的非可靠信號和可靠信號的的這些差別的呢?
  1. static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
  2.             int group, int from_ancestor_ns)
  3. {
  4.     struct sigpending *pending;
  5.     struct sigqueue *q;
  6.     int override_rlimit;
  7.     int ret = 0, result;

  8.     assert_spin_locked(&t->sighand->siglock);

  9.     result = TRACE_SIGNAL_IGNORED;
  10.     if (!prepare_signal(sig, t,
  11.             from_ancestor_ns || (info == SEND_SIG_FORCED)))
  12.         goto ret;

  13.     pending = group ? &t->signal->shared_pending : &t->pending;
  14.     /*
  15.      * Short-circuit ignored signals and support queuing
  16.      * exactly one non-rt signal, so that we can get more
  17.      * detailed information about the cause of the signal.
  18.      */
  19.     result = TRACE_SIGNAL_ALREADY_PENDING;
  20.     if (legacy_queue(pending, sig)) //如果是低于32的信號,并且已經(jīng)在pending中出現(xiàn)了的信號,就直接返回了,ignore
  21.         goto ret;

  22.     result = TRACE_SIGNAL_DELIVERED;
  23.     /*
  24.      * fast-pathed signals for kernel-internal things like SIGSTOP
  25.      * or SIGKILL.
  26.      */
  27.     if (info == SEND_SIG_FORCED)
  28.         goto out_set;

  29.     /*
  30.      * Real-time signals must be queued if sent by sigqueue, or
  31.      * some other real-time mechanism. It is implementation
  32.      * defined whether kill() does so. We attempt to do so, on
  33.      * the principle of least surprise, but since kill is not
  34.      * allowed to fail with EAGAIN when low on memory we just
  35.      * make sure at least one signal gets delivered and don't
  36.      * pass on the info struct.
  37.      */
  38.     if (sig < SIGRTMIN)
  39.         override_rlimit = (is_si_special(info) || info->si_code >= 0);
  40.     else
  41.         override_rlimit = 0;
      //分配sigqueue結構,并且鏈入到相應的pending。
  1.     q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
  2.         override_rlimit);
  3.     if (q) {
  4.         list_add_tail(&q->list, &pending->list);
  5.         switch ((unsigned long) info) {
  6.         case (unsigned long) SEND_SIG_NOINFO:
  7.             q->info.si_signo = sig;
  8.             q->info.si_errno = 0;
  9.             q->info.si_code = SI_USER;
  10.             q->info.si_pid = task_tgid_nr_ns(current,
  11.                             task_active_pid_ns(t));
  12.             q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
  13.             break;
  14.         case (unsigned long) SEND_SIG_PRIV:
  15.             q->info.si_signo = sig;
  16.             q->info.si_errno = 0;
  17.             q->info.si_code = SI_KERNEL;
  18.             q->info.si_pid = 0;
  19.             q->info.si_uid = 0;
  20.             break;
  21.         default:
  22.             copy_siginfo(&q->info, info);
  23.             if (from_ancestor_ns)
  24.                 q->info.si_pid = 0;
  25.             break;
  26.         }

  27.         userns_fixup_signal_uid(&q->info, t);

  28.     } else if (!is_si_special(info)) {
  29.         if (sig >= SIGRTMIN && info->si_code != SI_USER) {
  30.             /*
  31.              * Queue overflow, abort. We may abort if the
  32.              * signal was rt and sent by user using something
  33.              * other than kill().
  34.              */
  35.             result = TRACE_SIGNAL_OVERFLOW_FAIL;
  36.             ret = -EAGAIN;
  37.             goto ret;
  38.         } else {
  39.             /*
  40.              * This is a silent loss of information. We still
  41.              * send the signal, but the *info bits are lost.
  42.              */
  43.             result = TRACE_SIGNAL_LOSE_INFO;
  44.         }
  45.     }

  46. out_set:
  47.     signalfd_notify(t, sig);
  48.     sigaddset(&pending->signal, sig);  //加入位圖
  49.     complete_signal(sig, t, group);
  50. ret:
  51.     trace_signal_generate(sig, info, t, group, result);
  52.     return ret;
  53. }

  54. static inline int legacy_queue(struct sigpending *signals, int sig)
  55. {
  56.     return (sig < SIGRTMIN) && sigismember(&signals->signal, sig); //是不可靠信號,并且該信號已經(jīng)存在掛起信號,
    那么15408的限制是在哪里呢?在__sigqueue_alloc

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多