Showing posts with label unix programming. Show all posts
Showing posts with label unix programming. Show all posts

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);

Monday, September 10, 2012

UNIX Programming

บทแทรก ในเรื่องของการเริ่มต้น และสิ้นุด process

จาก เอกสารอ้างอิง ทำให้ทราบว่า แฟ้มที่เรียกมาทำงานให้ ที่ถูกสร้างโดย link editor ก่อนที่จะเขียนแฟ้มลงบน hard disk นั้น เขาจะเรียก system startup routines มา กำหนดตำแหน่งเริ่มต้นของโปรแกรม ของหน่วยความจำไว้ด้วย และ system startup routine นี้จะทำงานก่อนที่เขาจะไปเรียก main function มาอีกทีหนึ่ง

ดังนั้น เมื่อ kernel สั่งให้แฟ้มนี้ทำงาน, หรืออีกนัยหนึ่ง เมื่อ kernel เริ่มต้น process, จะไม่เรียก main function มาตรงๆ แต่จะเริ่มต้นด้วย system startup routine ดังกล่าว   แล้ว kernel ก็จะส่งผ่านค่าต่างๆ ไม่ว่าจะเป็น command line หรือ ค่าตัวแปรสภาพแวดล้อมการทำงาน ไปให้ process นั้นทาง system startup routine ที่ว่ามานี้แหละ

system startup routine เมื่อได้รับสรรพสิ่งครบถ้วนแล้ว จึงไปเรียก main funtion ให้มาทำงานไป แล้วก็คอยดูว่า main function จะกลับมาเมื่อไหร่  คงเสมือน สาวน้อยคอยคนรัก อะไรทำนองนั้น และที่สุดแล้ว เขา, system startup routine, ก็จะจบ process ด้วยคำสั่งที่คล้ายๆกันนี้

exit(main(argc,argv));

หากว่าเขียนด้วยภาษา C น่ะ แต่โดยทั่วไปแล้ว system startup routine จะเขียนด้วยภาษา assembly

ทีนี้ ตัว main function เองนั้นเมื่อจบงาน เขาจบได้ ๒ แบบ คือ
  1. จบงานแบบปกติ (หรือตายดี) ซึ่งก็มีอีกว่า เลือกตายยังไง ได้ตั้ง ๓ แบบ ดังนี้ 
    • กลับจาก function main() ธรรมดา กลับเฉยๆ เที่ยวอิ่มครบหมดแล้ว เจอทางออกก็กลับ
    • เรียก function exit()
    • เรียก function _exit()
  2. จบงานไม่ปกติ (หรือตายด้วยอุบัติเหตุ ถูกฆาตรกรรม ฆ่าตัวตาย ฯลฯ)  ซึ่งมีอยู่อีก ๒ ทางเลือก คือ
    • เรียก function abort()
    • ได้รับ signal ให้เลิก
ซึ่งแต่ละรายการ จะยังไม่ขอแทรกมากไปกว่านี้
และตัว exit(main(argc,argv)) ที่ยกมาข้างบนนั้น ก็จะบ่งบอกสถานะของ main() ได้อย่างดีว่า จบยังไง จาก ๕ รายการ ของ ๒ หมวดนั้น แล้วก็ส่งสถานะนี้ให้กับ kernel ต่อไป

ค่าแสดงสถานะ (อาจจะเป็นค่าจีดีพี หรือ อินแฟล้ทฌั่น หรือ คะแนนนิยมหรือ ค่ามัธยมฐาน ของอะไรสักอย่าง) นี้ kernel ก็จะเก็บไว้ในตัวแปรสภาพแวดล้อมการทำงาน ของ ผู้ที่เรียกโปรแกรมนี้ ขึ้นมาอีกทีหนึ่ง ซึ่งเขาก็เอาไปประเมินว่า โปรแกรมนั้นทำงานได้ผล ไม่ได้ผล อย่างไรต่อไป

พอเข้าใจมั้ยครับนี่
ดีน่ะ ล้วนแล้วแต่เป็นผู้ที่แนะนิดก็กระจ่างกันหมด

ขอขอบคุณ ที่สละเวลามาอ่านครับ

Thursday, August 30, 2012

UNIX Programing

The Life Cycle of A Process (ต่อ)


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

fork จะมีคุณสมบัติโดดเด่นจำเพาะตัวอยู่อย่างหนึ่งคือ เขาจะคืนค่ากลับมา ๒ ค่า  โดยจากมุมของ ทารก ที่เกิดมาแล้ว fork จะคืนค่า ศูนย์ (0) มาให้เสมอ   แต่กับพ่อแม่ หรือ ผู้ให้กำเนิด ในอีกมิติหนึ่งจะได้รับหมายเลข PID ของทารกที่เพิ่งเกิดมา  ถ้าจะเปรียบเทียบกับคน ก็หมายเอาว่า ได้หมายเลขประชาชนของลูก นั่นแหละ

และนี่คือส่วนที่ทั้ง ๒ โปรเซส สามารถแยกระหว่างกันและกันออกได้ ในภาษา C ที่พอยกมาให้เห็นกันตอนนี้ง่ายๆ

/*
 * fork()
 */

#include <sys/types.h>
#include <unistd.h> 

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

pid_t proc_id;

proc_id = fork();

if (proc_id == 0) {
    /* อันนี้เป็นส่วนของ child โปรเซส ก็จะมีพฤติกรรมแบบทารกทั่วๆไป */
} else {
    /* ตรงนี้ เป็นส่วนของ parent */
}
}


Control of Processes

จักขอกล่าว พอได้สาระในส่วนของ child ก่อนแต่พอสังเขปดังนี้

ภายหลัง fork() โปรเซสใหม่มักจะเรียกใช้บริการของ system calls ในตระกูล exec ดังได้กล่าวมาแล้วนั้นเสมอ ซึ่งเมื่อดำเนินการเสร็จสิ้นแล้วก็ ตายไปตามกาล ส่วนของ parent นั้นหากไม่มีอะไรมาก ก็มักจะยกภาระแลทรัพย์สินทั้งมวล (หน่วยความจำ) ให้กับ ลูก ที่เกิดขึ้นแล้วตัวเองก็ ตายเมื่องานสร้างทารกจบสิ้นลงด้วยดี


เป็นเช่นนี้แล้ว ใครจะเป็นผู้ดูแล โปรเซส ของทารกที่เกิดใหม่นี้

การที่จะตอบคำถามนี้ ขอท้าวความไปในตอนแรก อีกสักหนนึง ช่วงที่คอมพิวเตอร์เปิดเครื่องมาแล้ว kernel ทำงานนั้น kernel เขาสร้าง โปรเซศ มามากมายโดยอัตโนมัติ และที่โดดเด่นที่สุดในนั้นคือ init (ไม่รู้ออกเสียงยังไง จะเรียกว่า อีนิด กะดูว่าฝรั่งจะรู้จักคำว่า ไอ้ อี ด้วยรึ งงงๆยุ)  ทุกโปรเซสที่ไม่ใช่สร้างโดย kernel จะเป็นเทือกเถาเหล่ากอของ init ทั้งนั้น

ดังนี้แล้วเมื่อโปรเซสใดโปรเซสหนึ่ง พร้อมที่จะตาย คือจบงานแล้วนั่นแหละ   โปรเซสนั้นจะเรียกรูทีน _exit เพื่อแจ้ง kernel ว่าพร้อมแล้ว และก็มี  exit code เป็นของฝาก  มาให้ พ่อ-แม่ที่สร้างโปรเซสนั้นๆ เอาไปดูผลงานด้วย (exit code = 0 ถือว่า ทำงานสำเร็จ)

ข้างฝ่าย kernel เมื่อได้รับแจ้ง
  1. ก็จะจับเก็บ exit code นี้เอาไว้ จนกว่าจะมีรายการทวงของฝาก จากพ่อ-แม่ ผ่านทาง system call ที่ชื่อ wait  
  2. หน่วยความจำที่โปรเซสที่แจ้งตายนั้นใช้อยู่ ก็จะถูกเรียกกลับคืนมาสู่ส่วนกลาง

โปรเซส นั้นจะไม่ได้รับเจียดเวลา แม้ สักส่วนเสี้ยว ของ หน่วยเวลา จาก cpu อีกต่อไป  แต่ก็ยัง คงสถานะเอาไว้ จนกว่าพ่อ-แม่ จะมาจับไป ให้รางวัล หรือ ลงโทษลงทัณฑ์ อย่าใดอย่าหนึ่งนั่นแหละ   ถึงจะถูกลบสถานะออกจากระบบโปรเซส

โปรเซส ที่อยู่ระหว่างการแจ้งตายนี้ เรียกว่า zombie
พอมาถึงตรงนี้ กะคงพอมองเห็นเค้าลางของ อนาคตของโปรเซส ที่ถามานั้นได้

ที่เล่ามาก็ดูว่า OK น่ะ ในกรณีที่พ่อ-แม่ผู้สร้าง โปรเซส นั้นไม่ตายก่อนและยังเรียกหาโปรเซสลูก ด้วย wait() ในแบบนี้จะไม่มี zombie เกิดขึ้น  ตัวอย่างของ C codes ข้างล่างนี้แสดง process control ในแนวนี้

/*
 * parents wait  

 */

#include <sys/types.h>

#include <sys/wait.h>
#include <unistd.h> 

int main(int argc, char *argv[])
{    
    pid_t proc_id; 
    int status;

   proc_id = fork();

   if (proc_id == 0) {
        /* อันนี้เป็นส่วนของ child โปรเซส ก็จะมีพฤติกรรมแบบทารกทั่วๆไป */
        execl("/bin/sh", "sh",  NULL);
    } else {
        wait(&status); 
            /* สาระของงานที่ต้องทำต่อ เช่น ตรวจดูว่า จบงานลักษณะไหน 
             * stopped, terminated, continue, หรือโดยวิธีอื่น
             */
    }
}

wait() จะดูว่า ทารกจบงานอย่างไร ถ้าจบงานด้วย stopped, continued, terminated เหล่านี้ wait() จะได้ส่งค่า process id ของ ทารกนั้นให้กับ main() เพื่อเคลียร์งานต่อไป

ถ้าพ่อ-แม่ผู้สร้าง โปรเซส ขึ้นมานั้นชิงตายหนีไปก่อน ในกรณีนี้ kernel ก็ไม่รู้จะเก็บ exit code นั้นเอาไว้ทำไม เพราะไม่มีคิวคำขอ exit code นั้นจาก wait()    ดังนี้แล้ว kernel จึงได้แต่บริจาค zombie นั้นให้กับ init ไปอย่างเดียว เท่านั้น

ตัวอย่าง C codes ในส่วนนี้ ก็คล้ายกับส่วนแรก เพียงไม่ต้อง wait(&status); เท่านั้นเอง ดังนี้

/*
 * parents die first

 */
 
#include <sys/types.h>
#include <unistd.h> 

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

     pid_t proc_id; 

    proc_id = fork();

    if (proc_id == 0) {
        /* อันนี้เป็นส่วนของ child โปรเซส ก็จะมีพฤติกรรมแบบทารกทั่วๆไป */
        execl("/bin/sh", "sh",  NULL);
     }


กะคงพอให้สาระในเรื่องของ process control กันบ้างน่ะครับ

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

ขอขอบคุณมากครับ ที่สละเวลามาอ่านข้อเขียนในชุดนี้

Monday, August 6, 2012

UNIX Programming

ใน ตอนที่ผ่านมา พูดถึง process creation ด้วย execl แล้วก็มาต่อด้วยบทแทรกคั่นรายการด้วย สวรรค์ และทิ้งท้ายด้วยข้อสงสัยว่า ที่ว่า process creation นั้น มันตรงไหนกันแน่

มาตอนนี้เรามาต่อกันด้วยเรื่องราวของ วัฏจักร หรือวงจรชีวิตของ process กันสักเล็กน้อย เพื่อเสริมความรู้เรื่องโปรเซสกันก่อน แล้ว หลังจากนั้นเราจึงค่อยมาว่ากันต่อไป ส่วนจะจบตอนเลยทีเดียว หรือไม่ นั้น กะค่อยๆมาพิจารณากันอีกที เน้าะ

เอกสารอ้างอิงสำหรับตอนนี้กะมีอยู่ ๒ เล่ม แต่จะยกมาเพียงเล่มแรก ส่วนเล่มที่ ๒ นั้นมีภาพประกอบที่เห็นว่า ทั่วไป เลยไม่เอามา แต่กะสมควรที่จะหามาอ่าน สำหรับท่านที่สนใจ
  1. Evi Nemeth, Garth Snyder, Scott Seebass, Trent R. Hein; UNX SYSTEM ADMINISTRATOE HANDBOOK; Prentice Hall — the Red Book
  2. AEleen Frisch; Essentail System Administration; O'Reilly — the White Book

The Life Cycle of A Process

โปรเซส นั้น ไม่ใช่ว่า อยู่ดีดีก็จะโผล่มาในระบบอย่างมหัศจรรย์ หรือ kernel สร้างขึ้นมาเองอย่างไม่หยุดหย่อน  โปรเซสใหม่นั้น ถูกสร้างขึ้นมาจากโปรเซสอื่น ก็คล้ายๆกับคนใหม่นั่นแหละ — มีข้อที่ควรระวังอยู่คือ คนใหม่นั้นเกิดมาจากคนอื่น ไม่ใช่จาด UNIX โปรเซสแต่อย่างใด อย่าได้เข้าใจคลาดเคลื่อนไป 

ในการสร้างโปรเซสใหม่ โปรเซสเดิม จะผลิต สำเนา ของตัวตนของตนเองขึ้นมา ด้วยวิธีการที่เรียกกันว่า system call ผ่านระบบปฏิบัติการ อีกทีหนึ่ง คำสั่งที่ว่านี้คือ fork

สำเนา ที่สร้างขึ้นมาจากต้นฉบับเดิมนั้น เหมือน ต้นฉบับเดิม แทบทุกประการ ยกเว้นสิ่งเหล่านี้ คือ

  1. โปรเซสใหม่มีหมายเลข PID ของตัวเองจำเพาะไปเลย
  2. หมายเลข PPID ของโปรเซสใหม่จะอ้างถึง หรือ ชี้ไปยังโปรเซสต้นฉบับ
  3. ข้อมูลบัญชีของโปรเซสใหม่จะถูก reset
  4. โปรเซสใหม่ จะมี file descriptor เป็นของของตัวเอง
fork จะมีคุณสมบัติโดดเด่นจำเพาะตัวอยู่อย่างหนึ่งคือ เขาจะคืนค่ากลับมา ๒ ค่า  โดยจากมุมของ ทารก ที่เกิดมาแล้ว fork จะคืนค่า ศูนย์ (0) มาให้เสมอ   แต่กับพ่อแม่ หรือ ผู้ให้กำเนิด ในอีกมิติหนึ่งจะได้รับหมายเลข PID ของทารกที่เพิ่งเกิดมา  ถ้าจะเปรียบเทียบกับคน ก็หมายเอาว่า ได้หมายเลขประชาชนของลูก นั่นแหละ

Tuesday, July 3, 2012

UNIX Programming

ไปขึ้นสวรรค์น่ะพี่น่ะ
ใน ตอนที่ผ่านมา ได้กล่าวถึงการ overlay ทับหน่วยความจำ ของ process เดิมด้วย process ใหม่ ที่สร้างขึ้น ด้วยคำสั่ง execl แล้วบอกว่า เป็นการทำงานทางเดียว ไปแล้วไปลับ ไม่มีกลับมาอีก ทำนองนี้

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

เออก็ นี่พ่อ พ่อไปขึ้นสวรรค์เสียเถอะน่ะพ่อน่ะ

อะไรประมาณนี้ พร้อมกับมอบหมายงานบางอย่างให้ไปทำบนสวรรค์ ว่ายังงั้น จะชั้นไหนก็ช่างเหอะน่า

ในเรื่องของ process ก็คงคล้ายๆกัน กล่าวคือ เมื่อเราสั่งให้วิ่ง process ใหม่ด้วยคำสั่ง execl แล้ว เกิดมีข้อผิดพลาดขึ้น เรื่องกลับไม่ได้จบ ณ ตรงนั้น มันย้อนกลับมาหาตัวผู้สั่งการเอง มามีเรื่องกันตรงนี้ ดั่งตัวโปรแกรมสั้นๆ ข้างล่างนี้

/*
 * exec-return.c
 */

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    execl("/usr/local/bin/heaven", "heaven", NULL);
    fprintf(stderr,"Some one stole 'heaven', heaven closed.\n");
}


ข้อผิดพลาด มันเกิดขึ้น เพราะหาสวรรค์ไม่เจอ อันเนื่องมาจาก เราไปกำหนด ตำแหน่งแห่งหน ของสวรรค์ไว้ผิดที่ ผิดทางนั่นเอง  ซึ่งจะยังผลให้ process ใหม่ที่วิ่งอยู่นี้ ไม่สามารถทำงานได้ จึ่งต้องกลับมายังผู้สั่งการ แล้วเจอคำสั่งให้พิมพ์ข้อความ "Some one stole 'heaven', heaven closed.\n" ออกทาง standard error แล้วจึงปิด case จบงานกัน ที่ main นั่นเอง

ลองคอมไพล์โปรแกรมข้างบนนั้นดูซิ น่ะ

% cc -o myjob exec-return.c

แล้วลองเรียกดูด้วยคำสั่งนี้

% ./myjob

อันนี้ ก็เป็น หนึ่ง ในข้อยกเว้น ที่เกิดขึ้น ข้อยกเว้นที่ว่า เมื่อสั่ง exec แล้ว มันจะทำงานไป และจบงานตรงนั้น ไม่มีกลับมายังตัวผู้สั่งอีก

นี้ ขอคั่นรายการ ก่อนจะนำเสนอเรื่องราวของ process ในตอน process control ซึ่งจะว่าไป น่าจะเป็นการ create เสียมากกว่า แต่ก็อย่างว่า วิชาการเรื่องนี้ คนไทย ไม่ได้สร้าง/ผลิตขึ้น เขาว่ามายังไง ก็ต้องว่าไปยังงั้น อย่างมากก็แค่ท้วงติงไป ซึ่งอาจจะได้ผล ไม่ได้ผล ก็แล้วแต่ว่า เจ้าของนั้น จะรับฟังหรือไม่ (หรืออีกที ก็ เจ้าของนั้น จะยังมีชีวิตอยู่หรือเปล่า)

ลป.  ลืมไปว่า ใน FreeBSD นั้นไม่มีแฟ้ม /usr/local/bin/heaven ครับ หาก UNIX รสชาติที่ท่านใช้นั้นมี heaven ดูให้ดีดีก็แล้วกัน ยังไงเสียก็ อ่าน man heanven, คู่มือสวรรค์, ให้ละเอียดน่ะครับ

Tuesday, June 26, 2012

UNIX Programming


“Process”
ได้เคยเขียนเรื่องของ process ไว้บ้างแล้ว ใน blog นี้, fork, แต่ขอเริ่มใหม่อีกครั้ง ให้อยู่ในหัวข้อของ UNIX Programming ไปเลยด้วยกันนี้

หลักการทำงานของ UNIX เท่าที่คลุกคลีมาคือ หากมีของอยู่แล้ว ก็ใช้ของนั้นมาทำงานให้ ไม่ต้องสร้างของนั้นขึ้นมาใหม่อีก และเพราะเหตุนี้ ใน FreeBSD จึงเต็มไปด้วย tools เพื่อใช้งาน เพื่อให้แต่ละงาน ช่วยงานอื่นให้สำเร็จลุล่วงไปได้

โดยหลักอันนี้เอง ทำให้เรา เรียกโปรแกรมอื่น มาทำงานให้เราขณะที่เราก็กำลังวิ่ง process ของเราอยู่ ซึ่งก็คือสิ่งที่ได้เขียนไป และ ให้ลิงค์ไว้ข้างบนนั้น นั่นเอง

(๓.๑) “system” ฟังก์ชั่น
เราใช้บริการของฟังก์ชั่นใน system library ชื่อ system เรียกโปรแกรมระบบ ในตัวอย่างนี้คือโปรแกรมชื่อ date มาช่วยงานได้ ดังนี้

#include <stdlib.h>

main()
{
    system("date");
}

ซึ่งในชีวิตจริง คงไม่ใช่โปรแกรม date นี้หรอก เป็นแต่เพียงยกตัวอย่างมาให้เห็นว่า การเรียกใช้บริการของฟังก์ชั่นใน system library นั้นกระทำกันผ่านช่องทางไหน  อนึ่ง ท่านสามารถ เขียน คำสั่งที่จะส่งผ่านให้ฟังก์ชั่น system ผ่านทางฟังก์ชั่น sprintf ได้

แต่ถึงอย่างนั้น วิธีการนี้ก็ยังคงมีข้อจำกัดอยู่ ตรงที่ว่า buffer output นั้นเราต้อง flush เอาเอง

(๓.๒) “Process Creation – execl & execv”
จะเห็นว่า แม้ฟังก์ชั่น system จะอนุญาตให้เรา เรียกโปรแกรม จากระบบปฏิบัติการ มาทำงานให้เราได้  แต่บางครั้ง ยังรู้สึกอยู่ว่า เราน่าจะทำอะไร มากกว่านั้นได้ กับ process ที่จะวิ่งงานให้นี้

วิธีที่ง่ายที่สุด สำหรับงานนี้คือ สั่งให้โปรแกรมใหม่นั้น ทำงานไปเลย โดยไม่ต้องหวลกลับมายัง โปรแกรมหลัก ของเราอีก   โดยการใช้ execl ดังนี้

/*
 * execl routine
 */

#include <unistd.h>

int main(int argc, char *argv[])
{
    execl("/bin/date", "date", NULL);
}

execl จะทับหน่วยความจำของโปรแกรมเดิมหมดสิ้น ดังนั้น จึงไม่สามารถ ที่จะกลับมาหา สถานะเดิม ได้อีกแล้ว เมื่อ execl ได้ทำงานเสร็จสิ้นแล้ว จึงมีแต่ ทำงานแล้วตายไปเลย อยู่ที่ว่า
  • สุคติ ทำงานสำเร็จ exit status 0 หรือ 
  • ทุคติ ทำงานไม่สำเร็จ มีปัญหา exit status ไม่เป็น 0

อีกหนึ่ง routine ที่คล้ายๆกับ execl คือ execv ที่อนุญาตให้ท่าน สร้างองค์ประกอบต่างๆ ของคำสั่งที่จะเรียกใช้งานได้ โดยมีวิธีการเรียกใช้งาน ดังนี้

int execv(const char *pathname, const char *argv[]);

ทั้ง ๒ routine นี้ เมื่อจะเรียกใช้งาน ต้องรวมแฟ้ม unistd.h เอาไว้ด้วย   ขอให้ท่านอ่าน execl(3) ด้วย และสำหรับรายละเอียดอื่นๆ จากเอกสารอ้างอิง หมายเลข ๓ บทที่ ๘  หัวข้อ process control

Thursday, June 21, 2012

UNIX Programming

จาก standard I/O library ที่ได้เกริ่นนำมา คงพอให้ความคิด เกี่ยวกับการทำงานกับ แฟ้มเอกสาร ที่
  • ต้องเปิด
  • ต้องอ่าน/เขียน
  • ปิดแฟ้ม
นอกไปจากตอน I ที่เราทำงานตรงๆกับ terminal ซึ่ง file pointer นั้นเชื่อมโยงกับ terminal มาให้แล้ว ก็พอเห็นความแตกต่าง และ เนื้องานที่เพิ่มขึ้นมาบ้าง  มา เรามาไปกันต่อในเรื่องที่ยังค้างคาอยู่อีกนิด แต่ต้องทิ้งห่าง และ แยกออกมาพิเศษ

(๒.๒) Error Handling -- stderr and exit
 ในช่วงแรก ของตอน II นี้นั้น โปรแกรม wc.c เขาจะตรวจว่า สามารถเปิดแฟ้มเอกสารได้หรือไม่ หากไม่ได้ ก็จะแจ้งเตือน ผ่านทาง stdard error, stderr ซึ่ง ยังไงเสีย stdard error นี้ก็จะเชื่อมโยงกับ terminal เสมอ ไม่มีทาง กำหนดให้เป็นอย่างอื่น ไปได้ ทั้งนี้ก็เพื่อให้ผู้ใช้งาน ทราบ หรือ รับรู้ได้ทันทีว่า เกิดความผิดพลาดขึ้นแล้ว นั่นเอง

ความผิดพลาดนี้ จะแจ้งเตือนผู้ใช้งาน ผ่านทางประโยค fprintf(stderr,"...") ซึ่งนับว่าง่ายที่สุดแล้วในเบื้องต้นนี้

อนึ่ง โปรแกรมสามารถส่งสัญญาณแจ้งเตือนถึง ความผิดพลาด ในทางอื่นได้ โดยการใช้ฟังก์ชั่น exit เพื่อ หยุดการทำงาน ของโปรแกรม  และ ค่าที่ส่งให้กับฟังก์ชั่น exit นั้น จะเป็นตัวที่ส่งผ่านให้ โปรแกรมอื่นใดก็แล้วแต่ ที่เรียกโปรแกรมนี้ มารับไปตรวจสอบ ถึง ความสำเร็จ หรือ ล้มเหลว กันอีกที

โดยข้อตกลงพื้นฐาน ค่าที่ส่งผ่านให้กับ exit จะมีค่าเป็น 0 เพื่อบอกว่า ไม่มีความผิดพลาดเกิดขึ้น  และ ค่าที่ไม่เป็น 0 จะแสดงถึงสัญญาณบ่งชี้ถึง สถานการณ์ที่ไม่ปกติ

ฟังก์ชั่น exit จะเรียก ฟังก์ชั่น fclose  เพื่อกวาดล้าง buffer ออกให้หมด จากนั้น จะเรียกฟังก์ชั่น _exit  ซึ่ง ทำให้โปรแกรม หยุดการทำงานทันที โดยไม่แยแสว่า buffer จะมี หรือ ไม่มีข้อมูลอยู่

ฟังก์ชั่น exit ระบุไว้ใน stdlib.h ดังนั้น ก่อนใช้งานนี้ ท่านสมควรประกาศด้วยประโยค

#include <stdlib.h>

ในตอนแรกเริ่มของแฟ้มเอกสารที่ต้องใช้ฟังก์ชั่นนี้ด้วย เพื่อมิให้เกิดปัญหา ในตอน compile ขึ้นมา

อนึ่ง เพื่อเพิ่มพูนความรู้เรื่องฟังก์ชั่นต่างๆใน stdandard I/O library ขอให้ท่านอ่าน manual ในเรื่องนี้ด้วย  โดยใช้คำสั่งนี้

% man 3 stdio

หากท่านไม่สามารถอ่านจากเครื่องได้ ก็สามารถหาอ่านได้จาก เอกสารอ้างอิง ๑ หรือ เอกสารอ้างอิง ๒ ซึ่งมีอยู่ใน appendix ด้วยกันทั้งคู่

ในตอน II นี้ ขอยุติลงไว้แต่เพียงเท่านี้ ตอนหน้าเราจะกล่าวถึง process และ signal ในที่สุด ก็เป็นอันจบเรื่อง การเขียนโปรแกรม ในสภาพแวดล้อมของ FreeBSD (หรือ UNIX ในเทอมทั่วๆไป)

ขอขอบคุณ ที่สละเวลาอันมีค่ามาอ่าน

Wednesday, June 20, 2012

UNIX Programming

ทั้ง ๒ entries ที่ผ่านมา คือ program arguments  และ stdin/stdout  นั้น ถือเอาเป็น ตอนที่ I น่ะครับ นั่นเป็นเรื่องของ terminal ล้วนๆ ที่ไม่ต้องลำบากในการ “เปิด” และ “ปิด” อะไรให้ยุ่งยากแต่อย่างใดเลย  ใน entry นี้จะเป็นเรื่องที่ พิเศษ มาอีกนิดหนึ่ง  เชิญครับ

“STANDARD I/O LIBRARY”
standard I/O library (ต่อไปจะเรียกแต่สั้นๆว่า library) เป็นการรวบรวม routines ต่างๆ ไว้ที่เดียวกัน เพื่อให้เกิด ประสิทธิภาพ ของงานบริการด้าน I/O สำหรับโปรแกรมภาษา C และเพื่อให้สามารถใช้งานที่ไหนก็ได้  library นี้มีอยู่ในทุกๆระบบที่สนับสนุนภาษา C ดังนั้นโปรแกรมจึงสามารถโยกย้ายจากระบบหนึ่ง ไปยังอีกระบบอื่นได้โดยไม่ต้องแก้ไขเปลี่ยนแปลงอะไรเลย

“(๒.๑) File Access

โปรแกรมที่ยกมาให้ดูในตอน I นั้นทุกโปรแกรม อ่านข้อมูลจาก stdin และ เขียนผลลัพธ์ลงสู่ stdout ซึ่งได้นิยามเอาไว้ดีแล้ว และ โปรแกรม สามารถติดต่อได้โดยตรง    ในลำดับถัดมาเราจะเขียนโปรแกรมที่ต้องเข้าถึงแฟ้มเอกสาร ที่ยังไม่มีการติดต่อ กับโปรแกรม มาก่อนเป็นที่เรียบร้อยแล้ว เหมือนดั่งในกรณี stdin/stdout นั้น      ตัวอย่างง่ายๆ ได้แก่โปรแกรม wc ซึ่งนับจำนวนอักขระ จำนวนคำ จำนวนบรรทัดในแฟ้มเอกสารต่างๆ  เช่น

% wc x.c y.c

ก็จะพิมพ์ จำนวนบรรทัด คำ และอักขระในแฟ้มทั้งสอง และ จำนวนรวมแต่ละอย่างออกมา เป็นต้น

ปัญหาคือ เราจะให้แฟ้มเหล่านั้นถูกอ่านได้อย่างไรกัน นั่นคือจะเอาชื่อแฟ้ม ไปเชื่อมต่อกับประโยคทาง I/O ที่อ่าน-เขียนข้อมูลจริงๆ ได้อย่างไรกัน

กฏที่วางเอาไว้นั้น ง่ายมาก คือ ก่อนที่แฟ้มจะถูกอ่าน หรือ เขียน แฟ้มเอกสารต้องได้รับ การเปิด ก่อน ด้วยฟังก์ชั่นมาตรฐานใน library นี้ที่ชื่อ fopen.   ฟังก์ชั่นนี้ , fopen, จะเอาชื่อแฟ้มมา, ในที่นี้คือ x.c y.c ,  ดำเนินการบันทึกเป็นเบื้องต้นไว้ แล้วติดต่อ ถกเถียงกับระบบปฏิบัติการ  และ ได้ค่า ชื่อในวงการ มา ซึ่งจะใช้ชื่อนี้แหละ สำหรับการอ่าน และ การเขียนในลำดับต่อๆไป

ชื่อในวงการ นี้จะเป็น pointer, เรียกกันว่า file pointer, ที่ชี้ไปยัง structure ที่มีรายละเอียดของแฟ้ม เช่น ตำแหน่งของ buffer  ตำแหน่งปัจจุบันของอักขระใน buffer  แฟ้มนี้จะเปิดสำหรับอ่าน หรือ เปิดสำหรับเขียน และอื่นๆ  ซึ่งผู้ใช้งาน ไม่จำเป็นต้องรู้เลยแม้แต่น้อยนิดเกี่ยวกับ structure นี้ เพราะแฟ้ม stdio.h ได้ให้นิยามของ structure เอาไว้แล้ว ภายใต้ชื่อ FILE

สิ่งที่จำเป็นต้องระบุไว้ ก็มีเพียง การประกาศใช้งาน FILE เท่านั้น ดังตัวอย่าง

FILE *fp, *fopen();

ซึ่งบอกว่า fp เป็น pointer ไปยัง FILE และ fopen เป็นฟังก์ชั่นที่คืนค่า pointer มายัง FILE  ขอให้ทราบเอาง่ายๆว่า FILE นั้นเป็น ชนิดๆหนึ่ง คล้ายๆกับ int นั่นเอง อย่าคิดมาก

การเรียกใช้งานของฟังก์ชั่น fopen ก็ตรงไปตรงมา ดังนี้

    fp = fopen(name, mode);

โดยที่ name เป็นชื่อแฟ้มเอกสารที่เราต้องการทำงานด้วย   และ   mode นั้นคือลักษณะการทำงานกับแฟ้มเอกสารนั้นว่า เราจะ  อ่าน("r") หรือ เขียน("w") หรือ เขียนต่อท้าย("a") ทั้ง name และ mode เป็นอักขระ

ถ้าแฟ้มที่เราต้องการเปิดเพื่อ เขียน หรือ เขียนต่อท้าย นั้นยังไม่มี แฟ้มก็จะถูกสร้างขึ้นมา  การเปิด แฟ้มเอกสารที่มีอยู่แล้ว เพื่อ เขียน นั้น มีผลให้ สาระของแฟ้มเอกสารนั้น ถูกเขียนทับไป  และความพยายาม ที่จะอ่านแฟ้ม ที่ไม่มีอยู่เลย ยังผลให้ เกิด ความผิดพลาด ขึ้นมา   ในกรณีที่เกิด ความผิดพลาด ขึ้น ฟังก์ชั่น fopen จะคืนค่า NULL ซึ่งเป็น null pointer มาให้ผู้ใช้งาน และค่านี้ ได้รับ การนิยาม ไว้แล้ว ในแฟ้ม stdio.h

ในลำดับถัดมา ที่จำเป็นต้องทำคือ การอ่าน หรือ การเขียน หลังจากแฟ้มเอกสารได้รับ การเปิด ขึ้นมาแล้ว    ในการนี้ สามารถกระทำได้หลายทาง แต่ที่ง่ายที่สุดคือ  getc  และ  putc   โดยที่ getc จะคืนค่าถัดมาจากแฟ้มที่อ่าน มาให้, ฟังก์ชั่นนี้ ต้องการ file pointer เพื่อบอกว่า จะเอาจากแฟ้มไหน ดังนี้แล้ว

    c = getc(fp);

ก็จะนำค่าจากแฟ้มเอกสารที่อ้างอิงถึงโดย file pointer fp นั้น มาไว้ยัง c  และฟังก์ชันนี้ จะคืนค่า EOF ในกรณีที่อ่านไป จนถึงตำแหน่งสุดท้าย ของแฟ้มเอกสารแล้ว    putc  ก็จะกลับกันกับ  getc  กล่าวคือ

    putc(c, fp);

จะนำค่าอักขระ c ไปเก็บไว้ยังแฟ้ม ที่ชี้ไปโดย fp  แล้วคืนค่า c กลับมา    ทั้ง ๒ ฟังก์ชั่น จะคืนค่า  EOF ในกรณีที่เกิดความผิดพลาดขึ้น

อนึ่ง เมื่อโปรแกรมเริ่มทำงานนั้น แฟ้ม ๓ แฟ้มจะ ถูกเปิด ให้โดย อัตโนมัติทันที และ file pointer ก็จะกำหนดให้ ในทันที โดยอัตโนมัติ ด้วยเช่นกัน คือ  standard input, standard output และ standard error และ file pointer ของทั้ง ๓ นี้คือ stdin, stdout, stderr ตามลำดับ  และโดยปกติแล้ว ทั้ง ๓ file pointer นี้จะต่อตรงกับ terminal เสมอ แต่ก็อาจจะ redirect ไปยังที่อื่น หรือ pipe ต่อไปยังโปรแกรมอื่น อีกก็ได้  stdin, stdout, stderr นั้นเป็นวัตถุ ที่นิยามไว้แล้ว ในชนิด FILE * จึงสามารถใช้ได้กับวัตถุอื่นใด ที่เป็นชนิด FILE * ได้

เอาล่ะ เมื่อได้ความรู้มาเท่านี้แล้ว เรามาลองยกร่างโปรแกรมง่ายๆ ที่ชื่อ wc กันดูบ้าง ดังนี้

ถ้าโปรแกรมนี้ มี arguments มันก็จะ process arguments ไปจนหมด ถ้าไม่มี ก็จะรับจาก standard input  ซึ่งโดยลักษณะนี้ ทำให้โปรแกรม สามารถเรียกใช้งาน แบบเดี่ยวๆ ได้ หรือ สามารถใช้ร่วม เป็นส่วนหนึ่ง ของ process ใหญ่ได้    โปรแกม wc มีรายละเอียด, แฟ้มชื่อ wc.c,  และ Makefile ดังนี้

/*
 * wc.c
 */ 

#include <stdio.h>

int main(argc, argv)
int argc;
char *argv[];
{
  int c, i, inword;
  FILE *fp, *fopen();
  long linect, wordct, charct;
  long tlinect = 0, twordct = 0, tcharct = 0;

  i = 1;
  fp = stdin;
  do {
    if (argc > 1 && (fp=fopen(argv[i], "r")) == NULL) {
      fprintf(stderr, "wc: can't open %s\n", argv[i]);
      continue;
    }
    linect = wordct = charct = inword = 0;
    while((c = getc(fp)) != EOF) {
      charct++;
      if (c == '\n')
    linect++;
      if (c ==  ' ' || c == '\t' || c == '\n')
    inword = 0;
      else if (inword == 0) {
    inword = 1;
    wordct++;
      }
    }
    printf("%7ld %7ld %7ld", linect, wordct, charct);
    printf(argc > 1 ? " %s\n" : "\n", argv[i]);
    fclose(fp);
    tlinect += linect;
    twordct += wordct;
    tcharct += charct;
  } while (++i < argc);
  if (argc > 2)
    printf("%7ld %7ld %7ld total\n", tlinect, twordct, tcharct);
  return(0);
}



ฟังก์ชั่น fprintf ก็เหมือนกับฟังก์ชั่น printf เพียงแต่เราระบุ file pointer ชนิด FILE ไว้เป็น argument แรกก่อนเขาอื่น เท่านั้นเอง 

ฟังก์ชั่น fclose ก็เป็นฟังก์ชั่น ที่ตรงข้ามกับ fopen  มันทำหน้าที่ ตัดการเชื่อมต่อ ระหว่าง file pointer และ ชื่อข้างนอก ที่สร้างขึ้นมา โดยฟังก์ชั่น fopen นั้น และ ปลดปล่อย file pointer นั้น เพื่อให้ระบบ สามารถมี file pointer ไว้ใช้งานได้พอเพียง 

ขณะเดียวกัน fclose ก็จะกวาดล้าง buffer ที่ฟังก์ชั่น putc ได้รวบรวมเอาไว้นั้น ออกมาด้วยพร้อมกันเลย ฟังก์ชั่น fclose นี้ จะถูกเรียกมาโดยอัตโนมัติ ในทุกโปรแกรมที่ หยุดการทำงาน

และนี่คือ Makefile ของแฟ้มโปรแกรมที่ใช้ชื่อว่า wc.c

CC=clang
CFLAGS= -g
PROG= wc
SRCS= wc.c

NO_MAN=

.include <bsd.prog.mk>


อนึ่ง หากท่านสนใจในเรื่องของ Makefile ลองเข้าไปอ่านดูได้ที่ /usr/share/mk/bsd.README และแฟ้มอื่นๆ ที่เกี่ยวข้องได้ครับ

ขออนุญาตพัก ตอน II ช่วงแรกไว้ ที่นี่ก่อนครับ  ขอขอบคุณ ที่สละเวลามาอ่าน

Tuesday, June 19, 2012

UNIX Programming


(๑.๒) “Standard Input” และ “Standard Output”

จะไม่ขอแปลล่ะว่า  “Standard Input” คือ ทางเข้ามาตรฐาน และ  “Standard Output” คือ ทางออกมาตรฐาน เพราะหากพินิจพิจารณากันแล้ว อาจจะหมายถึง ทางอาหารใหม่เข้า และ ทางอาหารเก่าออก สำหรับคนธรรมดาทั่วไป แลหากมองไปถึงว่า standard input ของอีกโปรแกรมหนึ่ง จะมาจาก standard output ของอีกโปรแกรมหนึ่งแล้ว ยิ่งพาลไม่อยากกินอาหารไปเสียเลย ก็เป็นได้ แต่ ยังดีที่ว่า ยังไม่พบว่า stdanard output ของโปรแกรมหนึ่งเป็น standard input ของโปรแกรมเดียวกัน

ตกลง เรียกกันง่ายๆว่า stdin  สำหรับ “Standard input” และ stdout สำหรับ “Standard output” น่ะ

กลไกที่ง่ายที่สุดสำหรับการใส่ข้อมูลลงไปคือการอ่านค่าจาก stdin ซึ่งก็ได้แก่ termail ของผู้ใช้งานในขณะนั้นนั่นเอง    ฟังก์ชั่น getchar จะส่งค่าอักขระหนึ่งค่าถัดมา ทุกครั้งที่ฟังก์ชั่นนี้ถูกเรียกใช้งาน    แฟ้มอาจจะใช้แทนที่ terminal ได้โดยการใช้ข้อตกลง <file นี้ นั่นคือ ถ้า โปรแกรม prog เรียกข้อมูลด้วย getchar แล้ว คำสั่งนี้

% prog < file

บังคับให้ prog อ่านจาก file แทนที่  terminal  แลในความเป็นจริงแล้ว prog ไม่จำเป็นที่ต้องรู้เลยว่า ข้อมูลที่ใช้นั้น มาจากไหน  ซึ่งก็เป็นจริง ถ้าว่าข้อมูลนั้น มาจากอีกโปรแกรมหนึ่ง ดังนี้

% otherprog | prog

เพราะ stdin สำหรับ prog นั้นจะมาจาก stdout ของ otherprog

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

ในทำนองเดียวกันนี้ putchar(c) ก็จะนำอักขระ c ไปที่ stdout  ซึ่งโดยค่าปริยายก็คือ terminal ของผู้ใช้งานในชณะนั้น  และผลลัพธ์ ที่ออกมาสามารถ จับไว้ ได้และเก็บในแฟ้ม ด้วยข้อตกลง >otherfile นี้ คล้ายๆกับ getchar นั่นเอง  กล่าวคือ ถ้า prog ใช้ putchar  แล้ว คำสั่ง

% prog > otherfile

ทำให้โปรแกรม prog เขียนหรือส่ง stdout ไปที่แฟ้ม otherfile แทนที่จะเขียนไปที่ terminal ตามค่าปริยายที่ตั้งไว้แล้วนั้น  ในกรณีนี้  otherfile จะถูกสร้างขึ้น ถ้าไม่มีอยู่ก่อน และจะถูกเขียนทับ ในกรณีที่มีอยู่แล้ว  ในขณะเดียวกัน กลไกของ pipe ก็สามารถใช้งานได้ด้วย เช่น

% prog | otherprog

ทำให้ stdout ของ prog กลายเป็น stdin ของ otherprog ไป    ตัวอย่างของโปรแกรมสั้นๆ ที่เขียนขึ้นมา เพื่อแสดงการทำงานของ getchar, putchar ปรากฏในเอกสารอ้างอิง หมายเลข (๒) ในหัวข้อที่ 1.5 ของบทแรกนั้น   ในที่นี้ ใคร่ขอยกตัวอย่าง โปรแกรมที่นับ อักขระ คำ จาก terminal ของผู้ใช้งาน  ในหน้า 20 หัวข้อที่ 1.5.4 มาให้ได้ศึกษากัน อนึ่งสำหรับ printf นั้น ก็เป็นฟังก์ชั่น ที่กำลังจะกล่าวถึง ในย่อหน้าหน้านี้

/*
 *
 * K & R , the C Programming Language
 *
 * knrwc.c
 *
 */
 

#include <stdio.h>

#define IN 1
#define OUT 0

int main()
{
  int c, nl, nw,nc, state;

  state = OUT;
  nl = nw = nc = 0;
  while ((c  = getchar()) != EOF) {
    ++nc;
    if (c  == '\n')
      ++nl;
    if (c == ' ' || c == '\n' || c == '\t')
      state = OUT;
    else if (state == OUT) {
      state = IN;
      ++nw;
    }
  }
  printf("%d %d %d\n", nl, nw, nc);
  return 0;
}


และนี่คือ Makefile ของโปรแกรมข้างบนนั้น

CC=clang
CFLAGS= -g
PROG= knrwc
SRCS= knrwc.c

NO_MAN=

.include
<bsd.prog.mk>

ฟังก์ชั่น printf ซึ่งจัดการรูปแบบของผลลัพธ์ได้หลากหลายนั้น ก็ใช้กลไกการทำงานเดียวกับ putchar  ดังนั้น การเรียกใช้งาน printf และ putchar จึงสามารถสลับสับเปลี่ยนกันไปมาได้ ผลลัพธ์ก็จะปรากฏตามลำดับที่เรียกใช้งาน     ฟังก์ชั่นคู่เหมือนของ pritf คือ scanf ซึ่งจะจัดรูปแบบของ สิ่งที่จะรับเข้ามาทาง stdin  และ  scanf นี้ใช้กลไกเดียวกันกับ getchar

ก่อนจากในตอนนี้ ใคร่ขอยกตัวอย่างชัดๆ ว่า เพียงแค่ getchar, putchar ก็สามารถนำมาใช้งานในชีวิตจริงได้แล้ว ดังโปรแกรมข้างล่างนี้ ซึ่งเขาจะ ขลิป อักขระพิเศษ ที่รับเข้ามา ออกทิ้งเสีย แล้วจึงพิมพ์ออกทาง stdout


/*
 *
 * ccs.c
 *
 */
 
#include <stdio.h>

int main()
{
  int c;
  while ((c = getchar()) != EOF)
    if ((c >= ' ' && c < 0177) || c == '\t' || c == '\n')
      putchar(c);
  return 0;
}


ทั้งสองโปรแกรมที่ยกตัวอย่างมาให้ดูนี้ บรรทัดแรกที่เป็น

#include <stdio.h>

นั้น จะบอก compiler ว่าให้รวมเอาแฟ้ม /usr/include/stdio.h เข้าไว้ด้วย ซึ่งแฟ้มดังกล่าว จะนิยามค่าต่างๆ เช่น EOF เอาไว้แล้ว

และ นี่คือ Makefile ของโปรแกรมขลิปอักขระพิเศษนี้

CC=clang
CFLAGS= -g
PROG= ccs
SRCS= ccs.c

NO_MAN=

.include <bsd.prog.mk>


unix เขาจะมีหน้า manual ให้สำหรับทุกคำสั่ง ดังนั้น FreeBSD จึงสรีางแม่แบบสำหรับหน้า manual ขึ้นมาให้ด้วย สำหรับทุกโปรแกรมที่เราเขียนขึ้น แต่ในครั้งแรกนี้ เพื่อตัดความยุ่งยากออกไปก่อน จึงไม่ต้องให้มีหน้า manual ในโปรแกรมของเราที่เขียน และนี่คือความหมายของบรรทัด

NO_MAN=

ในแฟ้ม Makefile นั้น
ในตอนที่สองนี้ ขอยุติไว้เพียงเท่านี้ ยังมีอีกหลายตอนอยู่น่ะครับ

Monday, June 18, 2012

UNIX Programming


อันธรรมดาเครื่องคอมพิวเตอร์ หรือที่สมัยนี้เรียกกันเป็น Desktop มั่ง Notebook มั่ง Netbook มั่ง Tablet มั่ง หากปราศจากเสียซึ่ง softwares ซะแล้ว ก็ไม่ต่างอะไรกับเศษวัสดุธรรมดาๆนี่เอง ประโยชน์ แม้แต่จะใช้ขับไล่หมา แมว ปัดกวาดบ้าน ก็ยังแทบหาไม่ได้ อย่าว่าแต่การใช้งานตามวัตถุประสงค์เลย  อันว่า วัตถุประสงค์นี้ ก็ใช่แต่หมายถึงสิ่งที่ชายที่มีชื่อว่า ประสงค์ มีอยู่แต่เท่านั้นก็หามิได้ หากแต่ยังหมายความ ตามที่เขียนเอาไว้ใน พจนานุกรมภาษาไทย ฉบับราชบัณฑิตยสถาน พ.ศ. ๒๕๔๒ ซึ่งให้ความหมายเอาไว้ว่า

วัตถุประสงค์     น. ผลที่ประสงค์ให้บรรลุ เช่น วัตถุประสงค์ของ
    มัธยมศึกษาก็เพื่อให้เยาวชนมีความรู้ คิดเป็น ทำเป็น และมีคุณธรรม,
    จุดประสงค์ ก็ว่า.

ที่แม้จะพยายามอ่านอย่างไรๆ ก็ยากที่จะทำความเข้าใจได้ ภายในระยะเวลาทีกำหนด

เออก็ ปล่อยให้เป็นเรื่องของ ผู้เชี่ยวชาญในแต่ละเรื่องๆ เขาทำกันเข้าไปดีกว่า แล้วที่สุด ผู้เชี่ยวชาญทั้งหลายเหล่านั้น จะมาปรองดองกันอย่างไร ก็ให้เป็นหน้าที่ของอีกฝ่ายหนึ่ง นอกเหนือไปจากผู้เชี่ยวชาญ ว่า จะให้ปรองดองกันในแบบไหน หากว่าท่านทั้งหลายเหล่านั้น หาความปรองดองให้ลงรอยกันเข้ามิได้  ... ปล่อยไป

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

ความที่กล่าวมาแล้วนี้ ก็พอจะเป็นสิ่งยืนยันได้เป็นอย่างดีว่า อันว่าตัวเครื่องคอมพิวเตอร์นั้น หากปราศจากเสียซึ่ง softwares เข้าดำเนินการโดยผู้ใช้แล้ว ก็ไม่ต่างอันใดเลยกับเศษวัสดุ  ฤๅหากจะเทียบในทางธรรมก็คงกล่าวได้ว่า สักแต่ว่ารูปที่ไม่มีวิญญาณ(นาม)ครอง รังแต่จะผุพังไปตามกาล

กล่าวสั้นๆ เครื่องคอมพิวเตอร์นั้น เอามาใช้เขียน softwares หรือ programmes เท่านั้น จึงจะเกิดประโยชน์มหาศาล

ย้อนกลับเข้าสู่เรื่องเดิม UNIX Programming ที่ตั้งใจจะเขียนขึ้นมานี้ ก็เพียง ให้ท่านทั้งหลาย ได้ใช้ประโยชน์ เครื่องคอมพิวเตอร์ ที่ท่านหาซื้อมา ด้วยเงินของท่านนั้น ให้เกิดประโยชน์เต็มที่ โดยตั้งกรอบเรื่องราวเอาไว้ว่า
    ๑) เป็นการเขียนโปรแกรมภายใต้ระบบปฏิบัติการ FreeBSD อันเป็นการมุ่งหมายถึง สภาพแวดล้อมการทำงาน ในแบบฉบับของ UNIX เท่านั้น
    ๒) ผู้ใช้งานต้องมีความรู้เรื่องภาษา C เป็นอย่างดี
    ๓) เป็นการแนะนำแต่เรื่องพื้นๆ เป็นเบื้องต้นเท่านั้น

อนึ่ง เรื่องนี้ตั้งใจเขียนขึ้นมาด้วยความมุ่งมั่นดังกล่าว (ส่วนจะสำเร็จตามวัตถุประสงค์หรือไม่นั้น คงต้องไปนครนายกที่พี่สงค์เขาอาศัยอยู่ แล้วถามพี่เขาดู) ด้วยเอกสารอ้างอิงเท่าที่มีอยู่ในมือดังนี้
๑) 4.4 Programmer's Supplementary Documents;  CSRG University of California at Berkeley
๒) The C Programming Language; Brian W. Kernighan, Dennis M. Ritchie
๓) Advanced Programming in the UNIX Environment; W. Richard Stevens
๔) เล่มอื่นๆ อันจะยกมากล่าวเป็นครั้งคราวๆไป โดยเฉพาะอย่างยิ่ง FreeBSD documentations ที่ระบบปฏิบัติการให้มาทั้งหมด


(๑) พื้นฐาน

(๑.๑) โปรแกรม อาร์กิวเม้นต์
เมื่อโปรแกรมถูกเรียกใช้งานนั้น (บทที่ ๗) arguments ที่ระบุในคำสั่ง -- ที่ปรากฏในรูปแบบของ ชื่อแฟ้มเอกสาร ที่มักเรียกกันว่า โปรแกรม -- จะปรากฏในรูปแบบของ จำนวน arguments และ array ของตัวชี้ไปยัง arguments นั้นๆ ในฟังก์ชั่นที่ชื่อ main  แลโดยธรรมเนียมนิยม ค่าของ argv[0] จะเป็นชื่อของคำสั่งนั้นๆ ดังนี้แล้ว ค่าของ argc จึงมากกว่า 0 เสมอ

โปรแกรมต่อไปนี้ ซึ่งเป็นการยกตัวอย่างของโปรกม echo ในระบบมาอย่างหยาบๆ แสดงให้เห็นถึงกลไกดังกล่าวนั้น

/*
 *
 * myecho.c
 *
 */

#include <stdio.h>

int main(argc, argv)
int argc;
char *argv[];
{
  int i;

  for(i = 1; i < argc; i++)
    printf("%s%c", argv[i], (i < argc-1 ? ' ' : '\n'));

  return(0);
}


argv เป็นตัวชี้ไปยัง array ซึ่งมีตัวชี้แต่ละตัวชี้ไปยัง แถวของอักขระที่จบลงด้วย \0 อันทำให้ทึกทักเอาว่าเป็น strings ได้โดยง่าย   โปรแกรมนี้เริ่มด้วยการพิมพ์ argv[1] แล้วก็วนไปเรื่อยจนกว่าจะหมด

จำนวนนับของ arguments และตัว arguments เองจะเป็นพารามิเตอร์ให้กับฟังก์ชั่น main ที่ซึ่ง ถ้าเราต้องการใช้งานในภายหลัง เราต้อง copy เอาไว้ต่างหากในตัวแปรภายนอก อีกทีหนึ่ง

ในการคอมไพล์โปรแกรมนั้น เป็นกระบวนการที่ยุ่งยากเอาการอยู่ เพราะต้องกำหนดค่าของ search path ไปให้ครบ รวมทั้งการต้องระบุ libraries ต่างๆไปด้วย    ในการนี้ FreeBSD เขาเอื้อให้ผู้เขียนโปรแกรม สามารถคอมไพล์ง่ายๆ ผ่านคำสั่ง make โดยคำสั่งนี้จะมุ่งมองหาแฟ้มที่ชื่อ Makefile ในสาระบบแฟ้มปัจจุบันก่อนเป็นเบื้องต้น ก็แลสาระของแฟ้ม Makefile นั้นเป็นดังนี้,  สมมุติว่า โปรแกรมที่เขียนขึ้นมานี้ เก็บไว้ในแฟ้มชื่อ myecho.c และ ท่านต้องการให้ได้ผลลัพธ์ ของคำสั่ง จากการคอมไพล์แฟ้มนี้ ในชื่อ echo

CC=clang
CFLAGS= -g
PROG= echo
SRCS= myecho.c

NO_MAN=

.include <bsd.prog.mk>


ตรงบรรทัดที่ ๓ และ บรรทัดที่ ๔ จะเป็นการกำหนดชื่อโปรแกรม และ ชื่อแฟ้ม ตามลำดับ  บรรทัดอื่นๆ ขอให้ผ่านไปก่อน ยังไม่ต้องให้ความสนใจในตอนนี้  ท่านลำบากเพียงสั่ง

% make

ก็ได้ executable file ชื่อ echo ในสาระบบแฟ้มปัจจุบันมาทันที ทดลองเรียกดู ตามนี้

% ./echo This is my first C programmes in FreeBSD

แล้วสังเกตุดูผลลัพธ์ที่ได้ และ เมื่อท่านเรียกโปรแกรมของระบบ ด้วย argument เดียวกัน ให้สังเกตุผลลัพธ์ด้วยว่า ต่าง หรือ เหมือนกันอย่างไร

% echo This is my first C programmes in FreeBSD

ขอให้สังเกตุความแตกต่างของทั้ง ๒ คำสั่งที่ยกมาให้ดูนั้นด้วย อนึ่งระบบปฏิบัติการที่ใช้ขณะนี้คือ

FreeBSD mni.jes.in.th 9.0-STABLE FreeBSD 9.0-STABLE #16: Wed Jun 13 06:36:32 ICT 2012     root@mni.jes.in.th:/kaitag/obj/usr/src/sys/JOTAWSKI  amd64

ขอยุติตอนแรก ไว้ก่อนเท่านี้   ไม่นานเกินรอ ตอนถัดไปจะมาให้ท่านได้อ่านกัน

View My Stats