Friday, October 26, 2012

UNIX Programming

signal

ในตอนนี้เราจะพูดถึงการรับมือกับ signal ไม่ว่าจะมาจากภายนอก เช่น interrupt หรือจากในภายในโปรแกรมเอง และเนื่องจากว่า ในภายในตัวโปรแกรมเองนั้น ไม่มีอะไรน่าสนใจมากไปกว่า การอ้างถึงหน่วยความจำ ที่อ้างไม่ถึง หรือ อยู่นอกขอบข่ายที่จะอ้างอิงถึงได้ กับ การใช้คำสั่งที่แปลกประหลาด เราจึงมุ่งความสนใจไปที่ signal จากภายนอก เช่น

  • interrupt เมื่อมีการกดปุ่ม Delete
  • quit เมื่อกดปุ่ม FS
  • hangup เมื่อเกิดการวางสายโทรศัพท์ลง
  • terminate เมื่อได้รับคำสั่ง kill
เมื่อเกิด เหตุการ, event, เหล่านี้ขึ้น signal จะได้รับการส่ง หรือ ประกาศไปยังทุก process ที่ทำงานบน terminal นั้นๆ ซึ่งหากไม่มีการตระเตรียมอื่นใดเป็นพิเศษแล้ว signal มักจะหยุดการทำงานของ process และในกรณีของ quit จะได้แฟ้มเสมือน ในหน่วยความจำ ในขณะนั้นมาให้ เพื่อตรวจสอบดู หรือ debug ในภายหลัง
routine ที่เปลี่ยนแปลงการกระทำโดยปริยายเมื่อได้รับ signal ก็คือ signal() ซึ่งใช้ชื่อเดียวกันเอง รูทีนนี้ หรือ function นี้ต้องการอาร์กิวเม้นต์ ๒ ตัว คือ
  • ตัว signal
  • บอกว่าจะทำอะไร อย่างไรกับ signal นี้
ค่าแรกนั้น เป็นเพียงตัวเลขที่นิยามเอาไว้เท่านั้นเอง ไม่ได้มีความหมายอะไรมากนัก แต่ค่าที่สองนั้น เป็น address ซึ่งชี้ไปที่ function หรือ คำสั่งประหลาดยากแก่การเข้าใจ แต่โดยทั่วไป ก็ว่า ให้ละความสนใจ signal นี้ หรือ ให้ปฏิบัติตามข้อตกลงโดยปริยายของ signal นี้ที่กำหนดเอาไว้แล้ว อย่างไรก็ดี แฟ้ม signal.h ต้องระบุเอาไว้ด้วย เพื่อการเรียกใช้งาน signal ต่างๆ

#include <signal.h>

signal(SIGINT, SIG_IGN);

คำสั่งข้างบนนั้นเป็นเหตุให้ การ interrupt นั้นได้รับการละเลยไปเสีย กล่าวคือ ไม่ต้องไปสนใจอะไร นั่นเอง ขณะที่

signal(SIGINT, SIG_DFL);

นั้น จะนำการปฏิบัติการ ตามค่าปริยายกลับคืนมา ซึ่งก็คือ จบงาน

ในทุกกรณี signal() จะคืนค่าเดิมของ signal มาให้
ที่ยกมาให้ทราบเป็นตัวอย่างนี้ อาร์กิวเม้นต์ที่สองของ signal เป็นค่าคงที่ แต่มันสามารถ เป็นชื่อของฟังก์ชั่นใดใด ก็ได้ แต่ต้องระบุเอาไว้ก่อน ให้เป็นที่เรียบร้อย ในกรณีนี้ ฟังก์ชั่นชื่อดังกล่าวนี้ จะถูกเรียกมาใช้งาน เมื่อเกิด signal ขึ้นมา ซึ่งการใช้งาน ก็จะเป็นไปในกรณีที่ โปรแกรมต้องการลบแฟ้มขยะออก ก่อนที่จะจบงาน เสียเป็นส่วนมาก ตัวอย่างของ code ประเภทนี้ ก็อาจจะเป็น

#include <signal.h>

main()
{
     int onintr();

     if (signal(SIGINT, SIG_IGN) != SIG_IGN);
         signal(SIGINT, onintr);

/* process ...
 */
      exit(0);
}

onintr()
{
     unlink(tempfile);
     exit(1);
}

ความวุ่นวาย มันก็อยู่ที่ประโยค if () นั่นแหละ ตรงที่ต้องเรียก signal() สองหน

เรื่องก็มีอยู่ว่า ในกรณีของ interrupt นั้น signal จะส่งไปยังทุก process ที่ทำงานบน terminal นั้นๆ ที่นี้ ขอให้ดูบางโปรเซส ที่เขาทำงาน  โดยไม่ต้องการการรบกวนใดใด จากแป้นพิมพ์เลย งานแบบนี้ จะหยุดก็เมื่องานเสร็จ หรือเกิดอุบัติเหตุบางอย่างเท่านั้น ดังนั้น จึงสามารถสั่งงานประเภทนี้ ให้วิ่งในลักษณะของ background ได้ ก็คำสั่ง จาก shell ที่มีตัว & ปิดท้ายนั่นเอง , ให้สังเกตุตัวคำสั่งด้วย
signal(SIGINT, SIG_IGN)
ซึ่งระบุเอาไว้ว่า ถ้ามี interrupt เข้ามาก็ไม่ต้องไปสนใจน่ะ

ทีนี้ ถ้าหากว่า ตัวอย่างโปรแกรมข้างบนนั้น ทำงานเลย โดยไม่ต้องไปตรวจดูว่า signal เดิม ที่ตั้งเอาไว้นั้น คืออะไร ก็ไยมิใช่เท่ากับว่า ความพยายามที่จะทำงานให้เสร็จ นั้น ล้มเหลว จริงไหมครับ

ดังนั้น เราจึงดูว่าค่า signal ของ signal() ที่กำหนดไว้เดิมนั้น ใช่ ignore (SIG_IGN) หรือไม่ แล้วค่านี้จะได้มาก็โดยการเรียกด้วย signal() เท่านั้น นี่คือนัยยะของบรรทัด if() นั้น

บรรทัดถัดมานั่นจึงเป็นการส่ง signal ที่แท้จริง กล่าวคือ ให้ไปทำงานใน function onintr() น่ะ หากมีสัญญาน interrupt มาแล้ว ก็เท่านี้แหละ

นอกจากนี้แล้ว นิยาม หรือค่าที่กำหนดใน SIG_DLF, SIG_IGN เองก็ น่าเกลียด น่ากลัวเอามากมากพอ จนไม่อยากให้เรียกตรงๆ แต่กำหนดผ่าน #define เอา ก็ลองดูซิว่า จะเข้าใจหรือเปล่า

#define     SIG_DFL  (int (*)())0
#define     SIG_IGN  (int (*)())1

ยังมีสาระที่ขออนุญาตข้ามไปก่อน โดยเฉพาะในเรื่องของการ fork() นั้น จะไม่ขออธิบายละ แต่ยกเอา code สั้นๆมาให้ดูชมกัน แล้วพิจารณาเอาเองว่า ทำไม และขอยุติลงไว้แต่เพียงเท่านี้

if (fork() == 0)
      execl( .. );
signal(SIGINT, SIG_IGN);
wait(&status);
signal(SIGINT, onintr);

No comments:


View My Stats