2013-05-30 55 views
10

Tôi đã có mã tương tự như sau, sử dụng readline:Readline: Nhận một nhắc nhở mới trên SIGINT

#include <errno.h> 
#include <error.h> 
#include <getopt.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <signal.h> 
#include <readline/readline.h> 
#include <readline/history.h> 

void handle_signals(int signo) { 
    if (signo == SIGINT) { 
    printf("You pressed Ctrl+C\n"); 
    } 
} 

int main (int argc, char **argv) 
{ 
    //printf("path is: %s\n", path_string); 
    char * input; 
    char * shell_prompt = "i-shell> "; 
    if (signal(SIGINT, handle_signals) == SIG_ERR) { 
    printf("failed to register interrupts with kernel\n"); 
    } 

    //set up custom completer and associated data strucutres 
    setup_readline(); 

    while (1) 
    { 
    input = readline(shell_prompt); 
    if (!input) 
     break; 
    add_history(input); 

    //do something with the code 
    execute_command(input); 

    } 
    return 0; 
} 

Tôi đã có nó thiết lập để ngăn chặn SIGINT (tức là người dùng nhấn Ctrl+C), vì vậy tôi có thể nói rằng trình xử lý tín hiệu handle_signals() đang hoạt động. Tuy nhiên, khi điều khiển trở về readline(), nó sử dụng cùng một dòng văn bản mà nó đang sử dụng trước khi nhập. Những gì tôi muốn xảy ra là cho readline để "hủy bỏ" dòng hiện tại của văn bản và cho tôi một dòng mới, giống như vỏ BASH. Một cái gì đó như vậy:

i-shell> bad_command^C 
i-shell> _ 

Bất kỳ cơ hội nào để làm việc này? Một cái gì đó trên một danh sách gửi thư tôi đọc được đề cập bằng cách sử dụng longjmp(2), nhưng điều đó thực sự không có vẻ giống như một ý tưởng tốt.

Trả lời

5

Bạn đúng trong dòng suy nghĩ của mình để sử dụng longjmp. Nhưng vì longjmp sẽ ở trong một trình xử lý tín hiệu, bạn cần sử dụng sigsetjmp/siglongjmp.

Như một ví dụ nhanh sử dụng mã của bạn như một cơ sở:

#include <setjmp.h> 
#include <errno.h> 
#include <error.h> 
#include <getopt.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <signal.h> 
#include <readline/readline.h> 
#include <readline/history.h> 

sigjmp_buf ctrlc_buf; 

void handle_signals(int signo) { 
    if (signo == SIGINT) { 
    printf("You pressed Ctrl+C\n"); 
    siglongjmp(ctrlc_buf, 1); 
    } 
} 

int main (int argc, char **argv) 
{ 
    //printf("path is: %s\n", path_string); 
    char * input; 
    char * shell_prompt = "i-shell> "; 
    if (signal(SIGINT, handle_signals) == SIG_ERR) { 
    printf("failed to register interrupts with kernel\n"); 
    } 

    //set up custom completer and associated data strucutres 
    setup_readline(); 

    while (sigsetjmp(ctrlc_buf, 1) != 0); 

    while (1) 
    { 
    input = readline(shell_prompt); 
    if (!input) 
     break; 
    add_history(input); 

    //do something with the code 
    execute_command(input); 

    } 
    return 0; 
} 

siglongjmp trả về một giá trị khác 0 (trong trường hợp này 1) để sigsetjmp nên vòng lặp while cuộc gọi sigsetjmp một lần nữa (một giá trị trả về thành công của sigsetjmp là 0) và sau đó sẽ gọi readline một lần nữa.

cũng có thể hữu ích khi đặt rl_catch_signals = 1 và sau đó gọi rl_set_signals() để xử lý tín hiệu readline xóa bất kỳ biến nào cần thiết trước khi chuyển tín hiệu đến chương trình của bạn, sau đó bạn sẽ quay lại đọc lần nữa.

+1

Bạn không thể gọi một cách an toàn 'printf' từ trình xử lý tín hiệu. – pat

4

Tôi đã nhầm lẫn lúc đầu bởi câu trả lời của jancheta, cho đến khi tôi phát hiện ra rằng mục đích của siglongjmp là để bỏ chặn tín hiệu nhận được trong mặt nạ tín hiệu, trước khi thực hiện bước nhảy. Tín hiệu bị chặn ở đầu vào của trình xử lý tín hiệu để trình xử lý không tự ngắt. Chúng tôi không muốn để lại tín hiệu bị chặn khi chúng tôi tiếp tục thực hiện bình thường và đó là lý do chúng tôi sử dụng siglongjmp thay vì longjmp. AIUI, đây chỉ là viết tắt, chúng tôi cũng có thể gọi sigprocmask theo sau là longjmp, mà dường như là những gì glibc đang làm trong siglongjmp.

Tôi nghĩ có thể không an toàn khi thực hiện một bước nhảy vì readline() gọi mallocfree. Nếu tín hiệu được nhận trong khi một số chức năng không đồng bộ-tín hiệu-không an toàn như malloc hoặc free đang sửa đổi trạng thái toàn cầu, một số tham nhũng có thể xảy ra nếu chúng tôi sau đó nhảy ra khỏi trình xử lý tín hiệu. Nhưng Readline cài đặt các trình xử lý tín hiệu của riêng nó, điều này rất cẩn thận về điều này. Họ chỉ cần đặt cờ và xuất cảnh; khi thư viện Readline được kiểm soát một lần nữa (thường là sau khi một cuộc gọi bị gián đoạn 'read()' gọi nó là RL_CHECK_SIGNALS() mà sau đó chuyển tiếp bất kỳ tín hiệu đang chờ xử lý nào đến ứng dụng khách bằng cách sử dụng kill(). Vì vậy, an toàn khi sử dụng siglongjmp() để thoát khỏi trình xử lý tín hiệu cho tín hiệu gián đoạn cuộc gọi đến readline() - tín hiệu được đảm bảo không được nhận trong một chức năng không đồng bộ-tín hiệu-không an toàn.

Trên thực tế, đó không phải là hoàn toàn đúng bởi vì có một vài cuộc gọi đến malloc()free() trong rl_set_prompt(), mà readline() cuộc gọi ngay trước khi rl_set_signals(). Tôi tự hỏi nếu lệnh gọi này nên được thay đổi. Trong mọi trường hợp, xác suất của điều kiện chủng tộc là rất mỏng.

Tôi đã xem mã nguồn Bash và dường như nó nhảy ra khỏi bộ xử lý SIGINT của nó.

Một giao diện Readline khác mà bạn có thể sử dụng là giao diện gọi lại. Điều đó được sử dụng bởi các ứng dụng như Python hoặc R cần phải lắng nghe trên nhiều bộ mô tả tập tin cùng một lúc, ví dụ để cho biết nếu một cửa sổ lô đang được thay đổi kích cỡ trong khi giao diện dòng lệnh đang hoạt động. Họ sẽ làm điều này trong một vòng lặp select().

Dưới đây là một thông điệp từ Chet Ramey đó đưa ra một số ý tưởng về những gì phải làm để có được hành vi Bash giống như khi nhận SIGINT trong giao diện callback:

https://lists.gnu.org/archive/html/bug-readline/2016-04/msg00071.html

Các thông điệp gợi ý rằng bạn làm điều gì đó như này:

rl_free_line_state(); 
    rl_cleanup_after_signal(); 
    RL_UNSETSTATE(RL_STATE_ISEARCH|RL_STATE_NSEARCH|RL_STATE_VIMOTION|RL_STATE_NUMERICARG|RL_STATE_MULTIKEY); 
    rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0; 
    printf("\n"); 

Khi SIGINT của bạn được nhận, bạn có thể thiết lập một lá cờ, và sau đó kiểm tra các cờ trong select() vòng lặp của bạn - kể từ khi cuộc gọi select() sẽ bị gián đoạn bằng tín hiệu với errno==EINTR. Nếu bạn thấy rằng cờ đã được đặt, hãy thực thi mã trên.

Ý kiến ​​của tôi là Readline sẽ chạy một phần giống như đoạn trên trong mã xử lý SIGINT của riêng nó. Hiện nay nó ít nhiều thực hiện chỉ hai dòng đầu tiên, đó là lý do tại sao các công cụ như macro tìm kiếm gia tăng và bàn phím bị hủy bởi^C, nhưng dòng này không bị xóa.

Một áp phích khác cho biết "Gọi rl_clear_signals()", vẫn khiến tôi bối rối. Tôi đã không thử nó nhưng tôi không thấy làm thế nào nó sẽ thực hiện bất cứ điều gì cho rằng (1) xử lý tín hiệu Readline chuyển tiếp tín hiệu cho bạn anyway, và (2) readline() cài đặt xử lý tín hiệu khi nhập cảnh (và xóa chúng khi nó thoát), vì vậy chúng sẽ không hoạt động bình thường bên ngoài mã Readline.

1

Tạo một bước nhảy có vẻ như bị hack và dễ bị lỗi. Việc triển khai trình bao, tôi đã thêm hỗ trợ này để không cho phép thay đổi này.

May mắn thay, readline có giải pháp thay thế rõ ràng hơn. SIGINT xử lý của tôi trông như thế này:

static void 
int_handler(int status) { 
    printf("\n"); // Move to a new line 
    rl_on_new_line(); // Regenerate the prompt on a newline 
    rl_replace_line("", 0); // Clear the previous text 
    rl_redisplay(); 
} 

này đã không có mã bổ sung khác ở đâu đó để làm việc này - không có các biến toàn cục, không có thiết lập nhảy.