Mã thoát mặc định khi quá trình kết thúc?


54

Khi một tiến trình bị giết với tín hiệu có thể xử lý như SIGINThoặc SIGTERMnhưng nó không xử lý tín hiệu đó, mã thoát của quy trình sẽ là gì?

Còn đối với các tín hiệu không thể xử lý như thế SIGKILLnào?

Từ những gì tôi có thể nói, việc giết một quá trình có SIGINTkhả năng dẫn đến mã thoát 130, nhưng điều đó có thay đổi theo cách thực hiện kernel hay shell không?

$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130

Tôi không chắc mình sẽ kiểm tra các tín hiệu khác như thế nào ...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem

1
các killall myScripttác phẩm của bạn , do đó, sự trở lại của killall (chứ không phải của tập lệnh!) là 0. Bạn có thể đặt kill -x $$[x là số tín hiệu và $$ thường được mở rộng bởi trình bao tới PID của tập lệnh đó (hoạt động trong sh, bash, ...)] Bên trong tập lệnh và sau đó kiểm tra lõi thoát của nó là gì.
Olivier Dulac


nhận xét về câu hỏi bán phần: Đừng đặt myScript trong nền. (bỏ qua &). Gửi tín hiệu từ một quá trình shell khác (trong một thiết bị đầu cuối khác), sau đó bạn có thể sử dụng $?sau khi myScript kết thúc.
MattBianco

Câu trả lời:


61

Các quy trình có thể gọi cuộc gọi _exit()hệ thống (trên Linux, xem thêm exit_group()) với một đối số nguyên để báo cáo mã thoát cho cha mẹ của chúng. Mặc dù đó là một số nguyên, nhưng chỉ có 8 bit có ý nghĩa nhỏ nhất có sẵn cho cha mẹ (ngoại trừ đó là khi sử dụng waitid()hoặc xử lý trên SIGCHLD trong cha mẹ để lấy mã đó , mặc dù không có trên Linux).

Phụ huynh thường sẽ làm một wait()hoặc waitpid()để lấy trạng thái của con họ như một số nguyên (mặc dù waitid()với ngữ nghĩa hơi khác nhau cũng có thể được sử dụng).

Trên Linux và hầu hết các Unice, nếu quá trình kết thúc bình thường, các bit 8 đến 15 của số trạng thái đó sẽ chứa mã thoát như được chuyển đến exit(). Nếu không, thì 7 bit có ý nghĩa nhỏ nhất (0 đến 6) sẽ chứa số tín hiệu và bit 7 sẽ được đặt nếu một lõi bị đổ.

perl's $?ví dụ chứa con số đó là do waitpid():

$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

Các shell giống như Bourne cũng làm cho trạng thái thoát của lệnh chạy cuối cùng trong $?biến của riêng chúng . Tuy nhiên, nó không chứa trực tiếp số được trả về waitpid(), mà là một phép biến đổi trên nó và nó khác nhau giữa các shell.

Điều phổ biến giữa tất cả các shell là $?chứa 8 bit thấp nhất của mã thoát (số được truyền tới exit()) nếu quá trình kết thúc bình thường.

Nơi nó khác nhau là khi quá trình được kết thúc bởi một tín hiệu. Trong mọi trường hợp và theo yêu cầu của POSIX, số này sẽ lớn hơn 128. POSIX không chỉ định giá trị có thể là bao nhiêu. Trong thực tế, trong tất cả các vỏ giống như Bourne mà tôi biết, 7 bit thấp nhất $?sẽ chứa số tín hiệu. Nhưng, đâu nlà số tín hiệu,

  • trong tro, zsh, pdksh, bash, vỏ Bourne, $?128 + n. Điều đó có nghĩa là trong những vỏ, nếu bạn nhận được một $?số 129, bạn không biết liệu đó là vì quá trình này đã thoát với exit(129)hoặc cho dù đó đã bị giết bởi các tín hiệu 1( HUPtrên hầu hết các hệ thống). Nhưng lý do là các shell đó, khi chúng tự thoát, mặc định trả về trạng thái thoát của lệnh đã thoát cuối cùng. Bằng cách đảm bảo $?không bao giờ lớn hơn 255, điều đó cho phép có trạng thái thoát nhất quán:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    
  • ksh93, $?256 + n. Điều đó có nghĩa là từ một giá trị của $?bạn có thể phân biệt giữa một quá trình bị giết và không bị giết. Các phiên bản mới hơn ksh, khi thoát, nếu $?lớn hơn 255, sẽ tự giết mình với cùng một tín hiệu để có thể báo cáo trạng thái thoát tương tự cho cha mẹ của nó. Mặc dù nghe có vẻ là một ý tưởng hay, điều đó có nghĩa là kshsẽ tạo ra một bãi chứa lõi bổ sung (có khả năng ghi đè lên cái khác) nếu quá trình bị giết bởi tín hiệu tạo lõi:

    $ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.
    

    Nơi bạn thậm chí có thể nói có một lỗi là ksh93nó tự giết chết ngay cả khi $?xuất phát từ return 257một chức năng:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
    
  • yash. yashđưa ra một sự thỏa hiệp. Nó trở lại 256 + 128 + n. Điều đó có nghĩa là chúng ta cũng có thể phân biệt giữa một quá trình bị giết và một quá trình kết thúc đúng. Và khi thoát ra, nó sẽ báo cáo 128 + nmà không phải tự sát và các tác dụng phụ có thể có.

    $ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    8f  # that's from a exit(143), yash was not killed
    

Để nhận được tín hiệu từ giá trị của $?, cách di động là sử dụng kill -l:

$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM

(đối với tính di động, bạn không bao giờ nên sử dụng số tín hiệu, chỉ tên tín hiệu)

Trên các mặt trận không Bourne:

  • csh/ tcshfishgiống như trình bao Bourne ngoại trừ trạng thái ở trong $statusthay vì $?(lưu ý zshcũng đặt $statusđể tương thích với csh(ngoài $?)).
  • rc: trạng thái thoát $statuscũng vậy, nhưng khi bị giết bởi tín hiệu, biến đó chứa tên của tín hiệu (giống sigtermhoặc sigill+corenếu lõi được tạo) thay vì một số, đó là một bằng chứng khác về thiết kế tốt của lớp vỏ đó .
  • es. trạng thái thoát không phải là một biến. Nếu bạn quan tâm đến nó, bạn chạy lệnh như:

    status = <={cmd}
    

    sẽ trả về một số hoặc sigtermhoặc sigsegv+corethích rc.

Có lẽ cho đầy đủ, chúng ta nên đề cập đến zsh's $pipestatusbash' s $PIPESTATUSmảng có chứa các trạng thái thoát của các thành phần của đường ống ngoái.

Và cũng để hoàn thiện, khi nói đến các hàm shell và các tệp có nguồn gốc, theo mặc định các hàm sẽ trả về trạng thái thoát của lệnh chạy cuối cùng, nhưng cũng có thể đặt trạng thái trả về rõ ràng với returnnội dung. Và chúng tôi thấy một số khác biệt ở đây:

  • bashmksh(kể từ R41, một hồi quy ^ Wchange rõ ràng được giới thiệu có chủ ý ) sẽ cắt số (dương hoặc âm) thành 8 bit. Vì vậy, ví dụ return 1234sẽ được đặt $?thành 210, return -- -1sẽ được đặt $?thành 255.
  • zshpdksh(và các dẫn xuất khác mksh) cho phép mọi số nguyên thập phân 32 bit đã ký (-2 31 đến 2 31 -1) (và cắt số đó thành 32 bit ).
  • ashyashcho phép mọi số nguyên dương từ 0 đến 2 31 -1 và trả về lỗi cho bất kỳ số nào trong số đó.
  • ksh93cho return 0đến return 320thiết lập $?như là, nhưng đối với bất cứ điều gì khác, cắt xén đến 8 bit. Coi chừng như đã đề cập rằng việc trả về một số trong khoảng từ 256 đến 320 có thể gây ra kshtự tử khi thoát.
  • rcescho phép trả lại bất cứ thứ gì kể cả danh sách.

Cũng lưu ý rằng một số vỏ cũng sử dụng các giá trị đặc biệt của $?/ $statusbáo cáo một số điều kiện lỗi đó không phải là trạng thái thoát của một quá trình, như 127hoặc 126cho command not found hoặc không thực thi (hoặc lỗi cú pháp trong một tập tin nguồn) ...


1
an exit code to their parentto get the *status* of their child. bạn đã thêm nhấn mạnh vào "trạng thái". Là exit code*status*giống nhau? Trường hợp có, nguồn gốc của việc có hai tên là gì? Trường hợp không giống nhau, bạn có thể đưa ra định nghĩa / tham chiếu về trạng thái?
n611x007

2
Có 3 số ở đây. Các mã thoát : số truyền cho exit(). Các trạng thái thoát : số thu được bằng cách waitpid()bao gồm các mã xuất cảnh, số tín hiệu và liệu có một lõi đổ. Và số mà một số shell tạo sẵn trong một trong các biến đặc biệt của chúng ( $?, $status) là một biến đổi của trạng thái thoát theo cách chứa mã thoát trong trường hợp có một kết thúc bình thường, nhưng cũng mang thông tin tín hiệu nếu quá trình đã bị giết (cái đó thường được gọi là trạng thái thoát ). Đó là tất cả giải thích trong câu trả lời của tôi.
Stéphane Chazelas

1
Tôi hiểu rôi, cảm ơn bạn! Tôi chắc chắn đánh giá cao ghi chú rõ ràng này của sự phân biệt ở đây. Những biểu thức liên quan đến lối ra được sử dụng thay thế cho nhau tại một số nơi đáng để thực hiện. Liệu biến thể shell thậm chí có một tên (chung)? Vì vậy, tôi khuyên bạn nên xóa nó một cách rõ ràng trước khi đi vào chi tiết về vỏ. Tôi khuyên bạn nên chèn lời giải thích (từ nhận xét của bạn) sau đoạn đầu tiên hoặc đoạn thứ hai của bạn.
n611x007

1
Bạn có thể chỉ ra trích dẫn POSIX nói về 7 bit đầu tiên là tín hiệu không? Tất cả những gì tôi có thể tìm thấy là > 128một phần: "Trạng thái thoát của lệnh bị chấm dứt vì nhận được tín hiệu sẽ được báo cáo là lớn hơn 128." pubs.opengroup.org/onlinepub/9699919799/utilities/ory
Ciro Santilli

1
@cuonglm, tôi không nghĩ nó có sẵn công khai ở bất kỳ nơi nào khác trên HTTP, bạn vẫn có thể lấy nó từ gmane qua NNTP. Tìm id tin nhắn efe764d811849b34eef24bfb14106f61@austingroupbugs.net(từ 2015-05-06) hoặcXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas

23

Khi một tiến trình thoát, nó trả về một giá trị nguyên cho hệ điều hành. Trên hầu hết các biến thể unix, giá trị này được lấy modulo 256: mọi thứ trừ các bit thứ tự thấp đều bị bỏ qua. Trạng thái của một tiến trình con được trả về cho cha của nó thông qua số nguyên 16 bit trong đó

  • các bit 0 nhiệt6 (7 bit thứ tự thấp) là số tín hiệu được sử dụng để giết tiến trình hoặc 0 nếu quá trình thoát bình thường;
  • bit 7 được thiết lập nếu quá trình bị hủy bởi tín hiệu và lõi bị đổ;
  • bit 8 Ném15 là mã thoát của quy trình nếu quá trình thoát bình thường hoặc 0 nếu quá trình bị hủy bởi tín hiệu.

Trạng thái được trả về bởi waitcuộc gọi hệ thống hoặc một trong những anh chị em của nó. POSIX không chỉ định mã hóa chính xác trạng thái thoát và số tín hiệu; nó chỉ cung cấp

  • một cách để biết liệu trạng thái thoát tương ứng với tín hiệu hoặc với lối thoát bình thường;
  • một cách để truy cập mã thoát, nếu quá trình thoát bình thường;
  • một cách để truy cập số tín hiệu, nếu quá trình bị giết bởi tín hiệu.

Nói một cách chính xác, không có thoát khi một quá trình bị giết bởi tín hiệu: thay vào đó là trạng thái thoát .

Trong tập lệnh shell, trạng thái thoát của lệnh được báo cáo thông qua biến đặc biệt $?. Biến này mã hóa trạng thái thoát theo cách mơ hồ:

  • Nếu quá trình thoát bình thường thì $?trạng thái thoát của nó.
  • Nếu quá trình bị giết bởi tín hiệu thì $?là 128 cộng với số tín hiệu trên hầu hết các hệ thống. POSIX chỉ bắt buộc $?lớn hơn 128 trong trường hợp này; ksh93 thêm 256 thay vì 128. Tôi chưa bao giờ thấy một biến thể unix nào làm gì khác ngoài việc thêm một hằng số vào số tín hiệu.

Do đó, trong tập lệnh shell, bạn không thể đưa ra kết luận liệu lệnh có bị giết bởi tín hiệu hoặc thoát với mã trạng thái lớn hơn 128 hay không, ngoại trừ ksh93. Rất hiếm khi các chương trình thoát với mã trạng thái lớn hơn 128, một phần vì các lập trình viên tránh nó do $?sự mơ hồ.

SIGINT là tín hiệu 2 trên hầu hết các biến thể unix, do đó $?là 128 + 2 = 130 cho một quá trình đã bị SIGINT giết. Bạn sẽ thấy 129 cho SIGHUP, 137 cho SIGKILL, v.v.


Nhiều từ tốt hơn và nhiều hơn so với của tôi ngay cả khi nó nói về bản chất những điều tương tự. Bạn có thể muốn làm rõ rằng $?chỉ dành cho vỏ giống như Bourne. Xem thêm yashcho một hành vi khác (nhưng vẫn là POSIX). Cũng theo POSIX + XSI (Unix), a kill -2 "$pid"sẽ gửi SIGINT cho quá trình, nhưng số tín hiệu thực tế có thể không phải là 2, vậy $? sẽ không nhất thiết là 128 + 2 (hoặc 256 + 2 hoặc 384 + 2), mặc dù kill -l "$?"sẽ trả về INT, đó là lý do tại sao tôi khuyên bạn không nên tham khảo các số.
Stéphane Chazelas

8

Điều đó phụ thuộc vào vỏ của bạn. Từ bash(1)trang man, phần SHELL GRAMMAR , tiểu mục Lệnh đơn giản :

Giá trị trả về của một lệnh đơn giản là [...] 128+ n nếu lệnh bị chấm dứt bởi tín hiệu n .

SIGINTtrên hệ thống của bạn là tín hiệu số 2, giá trị trả về là 130 khi nó được chạy dưới Bash.


1
Làm thế nào trên thế giới bạn tìm thấy điều này, hoặc thậm chí biết nơi để tìm? Tôi cúi đầu trước thiên tài của bạn.
Cory Klein

1
@CoryKlein: Kinh nghiệm, chủ yếu. Ồ, và bạn cũng có thể muốn signal(7)trang người đàn ông.
Ignacio Vazquez-Abrams

những thứ mát mẻ; Bạn có biết nếu tôi có bao gồm các tệp trong C với các hằng số đó không? +1
Rui F Ribeiro

@CoryKlein Tại sao bạn không chọn đây là câu trả lời đúng?
Rui F Ribeiro

3

Nó dường như là nơi thích hợp để đề cập rằng SVr4 đã giới thiệu Waitid () vào năm 1989, nhưng cho đến nay không có chương trình quan trọng nào sử dụng nó. Waitid () cho phép truy xuất toàn bộ 32 bit từ mã exit ().

Khoảng 2 tháng trước, tôi đã viết lại phần kiểm soát chờ / công việc của Bourne Shell để sử dụng Waitid () thay vì Waitpid (). Điều này đã được thực hiện để loại bỏ giới hạn che dấu mã thoát với 0xFF.

Giao diện Waitid () sạch hơn nhiều so với các triển khai Wait () trước đó ngoại trừ lệnh gọi cwait () từ UNOS từ năm 1980.

Bạn có thể quan tâm để đọc trang người đàn ông tại:

http://schillix.sourceforge.net/man/man1/bosh.1.html

và kiểm tra phần "Thay thế thông số" hiện đang nhìn chằm chằm vào trang 8.

Các biến mới .sh. * Đã được giới thiệu cho giao diện Waitid (). Giao diện này không còn có ý nghĩa mơ hồ cho các số được biết đến với $? và làm cho giao tiếp dễ dàng hơn nhiều.

Lưu ý rằng bạn cần phải có Waitid () tuân thủ POSIX để có thể sử dụng tính năng này, vì vậy Mac OS X và Linux hiện không cung cấp tính năng này, nhưng Waitid () được mô phỏng trong lệnh gọi Waitpid (), v.v. nền tảng không phải POSIX, bạn vẫn sẽ chỉ nhận được 8 bit từ mã thoát.

Tóm lại: .sh.status là mã thoát số, .sh.code là lý do thoát số.

Để có tính di động tốt hơn, có: .sh.codename cho phiên bản văn bản của lý do thoát, ví dụ: "DUMPED" và .sh.termsig, tên riêng cho tín hiệu kết thúc quá trình.

Để sử dụng tốt hơn, có hai giá trị .sh.codename không liên quan đến thoát: "NOEXEC" và "NOTFOUND" được sử dụng khi không thể khởi chạy chương trình.

FreeBSD đã sửa lỗi kerlnel Waitid () của họ trong vòng 20 giờ sau báo cáo của tôi, Linux vẫn chưa bắt đầu với bản sửa lỗi của họ. Tôi hy vọng rằng 26 năm sau khi giới thiệu tính năng này có trong POSIX, tất cả các hệ điều hành sẽ sớm hỗ trợ nó.


Một câu trả lời liên quan là unix.stackexchange.com/a/453432/5132 .
JdeBP
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.