فصل ۲۰ -Signals: Fundamental Concepts

سوالات:

  1. فرق سیگنال SIGSTOP با SIGTSTP چیست؟
  2. در مورد سیستم کال kill و انواع مختلف pid تمرین کافی نکردم.
  3. تابع siginterrupt(3) را با سیستم کال sigaction پیاده‌سازی کنید.

process groups = jobs


سیگنال را یا کرنل می‌تواند به یک پراسس بزند یا یک پراسس به پراسس دیگر و یا حتی یک پراسس به خودش. پراسس در هنگام دریافت هر سیگنال یک رفتار پیش فرض دارد. این رفتار را می‌توانیم با توابع signal handler تغییر دهیم. سیگنال notification ای به پراسس است مبنی بر این که اتفاقی روی داده است. گاهی اوقات به سیگنال‌ها اینتراپت‌های نرم‌افزاری هم می‌گویند. آن‌ها مسیر نرمال برنامه را تغییر می‌دهند و در بیشتر موارد امکان اینکه پیش‌بینی کنیم که یک سیگنال دقیقا چه موقع خواهد رسید وجود ندارد.

سیگنال‌ها می‌توانند به عنوان یک تکنیک synchronization بین دو پراسس و یا حتی به عنوان یک فرم ابتدایی IPC مورد استفاده قرار بگیرند.

به طور کلی منبع بسیاری از سیگنال‌هایی که به یک پراسس زده می‌شود کرنل است. به عنوان نمونه:

  1. یک hardware exception رخ می‌دهد یعنی سخت‌افزار وقوع یک ایراد را تشخیص می‌دهد و به کرنل اطلاع می‌دهد و کرنل به نوبه‌ی خود پراسس مربوطه را در جریان قرار می‌دهد. مثال از این نوع نقص‌های سخت‌افزاری یک اینستراکشن زبان ماشین نادرست، یا تقسیم بر صفر کردن، یا مراجعه به آدرس حافظه‌ای که وجود ندارد است.
  2. کاربر یکی از کاراکترهای ویژه‌ی ترمینال terminal special characters را تایپ کرده است که سیگنال تولید می‌کند. مثل کاراکتر اینتراپت (معمولا Control-C) و کاراکتر suspend (معمولا Control-Z)
  3. یک رخداد نرم‌افزاری اتفاق افتاده است، مثلا ورودی یک file descriptor فراهم شده است، پنجره‌ی ترمینال resize شده است، یک تایمر منقضی شده است، زمان CPU یک پراسس سر آمده است و یا بچه‌ی این پراسس terminate شده است.

هر سیگنال توسط یک عدد integer یونیک که به ترتیب از عدد ۱ شروع شده‌اند تعریف میشود. این اعداد در سر فایل signal.h تعریف شده‌اند و هر کدام یک نام نمادین به صورت SIGXXXX دارند. همیشه باید از این نام‌های نمادین استفاده کنیم برای اینکه شماره‌ی سیگنال‌ها در پیاده‌سازی‌های مختلف ممکن است فرق داشته باشد.

لیست سیگنال‌های تعریف شده در سیستم فعلی. شماره‌ی سیگنال همراه با نام نمادین آن:

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

سیگنال‌ها به طور کلی به دو دسته تقسیم می‌شوند:

  1. Traditional or Standard signals: که کرنل آن‌ها را استفاده استفاده می‌کند تا وقوع رخدادها را به اطلاع پراسس برساند. در لینوکس سیگنال‌های استاندارد از ۱ تا ۳۱ شماره‌گذاری شده‌اند.
  2. Realtime signals: که تفاوت آن‌ها را با سیگنال‌های استاندارد در فصل ۲۲ بررسی خواهیم کرد. در فاصله‌ی زمانی که سیگنال تولید می‌شود تا زمانی که سیگنال به پراسس تحویل داده شود می‌گوییم سیگنال pending است.

A signal is said to be generated by some event. Once generated, a signal is later delivered to a process.

معمولا یک سیگنال pending به محض اینکه پراسس دریافت‌کننده‌ی آن schedule شود به آن پراسس تحویل می‌شود و یا اگر پراسس در حال اجراست فورا این اتفاق رخ می‌دهد. (به عنوان مثال پراسس به خودش سیگنال زده است)

بعضی وقت‌ها نیاز داریم تا یقین حاصل کنیم که در اجرای قسمتی از کد ما با تحویل یک سیگنال وقفه ایجاد نمی‌شود. برای انجام این کار سیگنال مربوطه را به signal mask پراسس اضافه می‌کنیم. signal mask یک مجموعه از سیگنال‌هایی است که در حال حاضر به پراسس تحویل داده نمی‌شوند یا به اصطلاح تحویل آن سیگنال‌ها بلاک شده است. اگر یک سیگنال در زمانی که بلاک شده است ساطع شود تحویل داده نمی‌شود و در حالت pending می‌ماند تا زمانی که unblock شود یعنی از signal mask حذف شود.

بسته به نوع سیگنال، یک پراسس در هنگام دریافت سیگنال به صورت پیش فرض یکی از پنج رفتار زیر را انجام می‌دهد:

  1. سیگنال ignore می‌شود و کرنل آن را دور می‌اندازد. پراسس حتی از رخداد سیگنال خبر نمی‌شود.
  2. پراسس terminate می‌شود. به این حالت گاهی abnormal process termination گفته می‌شود، در مقابل normal process termination که زمانی اتفاق می‌افتد که پراسس با exit() خاتمه می‌یابد.
  3. یک فایل core dump تولید می‌شود و بعد پراسس terminate می‌شود. فایل core dump حاوی تصویر حافظه‌ی مجازی پراسس است که می‌توان آن را در یک دیباگر لود کرد تا وضعیت پراسس را در زمانی که terminate شده است بررسی کرد.
  4. پراسس stop می‌شود یا suspend می‌شود.
  5. اجرای پراسس resume می‌شود مشروط به اینکه قبلا stop شده باشد.

به جای پذیرش رفتار پیش فرض در مواجهه با یک سیگنال بخصوص، برنامه می‌تواند رفتار مورد نظر خودش را در رویارویی با آن سیگنال تعیین کند. به این کار ست کردن disposition سیگنال می‌گویند. سه نوع disposition برای یک سیگنال قابل ست شدن است:

  1. ست کردن رفتار پیش فرض. مفید برای زمانی که قبلا رفتار پیش فرض را تغییر داده باشیم.
  2. ignore کردن سیگنال. وقتی رفتار پیش فرض یک سیگنال terminate کردن پراسس است این disposition مفید خواهد بود.
  3. تعریف یک signal handler و اجرای آن در زمان رخداد سیگنال.

The shell has a handler for the SIGINT signal (generated by the interrupt character, Control-C) that causes it to stop what it is currently doing and return control to the main input loop, so that the user is once more presented with the shell prompt.

وقتی که یک سیگنال هندلر در پاسخ به تحویل یک سیگنال فراخوانی می‌شود می‌گوییم سیگنال هندل شده است یا سیگنال catch شده است.

توجه کنید که ست کردن disposition یک سیگنال به terminate یا core dump توسط برنامه‌نویس امکان‌پذیر نیست (مگر اینکه پیش فرض آن سیگنال terminate یا core dump+terminate باشد و ما رفتار پیش فرض را به آن سیگنال برگردانیم.) نزدیک‌ترین روش برای رسیدن به چنین خواسته‌ای ایجاد یک سیگنال هندلر و install کردن آن است. در داخل این هندلر می‌توان تابع abort را فراخوانی کرد که باعث ساطع شدن سیگنال SIGABRT می‌شود. این سیگنال یک core dump از حافظه‌ی مجازی پراسس می‌سازد و بعد پراسس را terminate می‌کند. (نیاز به بررسی بیشتر دارد. core dump ایجاد نکرد. مثال)

#include <stdlib.h>

[[noreturn]] void abort(void);

    the abort() function never returns.

فایل مخصوص لینوکس /proc/PID/status (یک نمونه از این فایل) حاوی فیلدهای bit-mask ای است که می‌توان از آن‌ها برای بررسی رفتار پراسس در مقابل سیگنال‌های ساطع شده استفاده کرد. bit mask ها به صورت هگزا دسیمال هستند و کم ارزش‌ترین بیت نشان دهنده‌ی سیگنال شماره ۱ است و بیت کناری آن نشان‌دهنده‌ی سیگنال شماره ۲ است و به همین ترتیب الی آخر. فیلدها به قرار زیر هستند. همین اطلاعات را با آپشن‌های متنوعی از دستور ps(1) می‌توان گرفت.

SigPnd = per-thread pending signals
ShdPnd = process-wide pending signals
SigBlk = blocked signals
SigIgn = ignored signals
SigCgt = caught signals

در سیستم‌های اولیه‌ی یونیکس سیگنال‌ها ممکن بود گم شوند و تحویل پراسس نشوند. علاوه بر این اگر چه امکانات بلاک کردن سیگنال فراهم بود ولی چندان قابل اعتماد نبود و ممکن بود سیگنال تحویل پراسس شود. این مشکلات در نسخه‌ی 4.2BSD بر طرف شدند که مفهوم reliable signals را ایجاد کرد.

پیشتر گفتیم که سیگنال‌های استاندارد در لینوکس از ۱ تا ۳۱ شماره‌گذاری شده‌اند ولی signal(7) بیشتر از ۳۱ نام نمادین را لیست کرده است. بعضی از این نام‌ها synonym نام‌های دیگر هستند و به خاطر سازگاری با پیاده‌سازی‌های دیگر یونیکس تعریف شده‌اند. بعضی نام‌ها هم اگرچه تعریف شده‌اند ولی استفاده نمی‌شوند.

لیست بعضی از سیگنال‌ها

  1. SIGABRT or SIGIOT: abort()
    SIGIOT فقط در لینوکس به عنوان synonym سیگنال SIGABRT کاربرد دارد. در سیستم‌های دیگر معانی دیگری دارد.
  2. SIGALRM: alarm() or setitimer()
  3. SIGBUS: indicate certain kinds of memory access errors. One such error can occur when using memory mappings created with mmap(2), if we attempt to access an address that lies beyond the end of the underlying memory-mapped file.
  4. SIGCHLD or SIGCLD: this signal is sent by the kernel, to a parent process when one of its children terminates (either by calling exit() or as a result of being killed by a signal). It may also be sent to a process when one of its children is stopped or resumed by a signal.
  5. SIGCONT: when sent to a stopped process, this signal causes the process to resume (i.e., to be rescheduled to run at some later time).
    وقتی که به یک پراسسی که هم اکنون stop نیست ارسال شود این سیگنال نادیده گرفته خواهد شد. پراسس می‌تواند این سیگنال را catch کند و لذا در هنگام resume شدن یکسری کار انجام بدهد. مثال (در مثال از سیستم کال pause برای stop کردن پراسس استفاده کرده‌ام. آیا این کار درست است؟)
  6. SIGEMT: EMT=emulator trap. In linux this signal is used only in the SUN SPARC implementation. In UNIX systems generally, this signal is used to indicate an implementation dependent hardware error.
  7. SIGFPE: FPE=floating point exceptions.
    این سیگنال در مورد نوع‌های مشخصی از خطاهای ریاضیاتی مانند تقسیم بر صفر ساطع می‌شود. برخلاف نامش برای خطاهای ریاضیاتی اعداد integer هم کاربرد دارد. مثال (مثال نیاز به تدبر بیشتری دارد. چرا بعد از catch شدن باز هم مداوما سعی در تقسیم عدد بر صفر دارد و مرتبا سیگنال هندلر اجرا می‌شود؟)
    برای اطلاعات بیشتر به fenv(3) و سر فایل <fenv.h> و صفحه‌ی ۳۹۱ کتاب مراجعه کنید.
  8. SIGHUP: When a terminal disconnect (hangup) occurs, this signal is sent to the controlling process of the terminal. A second use of SIGHUP is with daemons (e.g., init, httpd, and inted). Many daemons are designed to respond to the receipt of SIGHUP by reinitializing themselves and rereading their configuration files.
  9. SIGILL: This signal is sent to a process if it tries to execute an illegal (i.e., incorrectly formed) machine-language instruction.
  10. SIGPWR or SIGINFO: This is the power failure signal.
    روی سیستم‌هایی که UPS (uninterruptible power supply) دارند می‌توان یک پراسس daemon راه اندازی کرد که در هنگام قطع شدن پاور اصلی سطح باتری بکاپ را چک بکند. اگر باتری در حال تمام شدن بود این پراسس کنترل کننده یک سیگنال SIGPWR به پراسس init می‌زند که init آن را به عنوان درخواست خاموش کردن سریع و تمیز سیستم تفسیر می‌کند.
    SIGINFO در سیستم‌های BSD معنی دیگری دارد.
  11. SIGINT: When the user types the terminal interrupt character (usually Control-C), the terminal driver sends this signal to the foreground process group. The default action for this signal is to terminate the process.
  12. SIGIO or SIGPOLL: Using the fcntl() system call, it is possible to arrange for this signal to be generated when an I/O event occurs on certain types of open file descriptors.
  13. SIGKILL:
    SIGKILL سیگنال قطعی kill کردن پراسس است. نمی‌توان آن را block کرد، نمی‌توان آن را ignore کرد و نمی‌توان آن را catch کرد لذا همیشه پراسس را terminate می‌کند.
  14. SIGLOST: This signal name exists on Linux, but is unused. (چک کردم. وجود نداشت.)
  15. SIGPIPE:
    این سیگنال وقتی ساطع می‌شود که پراسس سعی می‌کند در یک pipe یا FIFO یا یک socket بنویسد در حالی که هیچ پراسس خواننده‌ای روی آن pipe یا FIFO یا socket وجود ندارد. این اتفاق معمولا زمانی می‌افتد که پراسس خواننده file descriptor مربوط به خواندن از موارد ذکر شده را close کرده باشد.
    مثال از SIGCHLD و SIGPIPE
  16. SIGPROF:
    کرنل سیگنال SIGPROF را در هنگام منقضی شدن profiling timer که به وسیله‌ی سیستم کال setitimer ست شده است ساطع می‌کند. prfiling timer تایمری است که زمان CPU استفاده شده توسط یک پراسس را می‌شمارد. بر خلاف virtual timer (که توسط سیگنال SIGVTALRM اطلاع داده می‌شود) یک تایمر پروفایل CPU time در هر دو مد user و kernel را با هم می‌شمارد.
  17. SIGQUIT:
    وقتی کاربر کاراکتر quit (Control-\) را روی صفحه کلید تایپ می‌کند این پراسس به foreground process group ارسال می‌شود. به صورت پیش‌فرض این سیگنال باعث terminate شدن پراسس و تولید یک فایل core dump می‌شود که از آن می‌توان برای دیباگ کردن استفاده کرد. این سیگنال برای زمانی که برنامه در داخل یک حلقه‌ی پایان ناپذیر گیر افتاده است یا در حالت دیگری که برنامه پاسخ نمی‌دهد بسیار مفید است. برای این کار دانستن برنامه‌ی دیباگر gdb مفید است. [Matloff, 2008] مثال (‌با فشردن Control-C می‌نویسد core dumped ولی فایلی ایجاد نمی‌کند. چرا؟)
  18. SIGSEGV: Segmentation Violation
    این سیگنال بسیار محبوب زمانی ایجاد می‌شود که برنامه به یک حافظه‌ی نادرست مراجعه کند. یک مراجعه‌ی نادرست به حافظه در یکی از شرایط زیر است:
    • صفحه‌ی درخواست شده وجود ندارد به عنوان مثال این صفحه در جایی بین heap و stack قرار گرفته است که هنوز map نشده است.
    • پراسس سعی در آپدیت مکانی در حافظه را دارد که به عنوان read only علامت خورده است (مثلا program text segment)
    • پراسس سعی در دسترسی به مکانی از حافظه‌ی کرنل را دارد در حالی که در user mode در حال اجراست
  19. SIGSTKFLT: stack fault on coprocessor: This signal is unused on Linux.
  20. SIGSTOP:
    سیگنال قطعی stop کردن پراسس. این سیگنال نه می‌تواند catch شود و نه ignore شود و نه بلاک گردد، لذا همیشه پراسس را stop می‌کند.
  21. SIGSYS or SIGUNUSED: This signal is generated if a process makes a "bad" system call. This means that the process executed an instruction that was interpreted as a system call trap, but the associated system call number was not valid. On Linux 2.4 and later, SIGUNUSED is synonymous with SIGSYS on many architectures. In other words, this signal number is no longer unused on those architectures.
  22. SIGTERM:
    سیگنال استاندارد برای terminate کردن یک پراسس است. این سیگنال، سیگنال پیش فرض ارسالی توسط دستورات kill و killall است. ارسال سیگنال SIGKILL به یک پراسس صراحتا توسط دستور kill -9 یا kill -KILL انجام می‌شود. ۹ شماره‌ی سیگنال SIGKILL است. یک برنامه‌ی خوش ساخت باید یک هندلر برای SIGTERM داشته باشد که باعث خاتمه یافتن تمیز برنامه شود مثلا فایل‌های موقتی را حذف کند و resource های گرفته شده را آزاد کند. همیشه باید اولین گزینه برای خاتمه‌ی برنامه SIGTERM باشد چون می‌توان برای آن handler نوشت. اگر SIGTERM جواب نداد از SIGKILL به عنوان آخرین گزینه استفاده می‌کنیم. یعنی از SIGKILL برای پراسسی استفاده می‌کنیم که به SIGTERM جواب نداده است.
  23. SIGTRAP:
    این سیگنال برای پیاده‌سازی breakpointهای دیباگر و trace کردن سیستم کال به صورتی که در strace(1) استفاده می‌شود مورد استفاده قرار می‌گیرد.
  24. SIGTSTP: Terminal stop. This is the job-control stop signal, sent to stop the foreground process group when the user types the suspend character (usually Control-Z) on the keyboard. process groups = jobs
  25. SIGTTIN: when running under a job-controll shell, the terminal driver sends this signal to a background process group when it attempts to read() from the terminal. This signal stops a process by default.
  26. SIGTTOU: when running under a job-control shell, if the TOSTOP (terminal output stop) option has been enabled for the terminal (perhaps via the command stty tostop), the terminal driver sends SIGTTOU to a background process group when it attempts to write() to the terminal. This signal stops a process by default.
  27. SIGURG: This signal is sent to a process to indicate the presence of out-of-band (also known as urgent) data on a socket.
  28. SIGUSR1: This signal and SIGUSR2 are available for programmer-defined purposes. The kernel never generates these signals for a process. Modern UNIX implementations provide a large set of realtime signals that are also available for programmer-defined purposes.
  29. SIGUSR2
  30. SIGVTALRM:
    کرنل این سیگنال را در موقع منقضی شدن یک virtual timer که توسط سیستم کال setitimer ست شده است ساطع می‌کند. virtual timer تایمری است که زمان مصرف شده توسط پراسس در user mode را می‌شمارد.
  31. SIGWINCH:
    در یک محیط پنجره‌ای در هنگام تغییر اندازه‌ی پنجره (خواه از طریق کاربر و یا از طریق برنامه به وسیله‌ی فراخوانی‌هایی مثل ioctl())، این سیگنال به foreground process group ارسال می‌شود. برنامه‌هایی مثل vi و less از این سیگنال استفاده می‌کنند.
  32. SIGXCPU:
    این سیگنال به پراسسی ارسال می‌شود که از محدودیت زمانی وضع شده روی زمان CPU اختصاص داده شده به آن تجاوز کند. (RLIMIT_CPU)
    مثال از سیستم کال getrlimit
  33. SIGXFSZ: This signal is sent to a process if it attempts (using write() or truncate()) to increase the size of a file beyond the process's file size resource limit (RLIMIT_FSIZE).

در سیگنال‌های استاندارد لیست شده در بالا اگر SIGLOST (که نام نمادین آن تعریف شده است ولی استفاده نمی‌شود) را حذف کنیم، ۳۲ سیگنال خواهیم داشت.

The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

نام سیگنال‌ها و شماره‌های آن‌ها

برای تغییر disposition یک سیگنال دو روش وجود دارد، یا استفاده از روش قدیمی‌تر سیستم کال signal و یا استفاده از سیستم کال sigaction. سیستم کال signal ساده‌تر است در حالی که سیستم کال sigaction قابلیت‌ها و امکانات بیشتری را فراهم می‌کند.

در پیاده‌سازی‌های مختلف UNIX تفاوت رفتار در مورد سیستم کال signal وجود دارد. به همین خاطر در برنامه‌های portable از این سیستم کال استفاده نکنید.

sigaction() is the (strongly) preferred API for establishing a signal handler.

اگرچه مستندات signal در بخش ۲ یعنی بخش سیستم کال‌های لینوکس قرار گرفته است ولی در واقع در glibc به عنوان یک تابع کتابخانه‌ای و به عنوان لایه‌ای روی سیستم کال sigaction پیاده‌سازی شده است.

سیستم کال signal به عنوان خروجی disposition قبلی همان سیگنال را برمی‌گرداند.

#include <signal.h>

void ( *signal(int sig, void (*handler)(int)) ) (int);

    Returns previous signal disposition on success, or SIG_ERR on error.

پروتوتایپ جالب سیستم کال signal به ما اجازه می‌دهد که موقتا disposition یک سیگنال را تغییر دهیم و سپس آن را به disposition اولیه برگردانیم.

اگر #define _GNU_SOURCE قبل از include کردن سر فایل <signal.h> آمده باشد glibc نوع داده‌ی غیر استاندارد sighandler_t را expose می‌کند. در غیر این صورت خودمان می‌توانیم این نوع داده را به صورت زیر تعریف کنیم.

typedef void (*sighandler_t)(int);

// usage
sighandler_t signal(int sig, sighandler_t handler);

تغییر disposition یک سیگنال و بازگرداندن disposition اولیه
پیاده‌سازی نوع داده‌ی sighandler_t و استفاده از آن

به جای مشخص کردن آدرس یک تابع به عنوان آرگومان handler برای سیستم کال signal می‌توانیم از مقادیر SIG_DFL و SIG_IGN استفاده کنیم (توضیحات صفحه‌ی ۳۹۸). این مقادیر هم به اضافه‌ی SIG_ERR اشاره‌گر به تابع هستند. این موضوع را در این مثال بررسی کرده‌ایم.

A signal handler (also called a signal catcher) is a function that is called when a specified signal is delivered to a process.

فراخوانی یک signal handler ممکن است در هر زمانی جریان اصلی برنامه را به هم بزند. کرنل از طرف پراسس تابع handler را فراخوانی می‌کند و وقتی تابع handler برگشت اجرای برنامه از همان جایی که قطع شده بود از سر گرفته می‌شود.

اگر چه signal handler ها می‌توانند هر کاری را انجام دهند ولی به صورت کلی باید تا حد امکان ساده و سر راست باشند.

مثال نصب یک signal handler برای سیگنال اینتراپت SIGINT

The terminal driver generates SIGINT signal when we type the terminal interrupt character, usually Control-C.)

SIGQUIT is generated by the terminal driver when we type the terminal quit character, usually Control-\.

از آن جایی که شماره‌ی سیگنالی که باعث فراخوانی یک signal handler شده است به عنوان آرگومان به handler ارسال می‌شود می‌توان از یک تابع handler برای هندل کردن چند سیگنال متفاوت استفاده کرد.

نصب یک سیگنال هندلر برای سه سیگنال متفاوت

سیگنال SIGTERM در صورت catch شدن دیگر برنامه را خاتمه نمی‌دهد و به سادگی از signal handler به جایی که سیگنال خورده است return می‌کند. این موضوع را در برنامه‌ی بالا بررسی کنید.

بنا به دلایلی که در ادامه خواهیم خواند اپلیکیشن‌های واقعی نباید هرگز توابع stdio را از داخل یک signal handler فراخوانی کنند.

یک پراسس می‌تواند از طریق سیستم کال kill(2) به پراسس دیگر سیگنال بزند. این سیستم کال متناظر دستور kill(1) است که در shell استفاده می‌کنیم. انتخاب نام kill برای ارسال سیگنال دلایل تاریخی دارد چون در روزهای اولیه‌ی یونیکس اکثر سیگنال‌ها باعث terminate شدن پراسس می‌شدند.

#include <signal.h>

int kill(pid_t pid, int sig);
    Returns 0 on success, or -1 on error

در مورد آرگومان اول یعنی pid چهار مورد مهم زیر باید مورد ملاحظه قرار گیرد:

  1. اگر pid بزرگتر از صفر باشد، سیگنال خواسته شده به پراسس با pid داده شده ارسال می‌شود.
  2. اگر pid مساوی صفر باشد سیگنال خواسته شده به تمام پراسس‌های قرار گرفته در داخل همان process group از جمله پراسس فراخوان ارسال می‌شود. (SUSv3 نکات دیگری در این مورد ذکر می‌کند. برای مطالعه به صفحه‌ی ۴۰۲ مراجعه کنید.)
  3. اگر pid مساوی -1 باشد، سیگنال خواسته شده به تمام پراسس‌هایی که پراسس فراخوان اجازه‌ی سیگنال زدن به آن‌ها را دارد منهای پراسس init و پراسس فراخوان ارسال می‌شود. سیگنال‌هایی که به این شیوه ارسال می‌شوند گاهی اوقات broadcast signals نامیده می‌شوند.
  4. اگر pid کوچک‌تر از -1 باشد، سیگنال خواسته شده به تمام پراسس‌های قرار گرفته در داخل process group ای که ID آن قدر مطلق مقدار pid است ارسال می‌شود. ارسال سیگنال به تمام پراسس‌های قرار گرفته در یک process group در shell job control کاربرد دارد.

اگر هیچ پراسسی با pid داده شده بر مبنای قواعد بالا پیدا نشود سیستم کال kill ناموفق خواهد بود و errno را با مقدار ESRCH ست می‌کند، به معنای No such process!

دسترسی‌های لازم برای ارسال سیگنال به پراسس دیگر:

  • پراسسی که توانایی CAP_KILL دارد می‌تواند به هر پراسسی سیگنال بزند.
  • پراسس init با PID=1 که با user و group کاربر root اجرا می‌شود استثنا از قاعده‌ی قبلی است. به این پراسس فقط می‌توان سیگنال‌هایی را ارسال کرد که برای آن‌ها signal handler نصب کرده باشد. این کار باعث می‌شود تا مدیر سیستم اشتباها باعث kill شدن init نشود. این پراسس برای عملکرد سیستم حیاتی است.
  • یک پراسسی که دسترسی خاصی ندارد و به اصطلاح unpriviledged است در صورتی می‌تواند به پراسس دیگر سیگنال بزند که real or effective user ID آن با real user ID یا set-user-ID ذخیره شده‌ی دریافت کننده‌ی سیگنال مطابق باشد (صفحه‌ی ۴۰۲). این قاعده اجازه می‌دهد که کاربران به برنامه‌های set-user-ID که آغاز کرده‌اند سیگنال بزنند بدون توجه به effetctive user ID فعلی پراسس گیرنده‌ی سیگنال. چک نکردن effective user ID پراسس گیرنده‌ی سیگنال به کار دیگری نیز می‌آید: این کار مانع ارسال سیگنال توسط یک کاربر به پراسس کاربر دیگر که در حال اجرای یک برنامه‌ی set-user-ID که متعلق به کاربر ارسال کننده‌ی سیگنال است می‌شود. (صفحه‌ی ۴۰۲)
  • با سیگنال SIGCONT به صورت دیگری رفتار می‌شود. یک پراسس unpriviledged می‌تواند این سیگنال را به هر پراسس دیگری در همان session ارسال کند، بدون هیچ‌گونه بررسی user ID. این قاعده به job-control shell ها اجازه می‌دهد تا job ها (process groupها)ی stop شده را دوباره آغاز کنند حتی اگر process های داخل job قبلا user ID هایشان را تغییر داده باشند.

اگر پراسس دسترسی لازم برای ارسال سیگنال به پراسس دیگر را نداشته باشد errno با EPERM پر می‌شود. اگر pid مجموعه‌ای از پراسس‌ها را مشخص کند،حتی در صورتی که فقط به یکی از پراسس‌ها سیگنال ارسال شود سیستم کال kill موفق خواهد بود.

Control-Z = SIGTSTP

نصب یک signal handler برای تمام سیگنال‌های استاندارد: با اجرای کد خواهید دید که هنگام نصب signal handler برای دو سیگنال SIGSTOP و SIGKILL برنامه هشدار عدم موفقیت می‌دهد.

برنامه‌ی ارسال سیگنال مشابه دستور kill(1)

در سیستم کال kill اگر آرگومان sig مقدار صفر داشته باشد، چیزی که به آن سیگنال null می‌گوییم، هیچ سیگنالی ارسال نمی‌شود در عوض بررسی خطا انجام می‌شود که آیا می‌شود به پراسس داده شده سیگنال ارسال کرد یا خیر. اگر ارسال سیگنال null با خطای ESRCH مواجه شود یعنی پراسسی با pid داده شده وجود ندارد. اگر errno با مقدار EPERM پر شود یعنی پراسس وجود دارد ولی ما اجازه‌ی سیگنال زدن به آن را نداریم. اگر سیستم کال با موفقیت به پایان برسد یقینا هم پراسس وجود دارد و هم اجازه‌ی سیگنال دهی به آن را داریم.

از آنجایی که کرنل در طول زمان process ID های قبلا اختصاص داده شده را بازیابی می‌کند و به پراسس‌های جدید اختصاص می‌دهد وجود یک process ID به معنای وجود process ID مورد نظر ما نیست. علاوه بر آن یک process ID می‌تواند وجود داشته باشد ولی زامبی باشد (زامبی یعنی پراسسی که مرده است ولی parent اش هنوز روی آن wait نکرده است تا status code آن را بخواند.

موضوع فوق را در برنامه‌ی پیش نوشته‌ی kill.c با ارسال سیگنال 0 به یک پراسس بررسی کنید.

به ازای هر پراسس یک دایرکتوری با PID آن پراسس در دایرکتوری proc به صورت /proc/<PID> قرار دارد.

روش‌های مختلفی برای اطلاع یافتن از این که آیا یک پراسس وجود دارد یا خیر وجود دارد. این روش‌ها علاوه بر روش فوق عبارتند از:

  • سیستم کال‌های خانواده‌ی wait
  • سمافورها و exclusive file locks
  • IPC channels such as pipes and FIFOs
  • دایرکتوری /proc/PID

توضیحات بیشتر در این موارد در صفحات ۴۰۳ و ۴۰۴.

پراسس یا thread می‌توانند از تابع raise(3) برای سیگنال زدن به خودشان استفاده کنند:

#include <signal.h>

int raise(int sig);
        Returns 0 on success, or nonzero on error.

تابع raise به سادگی با سیستم کال kill در یک پراسس single thread و تابع (3)pthread_kill در یک پراسس multi thread پیاده سازی می‌شود. تنها خطای ممکن EINVAL است که در صورت نادرست بودن شماره‌ی سیگنال حادث می‌شود. به همین خاطر معمولا خطای این تابع را بررسی نمی‌کنیم.

اگر یک thread در یک پراسس multi thread تابع raise را فراخوانی کند یک سیگنال دقیقا به همان thread ای که تابع raise را فراخوانی کرده است ارسال می‌شود. بر عکس این موضوع اگر یک thread سیستم کال kill(getpid(), sig); را فراخوانی کند، سیگنال به پراسس داده شده ارسال می‌شود و خود پراسس آن را تحویل یکی از thread ها می‌دهد که لزوما thread فراخوان سیستم کال kill نخواهد بود.

وقتی پراسس به خودش سیگنال بزند، چه به وسیله‌ی تابع raise و چه به وسیله‌ی سیستم کال kill، سیگنال فورا تحویل داده می‌شود (یعنی قبل از اینکه raise برگردد.)

مثال از تابع raise

تابع killpg به تمام پراسس‌های یک process group سیگنال می‌زند. killpg که روی سیستم کال kill پیاده‌سازی شده مشابه دستور kill(-pgrp, sig); است.

#include <signal.h>

int killpg(pid_t pgrp, int sig);
    Returns 0 on success, -1 on error.

اگر آرگومان pgrp صفر باشد سیگنال به تمام پراسس‌های موجود در همان process group فراخوان killpg ارسال می‌شود. SUSv3 در این مورد چیزی نگفته است ولی اکثر پیاده‌سازی‌های یونیکس به این شیوه عمل می‌کنند.

برای گرفتن توضیحات مربوط به یک سیگنال از تابع (3)strsignal استفاده می‌کنیم:

#include <string.h>

char *strsignal(int sig);
        Returns pointer to signal description string

تابع strsignal مشابه تابع strerror در سر فایل <string.h> تعریف شده است و در صورت درست نبودن شماره‌ی سیگنال مانند تابع strerror اشاره‌گر به متنی حاوی اینکه چنین سیگنالی وجود ندارد برمی‌گرداند. مزیت مهمی که تابع strsignal دارد این است که locale-sensitive است و پیغام متناظر با هر سیگنال به زبان local سیستم نمایش داده می‌شود.

مثال از تابع strsignal

مانند تابع strsignal که متناظر تابع strerror است، تابع (3)psignal نیز نظیر تابع (3)perror است. تابع psignal رشته‌ی داده شده به عنوان آرگومان دوم را در خروجی استاندارد همراه با یک : می‌نویسد و سپس توضیح مختص شماره سیگنال داده شده را در ادامه می‌آورد. این تابع نیز locale-sensitive است.

#include <signal.h>

void psignal(int sig, const char *msg);

مثال از تابع psignal

با نوع داده‌ی سیستمی sigset_t می‌توانیم مجموعه‌ای از سیگنال‌ها را نگهداری کنیم. وجود یک مجموعه‌ی سیگنال برای کارکرد بسیاری از سیستم کال‌های مرتبط با مبحث سیگنال ضروری است. در لینوکس ÷۱۷\۷مانند بسیاری دیگر از پیاده‌سازی‌های یونیکس نوع داده‌ی sigset_t یک bit mask است ولی انتخاب این ساختار داده در SUSv3 ضروری نیست و می‌توان از ساختارهای دیگری هم برای پیاده‌سازی این نوع داده استفاده کرد. تنها چیزی که SUSv3 نیاز دارد این است که نوع داده‌ی sigset_t قابلیت انتساب داشته باشد یعنی assignable باشد.

از تابع sigemptyset برای initialize کردن یک signal set استفاده می‌کنیم. این تابع تمام سیگنال‌های موجود در signal set داده شده را حذف می‌کند. تابع sigfillset نیز برای initialize کردن یک signal set به کار می‌رود با این تفاوت که تمام سیگنال‌های موجود و از جمله سیگنال‌های real time را به مجموعه اضافه می‌کند.

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);

        Both return 0 on success, or -1 on error

فقط باید از توابع sigemptyset و sigfillset برای initialize کردن signal set استفاده کنیم، برای اینکه C متغیرهای اتوماتیک را مقداردهی اولیه نمی‌کند و مقدار دهی متغیرهای استاتیک به صفر برای خالی کردن یک signal set روشی portable نیست چرا که signal set ها می‌توانند به جای bit mask با روش استراکچر پیاده‌سازی شده باشند. به همین دلیل استفاده از memset(3) و مقدار دهی آن فضای حافظه به صفر به منظور خالی کردن signal set نادرست است.

بعد از initialize کردن signal set به منظور اضافه کردن و یا حذف تکی سیگنال به/از signal set از توابع sigaddset و sigdelset استفاده می‌کنیم.

#include <signal.h>

int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);

    Both return 0 on success, or -1 on error

برای اینکه ببینیم آیا یک سیگنال در داخل siganl set هست یا نه از تابع sigismember استفاده می‌کنیم.

#include <signal.h>

int sigismember(const sigset_t *set, int sig);

        Retunrs 1 (true) if sig is a member of set, otherwise 0 (false)

کتابخانه‌ی زبان C پروژه‌ی GNU سه تابع غیر استاندارد زیر را که در واقع مکمل توابع استاندارد برای کار با signal set ها هستند معرفی کرده است:

#define _GNU_SOURCE
#include <signal.h>

int sigandset(sigset_t *dest, sigset_t *left, sigset_t *right);
int sigorset(sigset_t  *dest, sigset_t *left, sigset_t *right);

        Both return 0 on success, or -1 on error

int sigisemptyset(const sigset_t *set);

        Returns 1 (true) if set is empty, otherwise 0

تابع sigandset سیگنال‌هایی که در هر دو signal setهای left و right وجود دارد را در signal set مقصد قرار می‌دهد. تابع sigorset نیز سیگنال‌هایی که حداقل در یکی از signal setهای left یا right باشد را در signal set مقصد قرار می‌دهد.

and = intersection = اشتراک
or = union = اجتماع

ثابت NSIG در سرفایل <signal.h> تعریف شده است. این ثابت به گونه‌ای طراحی شده که مقدارش همیشه یکی بیشتر از بزرگ‌ترین عدد سیگنال تعریف شده باشد. از این عدد می‌توان به عنوان کران بالا در حلقه‌ها استفاده کرد.

نمایش مقدار NSIG
برنامه‌ای جانبی برای تمرین روشن و خاموش کردن بیت‌ها

کرنل برای هر پراسسی یک signal mask دارد. سیگنال‌های موجود در signal mask در صورت ساطع شدن در حال حاضر به پراسس تحویل داده نمی‌شوند و به قولی این سیگنال‌ها بلاک شده‌اند. اگر سیگنالی که در در داخل signal mask قرار دارد و بلاک شده است به پراسسی ارسال شود کرنل آن را تحویل پراسس نمی‌دهد تا وقتی که پراسس آن سیگنال را از signal mask حذف کند.

در فصل ۳۳ خواهیم دید که signal mask در واقع attribute یا ویژگی thread است و در یک برنامه‌ی multi thread هر thread می‌تواند به صورت مستقل signal mask خود را از طریق تابع (3)pthread_sigmak مدیریت کند.

یک سیگنال به سه روش می‌تواند به signal mask اضافه شود:

  1. وقتی که یک signal handler احضار می‌شود، سیگنالی که باعث احضار هندلر شده می‌تواند به صورت اتوماتیک به signal mask اضافه می‌شود. اینکه این اتفاق بیفتد یا نیفتد بستگی به flag هایی دارد که موقع رجیستر کردن signal handler با سیستم کال (2)sigaction استفاده کرده‌ایم.
  2. هنگام نصب یک signal handler با سیستم کال sigaction امکان‌پذیر است که یک signal set اضافی به سیستم کال فرستاد تا در زمان احضار هندلر رجیستر شده سیگنال‌های موجود در این signal set بلاک شوند.
  3. در هر زمانی می‌توان از سیستم کال sigprocmask(2) استفاده کرد تا صراحتا به signal mask سیگنال اضافه کرد یا از آن سیگنال حذف کرد.

از سیستم کال sigprocmask(2) می‌توان برای تغییر signal mask پراسس یا بازیابی signal mask فعلی پراسس و یا هر دو عمل در یک زمان استفاده کرد.

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

                            returns 0 on success, or -1 on error

اینکه باید چه تغییری در signal mask ایجاد شود توسط آرگومان how مشخص می‌شود:

  • SIG_BLOCK: سیگنال‌هایی که در داخل مجموعه سیگنال set قرار دارند به لیست سیگنال‌های بلاک شده اضافه می‌شوند، یعنی به signal mask اضافه می‌شوند.
  • SIG_UNBLOCK: سیگنال‌هایی که در داخل مجموعه سیگنال set قرار دارند از لیست سیگنال‌های بلاک شده حذف می‌شوند، یعنی از signal mask حذف می‌شوند. حذف سیگنالی که در signal mask نیست باعث ایجاد خطا نمی‌شود.
  • SIG_SETMASK: سیگنال‌های قرار گرفته در مجموعه سیگنال set جایگزین signal mask کنونی می‌شود، یعنی signal mask کاملا رونویسی می‌شود.

اگر آرگومان سوم یعنی oldset مقدار NULL نداشته باشد با signal mask قبلی (قبل از تغییر) پر می‌شود.

برای بازیابی signal mask فعلی بدون تغییر آن می‌توانیم set را مقدار NULL بدهیم. در این صورت مقدار how نادیده گرفته می‌شود. در این صورت مجموعه سیگنال oldset با signal mask فعلی پر می‌شود.

SUSv3 می‌گوید که اگر هر سیگنال pending ای به وسیله‌ی فراخوانی سیستم کال sigprocmask آنبلاک شود حداقل یکی از آن‌ها قبل از بازگشت سیستم کال به پراسس تحویل داده می‌شود. به عبارت دیگر اگر یک pending signal را آنبلاک کنیم، آن سیگنال فورا تحویل پراسس می‌شود.

تلاش برای بلاک سیگنال‌های SIGSTOP و SIGKILL بدون تولید خطا نادیده گرفته می‌شود.

مثال از sigaddset و sigemptyset و sigprocmask و بلاک نتوانستن کردن SIGSTOP و SIGKILL را.
مثال از sigemptyset و sigaddset و sigismember
مثال از sigfillset و sigdelset و sigismember
بلاک کردن سیگنال‌های وارده از خط فرمان و سپس مشاهده‌ی سیگنال‌های بلاک شده

وقتی پراسسی سیگنالی که بلاک کرده است را دریافت کند آن سیگنال به مجموعه‌ی سیگنال‌های pending پراسس اضافه می‌شود. (این ساختار داده توسط کرنل مدیریت می‌شود؟) اگر بعدا سیگنال آنبلاک شود آن سیگنال به پراسس تحویل داده می‌شود. برای فهمیدن اینکه کدام سیگنال‌ها الان در وضعیت pending هستند از سیستم کال sigpending(2) استفاده می‌کنیم.

#include <signal.h>

int sigpending(sigset_t *set);

    Returns 0 on success, or -1 on error.

سیستم کال sigpending استراکت فراهم شده از نوع sigset_t را با سیگنال‌های pending حال حاضر پر می‌کند. در بررسی این signal set باز هم به اهمیت تابع sigismember و کار راه اندازی آن پی‌می‌بریم.

اگر disposition یک سیگنال pending را تغییر دهیم، وقتی که سیگنال آنبلاک شد، سیگنال بر مبنای disposition جدید هندل می‌شود. بر همین مبنا اگر رفتار پیش‌فرض در مواجهه با یک سیگنال نادیده گرفتن یا ignore کردن آن باشد بعد از تغییر disposition سیگنال بلاک شده به SIG_IGN و یا SIG_DFL آن سیگنال از pending signal set حذف می‌شود. مثال

مجموعه‌ی سیگنال‌های pending فقط یک bitmask است که می‌گوید فلان سیگنال رخ داده ولی نمی‌داند که چند بار رخ داده است. به عبارت دیگر در صورت آنبلاک شدن سیگنال حتی در صورت وقوع چندباره فقط یک بار تحویل می‌شود. (سیگنال‌های realtime همان گونه که در فصل ۲۲ خواهیم دید فرق دارند و queue می‌شوند.)

یک سیگنال بلاک شده فقط یکبار تحویل داده می‌شود فارغ از اینکه چند بار ساطع شده باشد.

بهره‌گیری از سیستم کال‌های pause(2) و sigsuspend(2) جهت منتظر رسیدن سیگنال شدن، روش موثرتر و بهتری در استفاده از CPU نسبت به روش حلقه و تست یا به اصطلاح busy-wait است.

حتی اگر یک پراسس سیگنالی را بلاک نکند باز هم ممکن است تعداد سیگنال کمتری نسبت به سیگنال‌هایی که به آن زده شده است دریافت کند. این مورد زمانی ممکن است اتفاق بیفتد که سیگنال‌ها به قدری سریع ارسال می‌شوند که که پراسس گیرنده شانس کمی برای schedule شدن توسط کرنل دارد، لذا چندین سیگنال فقط یکبار در مجموعه سیگنال‌های pending پراسس ثبت می‌شود. علت این امر این است که هر بار که پراسس ارسال کننده‌ی سیگنال، CPU را به دست می‌گیرد چندین سیگنال ارسال می‌کند. از این میان فقط یک سیگنال در مجموعه سیگنال‌های pending پراسس گیرنده ثبت می‌شود و در هنگام در اختیار گرفتن CPU تنها یکبار روتین هندلر مربوطه را اجرا می‌کند. (صفحه ۴۱۴)

روش اجرای دو برنامه‌ی زیر، که برای بررسی صحت موارد فوق نوشته شده‌اند را در صفحات ۴۱۳ و ۴۱۴ مشاهده کنید.

ارسال کننده‌ی سریع سیگنال
دریافت کننده‌ی سیگنال

از سیستم کال sigaction(2) همانند سیستم کال signal(2) برای ست کردن disposition سیگنال استفاده می‌کنیم. کار با این سیستم کال اگرچه اندکی سخت‌تر است ولی انعطاف بسیار بیشتری نسبت به سیستم کال signal برای برنامه‌نویس فراهم می‌کند.

#include <signal.h>

struct sigaction {
    void     (*sa_handler)(int);    /* Address of handler */
    sigset_t sa_mask;               /* Signals blocked during handler invocation*/
    int      sa_flags;              /* Flags controlling handler invocation */
    void     (*sa_restorer)(void);  /* Not for application use */
};

int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);

                                           Returns 0 on success, or -1 on error

به خاطر دارید که در سیستم کال signal فقط با تغییر disposition می‌توانستیم diposition فعلی (و در واقع حالا دیگر قبلی!) را به دست بیاوریم. سیستم کال sigaction به ما این امکان را می‌دهد که بدون تغییر disposition به آن دست یابیم و با ست کردن چندین attribute کنترل کنیم که در موقع فراخوانی signal handler دقیقا چه اتفاقی بیفتد. علاوه بر این sigaction نسبت به signal قابل حمل‌تر یا portable تر است.

آرگومان sig می‌تواند هر سیگنالی به جز SIGKILL و SIGSTOP باشد. این آرگومان نشانه‌ی سیگنالی است که قصد داریم disposition آن را ست یا بازیابی کنیم.

struct sigaction داده شده در بالا اندکی ساده‌تر از استراکت اصلی است.

دو فیلد sa_mask و sa_flags فقط در صورتی تفسیر می‌شوند که فیلد sa_handler مقداری غیر از SIG_IGN و SIG_DFL داشته باشد.

سیگنال‌های موجود در فیلد sa_mask که هم اکنون در لیست signal mask پراسس نیستند قبل از فراخوانی signal handler به صورت اتوماتیک به signal mask اضافه می‌شوند و وقتی signal handler برمی‌گردد یا return می‌کند باز هم به صورت خودکار این بار از لیست signal mask پراسس حذف می‌شوند.

فیلد sa_mask به ما اجازه می‌دهد تا لیستی از سیگنال‌هایی را مشخص کنیم که اجازه ندارند اجرای روتین signal handler این سیگنال را با وقفه روبرو کنند. سیگنالی که باعث اجرای signal handler می‌شود به صورت خودکار به signal mask پراسس اضافه خواهد شد. این به آن معناست که signal handler در طی اجرای خودش مجددا با همین سیگنال روبرو نخواهد شد و دوباره اجرا نخواهد شد. لازم به یادآوری است از آنجا که سیگنال‌های بلاک شده در طی اجرای signal handler به اصطلاح queue نمی‌شوند در صورت وقوع چندباره پس از آنبلاک شدن فقط یک بار تحویل می‌شوند.

فیلد sa_flags یک bitmask است که آپشن‌های مختلفی را در مورد چگونگی هندل کردن سیگنال مشخص می‌کند. بیت‌های زیر می‌تواند در فیلد sa_flags با هم OR شوند:

  1. SA_NOCLDSTOP: If sig is SIGCHLD don't generate this signal when a child process is stopped or resumed as a consequence of receiving a signal. (Refer to section 26.3.2)
  2. SA_NOCLDWAIT: If sig is SIGCHLD, don't transform children into zombies when they terminate. (Refer to section 26.3.3)
  3. SA_NODEFER or SA_NOMASK: When this signal is caught, don't automatically add it to the process signal mask while the handler is executing. The name SA_NOMASK is provided as a historical synonym for SA_NODEFER.
  4. SA_ONSTACK: Invoke the handler for this signal using an alternate stack installed by sigaltstack(2). (Refer to section 21.3)
  5. SA_RESETHAND or SA_ONESHOT: When this signal is caught, reset its disposition to the default (i.e., SIG_DFL). The name SA_ONESHOT is provided as a historical synonym for SA_RESETHAND. Example
  6. SA_RESTART: Automatically restart system calls interrupted by this signal handler. (Refer to section 21.5)
  7. SA_SIGINFO: Invoke the signal handler with additional arguments providing further information about the signal. (Refer to section 21.4)

سیگنال شماره‌ی ۳۲ و ۳۳ وجود ندارد.

مثال نسبتا جامع از سیستم کال sigaction همراه با کاربرد فیلدهای SA_RESTART و SA_NOCLDSTOP
مثال از فلگ SA_NOCLDWAIT (بعد از اجرا و قبل از خاتمه‌ی برنامه خروجی دستور $ ps aux را بررسی کنید.)

سیستم کال pause(2) اجرای برنامه را معلق یا suspend می‌کند تا زمانی که این سیستم کال با فراخوانی یک signal handler و یا یک سیگنال catch نشده که باعث terminate برنامه شود مواجه شود. سیستم کال pause فقط در صورت catch شدن سیگنال و بازگشتن signal handler برمی‌گردد.

#include <unistd.h>

int pause(void);

    always returns -1 with errno set to EINTR

سیستم کال pause با ست کردن فیلد sa_flags به SA_RESTART در struct sigaction، در صورت سیگنال خوردن restart نمی‌شود. مثال

سیگنال یک notfication است که به پراسس گیرنده می‌گوید اتفاقی رخ داده است. پراسس می‌تواند سیگنال را از کرنل یا از پراسس دیگر یا از خودش دریافت کند.

تحویل سیگنال یا به عبارت دیگر دریافت سیگنال معمولا asynchronous است یعنی زمانی که یک سیگنال اجرای برنامه را با وقفه روبرو می‌کند قابل پیش‌بینی نیست. در بعضی موارد مثل سیگنال‌های تولیدی توسط سخت افزار، سیگنال‌ها به صورت synchronous تحویل داده یا دریافت می‌شوند یعنی این عمل تحویل یا دریافت به صورت پیش‌بینی پذیر و قابل تولید در نقاط مشخصی از اجرای برنامه اتفاق می‌افتد.

به صورت پیش‌فرض یک سیگنال در صورت وقوع:

  1. نادیده گرفته می‌شود.
  2. پراسس را با core dump یا بدون آن terminate می‌کند.
  3. پراسس در حال اجرا را STOP می‌کند.
  4. پراسس STOP شده را restart می‌کند.

این که رفتار پیش‌فرض در مواجهه با سیگنال چه باشد بستگی به نوع سیگنال دارد.

برای نصب یک signal handler بهتر است از سیستم کال sigaction استفاده کنیم و نه siganl. اولی portable تر است.

هر پراسسی یک signal mask دارد. signal mask یک مجموعه از سیگنال‌هایی است که در حال حاضر در صورت وقوع به پراسس تحویل داده نمی‌شوند. (رجوع کنید به سیستم کال sigprocmask(2))

Using pause(2), a process can suspend execution until a signal arrives.

مطالعه‌ی بیشتر

نوشته شده در: 1405-03-10 (1 هفته 1 روز 5 ساعت پیش)

من محسن هستم؛ برنامه‌نویس تفننی!

برای ارتباط با من یا در همین سایت کامنت بگذارید و یا به dokaj.ir(at)gmail.com ایمیل بزنید.

پست بعدی: فصل ۱۰ - Time

در مورد این مطلب یادداشتی بنویسید.