Sự khác biệt giữa tín hiệu và tín hiệu là gì?


143

Tôi chuẩn bị thêm một trình xử lý tín hiệu bổ sung vào một ứng dụng chúng tôi có ở đây và tôi nhận thấy rằng tác giả đã sử dụng sigaction()để thiết lập các trình xử lý tín hiệu khác. Tôi sẽ sử dụng signal(). Để theo quy ước tôi nên sử dụng sigaction()nhưng nếu tôi viết từ đầu, tôi nên chọn cái nào?

Câu trả lời:


167

Sử dụng sigaction()trừ khi bạn có lý do rất thuyết phục để không làm như vậy.

Các signal()giao diện có cổ (và vì thế sẵn có) ủng hộ nó, và nó được định nghĩa trong tiêu chuẩn C. Tuy nhiên, nó có một số đặc điểm không mong muốn sigaction()tránh được - trừ khi bạn sử dụng các cờ được thêm rõ ràng sigaction()để cho phép nó mô phỏng trung thực signal()hành vi cũ .

  1. Các signal()chức năng không (nhất thiết) chặn các tín hiệu khác từ đến khi xử lý hiện nay đang thực hiện; sigaction()có thể chặn các tín hiệu khác cho đến khi trình xử lý hiện tại trở lại.
  2. Các signal()chức năng (thường) reset lại hành động tín hiệu SIG_DFL(mặc định) cho hầu hết các tín hiệu. Điều này có nghĩa là signal()trình xử lý phải tự cài đặt lại như là hành động đầu tiên của nó. Nó cũng mở ra một cửa sổ dễ bị tổn thương giữa thời điểm tín hiệu được phát hiện và trình xử lý được cài đặt lại trong đó nếu một trường hợp thứ hai của tín hiệu đến, hành vi mặc định (thường chấm dứt, đôi khi có thành kiến ​​- còn gọi là kết xuất lõi).
  3. Hành vi chính xác của signal()các hệ thống khác nhau - và các tiêu chuẩn cho phép các biến thể đó.

Đây thường là những lý do tốt để sử dụng sigaction()thay vì signal(). Tuy nhiên, giao diện của sigaction()không thể phủ nhận là khó sử dụng hơn.

Cho dù hai bạn sử dụng, không bị cám dỗ bởi các giao diện tín hiệu thay thế chẳng hạn như sighold(), sigignore(), sigpause()sigrelse(). Chúng là những lựa chọn thay thế trên danh nghĩa sigaction(), nhưng chúng chỉ được chuẩn hóa và hầu như không có trong POSIX để tương thích ngược hơn là sử dụng nghiêm túc. Lưu ý rằng tiêu chuẩn POSIX cho biết hành vi của họ trong các chương trình đa luồng là không xác định.

Các chương trình và tín hiệu đa luồng là một câu chuyện phức tạp khác. AFAIK, cả hai signal()sigaction()đều ổn trong các ứng dụng đa luồng.

Cornstalks quan sát :

Trang người dùng Linux cho signal()biết:

  Ảnh hưởng của signal()trong một quy trình đa luồng là không xác định.

Vì vậy, tôi nghĩ sigaction()là duy nhất có thể được sử dụng một cách an toàn trong một quy trình đa luồng.

Nó thật thú vị. Trang hướng dẫn Linux hạn chế hơn POSIX trong trường hợp này. POSIX chỉ định cho signal():

Nếu quy trình là đa luồng hoặc nếu quy trình là đơn luồng và trình xử lý tín hiệu được thực thi khác với kết quả của:

  • Việc kêu gọi quá trình abort(), raise(), kill(), pthread_kill(), hoặc sigqueue()để tạo ra một tín hiệu mà không bị chặn
  • Một tín hiệu đang chờ xử lý được bỏ chặn và được gửi trước khi cuộc gọi được bỏ chặn nó trả về

hành vi không được xác định nếu trình xử lý tín hiệu đề cập đến bất kỳ đối tượng nào ngoài errnothời lượng lưu trữ tĩnh ngoài việc gán giá trị cho đối tượng được khai báo là volatile sig_atomic_thoặc nếu trình xử lý tín hiệu gọi bất kỳ chức năng nào được xác định trong tiêu chuẩn này ngoài một trong các chức năng được liệt kê trong Khái niệm tín hiệu .

Vì vậy, POSIX chỉ định rõ ràng hành vi của signal()trong một ứng dụng đa luồng.

Tuy nhiên, về cơ bản, sigaction()được ưu tiên trong mọi trường hợp - và mã đa luồng di động nên sử dụng sigaction()trừ khi có lý do áp đảo tại sao nó không thể (chẳng hạn như "chỉ sử dụng các hàm được xác định bởi Tiêu chuẩn C" - và có, mã C11 có thể là đa -đọc). Đó là cơ bản những gì đoạn mở đầu của câu trả lời này cũng nói.


12
Mô tả signalnày thực sự là về hành vi của Unix System V. POSIX cho phép hành vi này hoặc hành vi BSD lành mạnh hơn nhiều, nhưng vì bạn không thể chắc chắn mình sẽ nhận được hành vi nào, nên vẫn tốt nhất để sử dụng sigaction.
R .. GitHub DỪNG GIÚP ICE

1
trừ khi bạn sử dụng các cờ được thêm rõ ràng vào sigaction () để cho phép nó mô phỏng trung thực hành vi tín hiệu () cũ. Những lá cờ đó sẽ là gì (cụ thể)?
ChristianCuevas

@AlexFritz: Chủ yếu SA_RESETHAND, cũng có SA_NODEFER.
Jonathan Leffler

2
@BulatM. Nếu bạn không thể sử dụng sigaction(), thì về cơ bản bạn bắt buộc phải sử dụng thông số kỹ thuật tiêu chuẩn C cho signal(). Tuy nhiên, điều đó mang lại cho bạn một bộ tùy chọn cực kỳ nghèo nàn cho những gì bạn có thể làm. Bạn có thể: sửa đổi (phạm vi tệp) các biến loại volatile sig_atomic_t; gọi một trong các chức năng 'thoát nhanh' ( _Exit(), quick_exit()) hoặc abort(); gọi signal()với số tín hiệu hiện tại làm đối số tín hiệu; trở về. Và đó là nó. Bất cứ điều gì khác không được đảm bảo là di động. Điều đó nghiêm ngặt đến mức hầu hết mọi người bỏ qua các quy tắc đó - nhưng mã kết quả là tinh ranh.
Jonathan Leffler

1
sigaction()Bản demo tuyệt vời từ chính GCC: gnu.org/software/libc/manual/html_node/ mẹo ; và signal()bản demo tuyệt vời từ chính GCC: gnu.org/software/libc/manual/html_node/ . Lưu ý rằng trong signalbản demo, họ tránh thay đổi trình xử lý từ bỏ qua ( SIG_IGN) nếu đó là những gì trước đây nó được cố ý đặt thành.
Gabriel Staples

8

Đối với tôi, dòng dưới đây là đủ để quyết định:

Hàm sigaction () cung cấp một cơ chế toàn diện và đáng tin cậy hơn để kiểm soát tín hiệu; các ứng dụng mới nên sử dụng sigaction () thay vì signal ()

http://pub.opengroup.org/onlinepub/009695399/fifts/signal.html#tag_03_690_07

Cho dù bạn đang bắt đầu từ đầu hoặc sửa đổi một chương trình cũ, sigaction nên là lựa chọn phù hợp.


5

Chúng là các giao diện khác nhau cho các cơ sở tín hiệu của HĐH. Mọi người nên ưu tiên sử dụng sigaction để báo hiệu nếu có thể vì tín hiệu () có hành vi được xác định theo triển khai (thường là theo chủng tộc) và hoạt động khác nhau trên Windows, OS X, Linux và các hệ thống UNIX khác.

Xem lưu ý bảo mật này để biết chi tiết.


1
Tôi chỉ nhìn vào mã nguồn glibc và signal () chỉ gọi vào sigaction (). Cũng xem ở trên nơi trang người đàn ông MacOS tuyên bố tương tự.
bmdhacks

Đó là tốt để biết. Tôi chỉ từng thấy các trình xử lý tín hiệu được sử dụng để đóng mọi thứ gọn gàng trước khi thoát, vì vậy tôi thường không dựa vào hành vi để làm với việc cài đặt lại trình xử lý.
Matthew Smith

5

signal () là chuẩn C, sigaction () thì không.

Nếu bạn có thể sử dụng một trong hai (nghĩa là bạn đang sử dụng hệ thống POSIX), thì hãy sử dụng sigaction (); không xác định được liệu tín hiệu () có đặt lại trình xử lý hay không, nghĩa là để có thể di động, bạn phải gọi lại tín hiệu () bên trong trình xử lý. Điều tồi tệ hơn là có một cuộc đua: nếu bạn nhận được hai tín hiệu liên tiếp và lần thứ hai được gửi trước khi bạn cài đặt lại trình xử lý, bạn sẽ có hành động mặc định, có thể sẽ giết quá trình của bạn. sigaction () , mặt khác, được đảm bảo sử dụng ngữ nghĩa tín hiệu của độ tin cậy. Bạn không cần phải cài đặt lại trình xử lý, bởi vì nó sẽ không bao giờ được thiết lập lại. Với SA_RESTART, bạn cũng có thể nhận được một số cuộc gọi hệ thống để tự động khởi động lại (vì vậy bạn không phải kiểm tra EINTR theo cách thủ công). hướng dẫn () có nhiều lựa chọn hơn và đáng tin cậy, vì vậy việc sử dụng nó được khuyến khích.

Psst ... đừng nói với ai tôi đã nói với bạn điều này, nhưng POSIX hiện có hàm bsd_signal () hoạt động như signal () nhưng mang lại ngữ nghĩa BSD, nghĩa là nó đáng tin cậy. Công dụng chính của nó là để chuyển các ứng dụng cũ giả định tín hiệu đáng tin cậy và POSIX không khuyến nghị sử dụng nó.


POSIX không có chức năng bsd_signal()- một số triển khai POSIX có thể có chức năng, nhưng bản thân POSIX không chứa chức năng đó (xem POSIX ).
Jonathan Leffler

4

Nói ngắn gọn:

sigaction()là tốt và được xác định rõ, nhưng là một chức năng của Linux và vì vậy nó chỉ hoạt động trên Linux. signal()là xấu và được định nghĩa kém, nhưng là một hàm tiêu chuẩn C và vì vậy nó hoạt động trên mọi thứ.

Các trang người Linux nói gì về nó?

man 2 signal(xem nó trực tuyến ở đây ) nêu:

Hành vi của signal () khác nhau giữa các phiên bản UNIX và cũng đã thay đổi theo lịch sử trên các phiên bản Linux khác nhau. Tránh sử dụng của nó: sử dụng sigaction(2)thay thế. Xem tính di động dưới đây.

Tính di động Việc sử dụng tín hiệu di động () di động duy nhất là đặt bố trí tín hiệu thành SIG_DFL hoặc SIG_IGN. Các ngữ nghĩa khi sử dụng tín hiệu () để thiết lập trình xử lý tín hiệu khác nhau giữa các hệ thống (và POSIX.1 cho phép rõ ràng sự thay đổi này); không sử dụng nó cho mục đích này.

Nói cách khác: không sử dụng signal() . Sử dụng sigaction()thay thế!

GCC nghĩ gì?

Lưu ý tương thích: Như đã nói ở trên signal, nên tránh chức năng này khi có thể. sigactionlà phương pháp ưa thích.

Nguồn: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

Vì vậy, nếu cả Linux và GCC đều nói không sử dụng signal()sigaction()thay vào đó, sẽ đặt ra câu hỏi: làm thế nào để chúng ta sử dụng cái này khó hiểusigaction() thứ này!?

Ví dụ sử dụng:

Đọc signal()ví dụ TUYỆT VỜI của GCC tại đây: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling

sigaction()ví dụ TUYỆT VỜI của họ ở đây: https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

Sau khi đọc những trang đó, tôi đã nghĩ ra kỹ thuật sau đây cho sigaction():

1. sigaction(), vì đó là cách đúng để gắn bộ xử lý tín hiệu, như được mô tả ở trên:

#include <errno.h>  // errno
#include <signal.h> // sigaction()
#include <stdio.h>  // printf()
#include <string.h> // strerror()

#define LOG_LOCATION __FILE__, __LINE__, __func__ // Format: const char *, unsigned int, const char *
#define LOG_FORMAT_STR "file: %s, line: %u, func: %s: "

/// @brief      Callback function to handle termination signals, such as Ctrl + C
/// @param[in]  signal  Signal number of the signal being handled by this callback function
/// @return     None
static void termination_handler(const int signal)
{
    switch (signal)
    {
    case SIGINT:
        printf("\nSIGINT (%i) (Ctrl + C) signal caught.\n", signal);
        break;
    case SIGTERM:
        printf("\nSIGTERM (%i) (default `kill` or `killall`) signal caught.\n", signal);
        break;
    case SIGHUP:
        printf("\nSIGHUP (%i) (\"hang-up\") signal caught.\n", signal);
        break;
    default:
        printf("\nUnk signal (%i) caught.\n", signal);
        break;
    }

    // DO PROGRAM CLEANUP HERE, such as freeing memory, closing files, etc.


    exit(signal);
}

/// @brief      Set a new signal handler action for a given signal
/// @details    Only update the signals with our custom handler if they are NOT set to "signal ignore" (`SIG_IGN`),
///             which means they are currently intentionally ignored. GCC recommends this "because non-job-control
///             shells often ignore certain signals when starting children, and it is important for children
///             to respect this." See
///             https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
///             and https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html.
///             Note that termination signals can be found here:
///             https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
/// @param[in]  signal  Signal to set to this action
/// @param[in]  action  Pointer to sigaction struct, including the callback function inside it, to attach to this signal
/// @return     None
static inline void set_sigaction(int signal, const struct sigaction *action)
{
    struct sigaction old_action;

    // check current signal handler action to see if it's set to SIGNAL IGNORE
    sigaction(signal, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN)
    {
        // set new signal handler action to what we want
        int ret_code = sigaction(signal, action, NULL);
        if (ret_code == -1)
        {
            printf(LOG_FORMAT_STR "sigaction failed when setting signal to %i;\n"
                   "  errno = %i: %s\n", LOG_LOCATION, signal, errno, strerror(errno));
        }
    }
}

int main(int argc, char *argv[])
{
    //...

    // Register callbacks to handle kill signals; prefer the Linux function `sigaction()` over the C function
    // `signal()`: "It is better to use sigaction if it is available since the results are much more reliable."
    // Source: https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
    // and /programming/231912/what-is-the-difference-between-sigaction-and-signal/232711#232711.
    // See here for official gcc `sigaction()` demo, which this code is modeled after:
    // https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html

    // Set up the structure to specify the new action, per GCC's demo.
    struct sigaction new_action;
    new_action.sa_handler = termination_handler; // set callback function
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;

    // SIGINT: ie: Ctrl + C kill signal
    set_sigaction(SIGINT, &new_action);
    // SIGTERM: termination signal--the default generated by `kill` and `killall`
    set_sigaction(SIGTERM, &new_action);
    // SIGHUP: "hang-up" signal due to lost connection
    set_sigaction(SIGHUP, &new_action);

    //...
}

2. Và cho signal() mặc dù không phải là cách tốt để gắn bộ xử lý tín hiệu, như được mô tả ở trên, vẫn rất tốt để biết cách sử dụng nó.

Đây là mã trình diễn GCC được sao chép, vì nó sẽ tốt như nó sẽ nhận được:

#include <signal.h>

void
termination_handler (int signum)
{
  struct temp_file *p;

  for (p = temp_file_list; p; p = p->next)
    unlink (p->name);
}

int
main (void)
{
  
  if (signal (SIGINT, termination_handler) == SIG_IGN)
    signal (SIGINT, SIG_IGN);
  if (signal (SIGHUP, termination_handler) == SIG_IGN)
    signal (SIGHUP, SIG_IGN);
  if (signal (SIGTERM, termination_handler) == SIG_IGN)
    signal (SIGTERM, SIG_IGN);
  
}

Các liên kết chính cần lưu ý:

  1. Tín hiệu chuẩn: https://www.gnu.org/software/libc/manual/html_node/Stiteria-Signals.html#St Chuẩn-Signals
    1. Tín hiệu chấm dứt: https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html#Termination-Signals
  2. Xử lý tín hiệu cơ bản, bao gồm signal()ví dụ sử dụng GCC chính thức : https://www.gnu.org/software/libc/manual/html_node/Basic-Signal-Handling.html#Basic-Signal-Handling
  3. sigaction()Ví dụ sử dụng GCC chính thức : https://www.gnu.org/software/libc/manual/html_node/Sigaction-Function-Example.html
  4. Bộ tín hiệu, bao gồm sigemptyset()sigfillset(); Tôi vẫn không hiểu chính xác những điều này, nhưng biết rằng chúng rất quan trọng: https://www.gnu.org/software/libc/manual/html_node/Signal-Sets.html

Xem thêm:

  1. Hướng dẫn Xử lý tín hiệu C ++ [với mã trình diễn xuất sắc]: https://www.tutorialspoint.com/cplusplus/cpp_signal_handling.htmlm
  2. https: //www.tutorialspoint.com/c_stiteria_l Library / signal_h.htm

2

Từ signal(3)trang người đàn ông:

SỰ MIÊU TẢ

 This signal() facility is a simplified interface to the more
 general sigaction(2) facility.

Cả hai đều gọi cùng một cơ sở bên dưới. Có lẽ bạn không nên thao túng phản hồi của một tín hiệu bằng cả hai, nhưng trộn chúng không nên làm bất cứ điều gì bị phá vỡ ...


Điều đó không có trong trang người đàn ông của tôi! Tất cả những gì tôi nhận được là "MÔ TẢ Cuộc gọi hệ thống signal () cài đặt trình xử lý tín hiệu mới cho tín hiệu có ký hiệu số." Tôi cần nâng cấp lên gói trang man hữu ích .
Matthew Smith

1
Đó là trang Mac OS X 10.5.
dmckee --- ex-moderator mèo con

Cũng được xác minh từ mã nguồn của glibc. signal () chỉ gọi sigaction ()
bmdhacks

2
Tuy nhiên, điều này không đúng với tất cả các triển khai tín hiệu. Nếu bạn muốn bắt buộc hành vi "sigaction", đừng dựa vào giả định này.
Ben Burns

1

Tôi cũng sẽ đề nghị sử dụng sigaction () over signal () và muốn thêm một điểm nữa. sigaction () cung cấp cho bạn nhiều tùy chọn hơn như pid của quá trình đã chết (có thể sử dụng cấu trúc siginfo_t).


0

Về lý thuyết, tôi sẽ sử dụng tín hiệu () vì nó dễ mang theo hơn. Tôi sẽ bỏ phiếu cho bất kỳ người bình luận nào có thể đưa ra một hệ thống hiện đại không có lớp tương thích POSIX và tín hiệu hỗ trợ ().

Trích dẫn từ tài liệu GLIBC :

Bạn có thể sử dụng cả hai chức năng tín hiệu và tín hiệu trong một chương trình, nhưng bạn phải cẩn thận vì chúng có thể tương tác theo những cách hơi lạ.

Hàm sigaction chỉ định nhiều thông tin hơn hàm tín hiệu, vì vậy giá trị trả về từ tín hiệu không thể biểu thị đầy đủ các khả năng sigaction. Do đó, nếu bạn sử dụng tín hiệu để lưu và sau đó thiết lập lại một hành động, nó có thể không thể thiết lập lại đúng cách một trình xử lý được thiết lập với sigaction.

Để tránh gặp sự cố do đó, luôn luôn sử dụng sigaction để lưu và khôi phục trình xử lý nếu chương trình của bạn sử dụng sigaction. Vì sigaction là tổng quát hơn, nó có thể lưu và thiết lập lại bất kỳ hành động nào một cách hợp lý, bất kể nó được thiết lập ban đầu bằng tín hiệu hay sigaction.

Trên một số hệ thống nếu bạn thiết lập một hành động với tín hiệu và sau đó kiểm tra nó với sự điều hướng, địa chỉ xử lý mà bạn nhận được có thể không giống với những gì bạn đã chỉ định với tín hiệu. Nó thậm chí có thể không phù hợp để sử dụng làm đối số hành động với tín hiệu. Nhưng bạn có thể dựa vào việc sử dụng nó như là một đối số để sigaction. Vấn đề này không bao giờ xảy ra trên hệ thống GNU.

Vì vậy, tốt hơn hết là bạn nên sử dụng một hoặc các cơ chế khác trong một chương trình.

Lưu ý về tính di động: Chức năng tín hiệu cơ bản là một tính năng của ISO C, trong khi sigaction là một phần của tiêu chuẩn POSIX.1. Nếu bạn lo ngại về tính di động đối với các hệ thống không phải POSIX, thì bạn nên sử dụng chức năng tín hiệu thay thế.

Bản quyền (C) 1996-2008 Free Software Foundation, Inc.

Quyền được cấp để sao chép, phân phối và / hoặc sửa đổi tài liệu này theo các điều khoản của Giấy phép Tài liệu Tự do GNU, Phiên bản 1.2 hoặc bất kỳ phiên bản nào mới hơn do Tổ chức Phần mềm Tự do xuất bản; không có phần bất biến, không có nội dung trang bìa và không có nội dung trang bìa. Một bản sao của giấy phép được bao gồm trong phần có tên "Giấy phép tài liệu miễn phí GNU".


0

Từ tín hiệu trang man (7)

Tín hiệu theo quy trình có thể được gửi đến bất kỳ một trong các luồng hiện không có tín hiệu bị chặn. Nếu có nhiều hơn một trong các luồng có tín hiệu được bỏ chặn, thì nhân chọn một luồng tùy ý để phát tín hiệu.

Và tôi muốn nói rằng "vấn đề" này tồn tại cho tín hiệu (2)sigaction (2) . Vì vậy, hãy cẩn thận với các tín hiệu và pthreads.

... và tín hiệu (2) dường như gọi sigaction (2) bên dưới Linux với glibc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.