• בלוג
  • סוד התוכניות הנעלמות

סוד התוכניות הנעלמות

01/12/2020

בפעם הראשונה ש mutt סגרה את עצמה חשבתי שזה קורה, שזה באג, שככה זה תוכניות - עובדות עובדות ואז נעלמות. אבל כשזה המשיך לקרות שוב ושוב ועם תוכניות נוספות כבר היה ברור שאי אפשר לתת למחשב להשתולל ככה. מה קרה אתם שואלים? בואו נצא למסע לארץ הסיגנלים בעקבות התוכניות הנעלמות.

1. דמו? כן ברור

יש לכם לינוקס? יופי. יש מסוף? מעולה. יש פייתון? גדול. עכשיו כתבו את הסקריפט הבא בקובץ demo.py והריצו מהמסוף החביב עליכם:

while True:
    pass

אני מריץ עם:

$ python demo.py

וכמובן שהסקריפט לא עושה הרבה רק נכנס ללולאה אינסופית. עכשיו לחצו Ctrl+C (מחזיקים את ה Ctrl ואז לוחצים c). נסגר? נסגר.

בואו ננסה את זה שוב הפעם עם כפתור אחר. אותו סקריפט ואותה הרצה:

$ python demo.py

ועכשיו לחצו על Ctrl+\ (מחזיקים את ה ctrl ואז לוחצים על Backslash). נסגר? נסגר.

למה הסקריפט נסגר כל פעם? ומה ההבדל בין שני צירופי המקשים? בואו נדבר רגע על סיגנלים.

2. סיגנלים ביוניקס

סיגנל הוא דרך להעביר הודעה בין תוכניות. הפקודה kill מקבלת מזהה תהליך ומזהה סיגנל ושולחת את הסיגנל לתהליך, מערכת ההפעלה לפעמים תחליט לשלוח סיגנלים לתהליכים מסוימים ואנחנו יכולים דרך המקלדת לשלוח כמה סיגנלים לפי מה שמוגדר אצלנו במסוף.

אני חוזר לפייתון ובשביל הדמו הבא נצטרך שני חלונות מסוף (או אחד מפוצל). כתבו את התוכן הבא בקובץ demo.py והריצו:

import os
print(os.getpid())

while True:
    pass

בהרצה הפעם נקבל הדפסה של מספר לפני הכניסה ללולאה. המספר נקרא מזהה התהליך הנוכחי ובעזרתו נוכל לשלוח סיגנלים לאותו תהליך. המספר שהודפס אצלי הוא 545878 אז אני נכנס לחלון חדש וכותב:

$ kill -1 545878

ובחלון המקורי של תוכנית הפייתון התוכנית תצא ותופיע ההודעה:

[1]    545878 hangup     python demo.py

אתם יכולים לנסות את זה עם מספרים שונים אחרי ה kill, כל מספר מייצג סיגנל אחר. אפשר גם לראות את כל הסיגנלים בעזרת הפקודה kill -l ואצלי התוצאה נראית כך:

$ kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

מה עושים עם הסיגנלים? מסתבר שתוכניות יכולות לבחור לטפל בהם בכל מיני צורות. בפייתון המודול signal מאפשר לנו לתפוס סיגנלים לפי סוג הסיגנל. עדכנו את קוד הדמו לגירסה הבאה:

import os, signal

print(os.getpid())

def hahaha(sig, frame):
    print("Hahaha")

signal.signal(signal.SIGINT, hahaha)

while True:
    pass

נריץ ונלחץ על Ctrl+C הפעם המחשב כבר לא מוותר וכך נראה הפלט אצלי:

$ python demo.py
547128
^CHahaha
^CHahaha
^CHahaha

קוד התוכנית מטפל בסיגנל שנקרא SIGINT באמצעות הדפסת הטקסט hahaha. סיגנל זה הוא בדיוק מה שמתקבל כשאנחנו לוחצים על Ctrl+C ולכן התוכנית לא יצאה כשלחצנו על צירוף מקשים זה. עדיין אנחנו יכולים לסיים את התוכנית עם Ctrl+\ ששולח את הסיגנל SIGQUIT. אני אשאיר לכם את הכיף לטפל גם ב SIGQUIT ואז לנסות למצוא איך לצאת מהתוכנית.

3. אז מה קרה לתוכניות המסוף שלי?

בעבודה עם אימקס הצירוף Ctrl+\ הוא זה שמחליף שפה ובשלב מסוים התרגלתי ללחוץ על צירוף מקשים זה כדי (לנסות) להחליף שפה גם מחוץ לאימקס. התוצאה כמו שאני מקווה שכבר הבנתם היא שליחת סיגנל SIGQUIT ליישום שבמסוף מה שגורם לסגירת התוכנית.

הפיתרון במצבים כאלה הוא ממש פשוט ולא דורש להתרגל לצירופי מקשים חדשים. הפקודה stty של המסוף קובעת איזה כפתור ישלח את הסיגנלים של השונים של המסוף (ועוד קיצורי מקשים מעניינים). אתם יכולים לראות את כל הקיצורים שמוגדרים אצלכם עם:

$ stty -a
speed 38400 baud; rows 23; columns 94; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany
-imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
-flusho -extproc

עכשיו כל מה שצריך בשביל לבטל קיצור מסוים הוא לבחור עבורו את צירוף המקשים undef - במקרה של ה Ctrl+\ שלנו אקליד במסוף או בסקריפט:

$ stty quit undef

ועכשיו אפשר ללחוץ כמה שרוצים על Ctrl+\ בלי ששום תוכנית תקבל סיגנל שלא התכוונתי לשלוח.