Showing posts with label FreeBSD. Show all posts
Showing posts with label FreeBSD. 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

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

Thursday, May 24, 2012

FreeBSD: สภาวะแวดล้อมการทำงาน single user mode

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

อันนี้ ก็อย่างกังวลมากไปนัก ในสาระบบแฟ้ม /etc/rc.d/ นั้นมี ๒ แฟ้ม สำหรับ เริ่ม ให้ network ทำงานได้ คือ

/etc/rc.d/netif
/etc/rc.d/routing

ลำพังหากท่านใช้งานที่บ้าน ซึ่งเรียกผ่าน ADSL model แล้ว เพียงเรียก

# /etc/rc.d/netif start

ก็ได้ทั้งการต่อเชื่อมกับ ISP และได้ default route มาด้วยไปในคราเดียว (ได้ความรู้นี้จาก โฟรัม )

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

ขอให้โชคดีในการทำงานใน single user mode น่ะครับ  .... รูป จากแฟ้มตัวอย่างของ epix ข้างล่างนี้ไงครับ

Wednesday, May 23, 2012

FreeBSD: ports --> graphics --> epix

epix เป็นอีกหนึ่งโปรแกรม ที่ทำให้เราเขียนกราฟที่ยุ่งยากให้ง่ายเข้า แต่ งวดนี้แตกต่างจาก gnuplot ที่ว่า ต้องอาศัยเครื่องมือของ TeX เป็นหลัก นั่นก็หมายถึงว่า ผู้ใช้ ก็ต้องเป็นผู้ที่รู้เรื่อง TeX ดีพอตัว

ไปลงจาก ports ใน category graphics ครับ ชื่อก็ epix  ไม่ต้องกังวลเรื่องการใช้งาน เขามีคู่มือมาให้ในรูปแฟ้ม .pdf อ่านกันสบายๆ

Riemann
ข้อที่ถือว่าดี ควรค่าแก่การแนะนำคือ สามารถผลิต animation เล็กๆ ออกมาได้  แต่ทั้งนี้ ก็อย่างที่ว่า ผู้ใช้งานต้องมีพื้นความรู้พอตัว ไม่ว่าจะเป็นในเรื่องของการเขียนโปรแกรมด้วยภาษา C/C++ หรือ การใช้งาน LaTeX ที่ค่อนข้างยุ่งยากเอาการอยู่ เป็นเรื่องที่ ต้องมีไว้ รวมไปถึงการใช้งาน macro ยักษ์ อย่าง emacs ด้วย โดยเฉพาะอย่างยิ่ง อันหลังนี้ถือว่า เป็นหัวใจ ในการเขียนฟังก์ชั่น สำหรับผลิตกราฟที่เราต้องการออกมา แม้จะเป็น editors แต่ก็ได้รับการ ปรับ มาให้เป็นการจำเพาะแล้ว ต้องรู้จักด้วย ไม่งั้น  เอาไม่อยู่ แน่แน่

การใช้งานก็ง่าย
๑) เขียนแฟ้มสำหรับเขียนกราฟขึ้นมา ซึ่งไวยากรณ์คือ ภาษา C/C++
๒) เรียก script ที่เขาทำมาให้เพื่อมา process แฟ้มที่เขียนขึ้น
๓) เอากราฟไปใช้งาน

ตัวอย่างที่แสดงให้ดูนั้น แฟ้มในข้อ ๑) เป็นดังนี้
/* -*-ePiX-*- */
#include "epix.h"
using namespace ePiX;

P F(double t, double r, double theta)
{
  return P(r*r*Cos(2*theta),
           r*r*Cos(t)*Sin(2*theta) + r*Sin(t)*Sin(theta),
           r*Cos(theta));
}

domain R(P(0,0,0), P(1, 1.5, 1), mesh(24, 8, 32), mesh(24, 40, 80));

int main(int argc, char* argv[])
{
  if (argc == 3)
    {
      char* arg;
      double temp1, temp2;
      temp1=strtod(argv[1], &arg);
      temp2=strtod(argv[2], &arg);

      tix()=temp1/temp2;
    }

  domain R1(R.slice1(tix()));

  picture(P(-4,-4),P(4,4), "5x5in");

  begin();
  revolutions();

  grid(1,1);

  const P ctr(3.25,-3.25); // "clock" center
  bold();
  circle(ctr, 0.5);        // clock face

  line(ctr, ctr+polar(0.4, tix())); // hands
  label(ctr+polar(0.6, tix()), "$y$");

  line(ctr, ctr+polar(0.4, tix()+0.25));
  label(ctr+polar(0.6, tix()+0.25), "$w$");

  viewpoint(4,5,3);
  camera.range(20);

  line(P(0,0,0), P(3,0,0));
  line(P(0,0,0), P(0,3,0));
  line(P(0,0,0), P(0,0,2));

  plain(Red());
  plot(F, R1.resize3(0, 0.5));

  blue();
  plot(F, R1.resize3(0.5, 1));

  bold(Magenta());
  plot(F, R.slice3(0));
  plot(F, R.slice3(0.5));

  black();
  masklabel(P(3,0,0), "$x$");
  masklabel(P(0,3,0), "$y$");
  masklabel(P(0,0,2), P(-4,0), "$z=\\textrm{Re}\\,\\sqrt{x+iy}$", tr);

  end();
}

แฟ้มนี้ชื่อ riemann.flx ซึ่งเมื่อ process โดย script ที่ชื่อ flix แล้ว ก็ได้ผลที่แสดงมาให้ดูนั่นแหละ

% flix  --gif  riemann.flx

ผลลัพธ์ที่ได้ สามารถนำไปใช้งานร่วมกับ latex ได้เลย ในการผลิตเอกสารต่างๆ ที่ต้องการรูป ที่ยุ่งๆ

% epix  riemann.flx

เราจะได้แฟ้ม riemann.eepic ซึ่งมาสามารถนำไปรวมในแฟ้มที่เตรียมโดย LaTeX ได้ทันที
script ที่ให้มา เขาว่ามีอยู่ ๔ แต่เห็นใช้งานจริงตอนนี้เพียง epix, flix และ elaps  ซึ่งหากเราใช้กับแฟ้มตัวอย่าง ข้างบนนั้น ยกเว้น flix ที่ให้ผลลัพธ์มาเลยแล้ว ก็จะเป็นดั่งนี้

% epix  riemann.flx
% elaps riemann

คำสั่งสุดท้าย จะให้แฟ้ม riemann.eps ออกมาเป็นผลลัพธ์

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

คำสั่งที่ท่านต้องเติม ก็อยู่ระหว่าง begin();  และ  end(); นั่นหละ ค่อยๆอ่านจาก เอกสารที่เขาให้มา แล้วดูแฟ้มตัวอย่างจาก sample files ที่เขาให้มา ไม่นานนักก็คงบรรลุธรรม

พาดพิงถึง LaTeX แล้วคิดถึงเจ้าปลิวแฮะ

FreeBSD: สภาวะแวดล้อมการทำงาน . การเพิ่ม hard dsik

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

ในการนี้ ใคร่ขอแนะนำให้เพิ่ม usb hard disk มาสักก้อน ความจุราวๆสามสี่ร้อยกิกะไบท์ น่าจะพอ แต่คงหายากในยามนี้ ที่ความจุทะลุเทอราไบท์ไปแล้ว กอรปด้วยความที่โรงงานผลิต ยังไม่สามารถ ผลักดันสินค้า ออกมาสู้ท้องตลาด อันเนื่องาจาก น้ำท่วประเทศ เมื่อปีที่แล้ว  เอาเป็นว่า ท่านหามาได้ก็แล้วกันน่ะ usb hard disk

สำหรับวิธีการโดยทั่วๆไปในการเพิ่ม hard disk ให้กับระบบนั้น สมควรดำเนินการใน single user mode เป็นดีที่สุด  ก็ขอให้ท่านนำ usb hard disk มาต่อเข้ากับเครื่องผ่านทาง usb port ใด port หนึ่ง แล้วเปิดระบบปฏิบัติการ แล้วลงสู่ single user mode แล้วตระเตรียมการตามที่บอกไว้ในเรื่องท่านผ่านมานั้น


จากนั้นเรียก /usr/sbin/sysinstall มาทำงานจัดการเพิ่ม hard disk และจัดการ สร้าง partition รวมทั้ง mount point ให้ ตาม links ที่วางไว้ให้นี้ โปรดอ่านเอาจากนั้นให้ละเอียดก่อนเป็นเบื้องต้น (มะไฟ แปลมาจาก hand book ครับ)

FreeBSD: การจัดแบ่ง partition
FreeBSD: sysinstall ---> Allocating Disk Space
FreeBSD: sysinstall ---> Allocating Disk Space(cont.)
FreeBSD: installation


การกำหนด mount point
 
เอาหละ เอาเป็นว่าทำได้แล้ว และให้ usb hard disk นั้นเรียกชื่อว่า da0 และใช้ทั้งก้อนเพื่อ FreeBSD ดังนั้น slice จึงเป็น 1

ท่านเจียดเนื้อที่แบ่งเป็น swap partition สัก 4 GB และ file system ในส่วนที่เหลือของความจุทั้งหมด ระบบ จะกำหนดให้เองว่าเป็น partition d ชื่อของ file system นี้ก็คือ /dev/da0s1d โดยวาง mount point ไว้ที่ /kaitag

ดั่งนี้แล้ว /etc/fstab สำหรับ usb hard disk ก้อนใหม่นี้ ก็เป็นดังนี้

/dev/da0s1b        none        swap    sw        0    0
/dev/da0s1d        /kaitag        ufs      rw        2    2

หากชื่อของ usb hard disk ที่ท่านมี ท่านใช้ ต่างไปจากนี้ ก็ให้แก้ไขให้ตรง ให้สอดคล้องกันด้วย  อนึ่ง ๒ บรรทัดข้างบนนั้น ขอให้เพิ่มเข้าไปในแฟ้ม /etc/fstab ด้วย เพื่อว่าเมื่อท่านเปิดเครื่องทำงานแล้ว ระบบ จะได้ mount ให้เองโดยอัตโนมัติ


การเพิ่ม hard disk และการกำหนด mout point ก็มีเพียงเท่านี้ จะยังไม่เพิ่มสาระอื่น ให้รกมากไปกว่านี้ครับ

Tuesday, May 22, 2012

FreeBSD: สภาวะแวดล้อมการทำงาน single user mode

มูลเหตุ ที่ต้องเขียนเรื่องนี้ขึ้นมานั้น ก็เนื่องมาจากการที่ต้องลงไปทำงานใน single user mode บ่อยครั้งเป็นพิเศษ ในระยะนี้ ทั้งนี้เพราะต้องการลดการทำงานของ process อื่นๆลง เพื่อไม่ให้อุณหภูมิของ cpu สูงเกินไป

อุณหภูมิของ cpu สูงนั้นเกิดจากการที่ไป compile บางโปรแกรมเข้า ซึ่งก็ให้งุนงงงไปหมดทั้งตัวเหมือนกันว่า มันเขียนโปรแกรมของมันยังไง ถึงกับให้ cpu ทำงานหนักมาก จนความร้อนขึ้นสูงขนาดนั้น 102.5 c เลยครับ

โปรแกรมที่ว่านี้ก็ได้แก่ gcc, firefox, chromium, boost-*, openjdk, openoffice, libreoffice พวกนี้ โดยเฉพาะอย่างยิ่ง ตัวหลังสุดเนี่ย ยอดเยี่ยมกระเทียมดองเอาเลยทีเดียวเชียว

พอสั่ง make ทีไร พวกเป็นต้องไปไล่ตั้งแต่ต้นใหม่ไปเสียทุกครั้ง

เครื่องที่ใช้ เป็น notebook ตัวเล็กๆ พอใช้ไปสักพักพวกก็ร้อน แล้วพาลดับตัวเองก่อนที่จะพัง ไปทุกครั้ง   เริ่มตั้งแต่ May 16 จนเอาเนี่ย วันนี้ May 22 เพิ่งจะเสร็จ เพราะความที่คอยเริ่มใหม่ทุกครั้ง ที่เครื่องตัด .... อย่างน้อยๆ ก็ สาม-สี่ชั่วโมงล่ะ ถึงเริ่มใหม่ได้อีกหน ขืนไม่หยุดรอ ก็พอดี เครื่องไหม้เอาได้

สิบนาฑีครับ ใช่ครับ เครื่อง compile ไม่เกินไปกว่า 10 นาฑี ก็ดับ ซึ่งพอดับ บางทีต้องมาเสียเวลาทำ ฟิ้สกี้ ก่อนอีกถึงจะทำงานต่อไปได้

เลยตัดใจลงมาทำงานที่ single user mode เสียเลยดีกว่า ซึ่งทำให้บาง partition นั้นไม่ต้อง mount ด้วยซ้ำไป

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

ดังนั้น เมื่อลงสู่ single user mode แล้ว สิ่งที่ต้องทำเป็นลำดับแรกหลังจากได้รับ shell มาแล้ว (ซึ่งก็คือ sh) ก็ได้แก่ลำดับคำสั่งพวกนี้

# swapon  -a
# adjkerntz  -i
# mount  -a
# umount  /home

# alias  rm='rm -i'
# set  -o  vi
# set  autolist

ทั้งนี้ หมายความว่า file system ทุกตัวต้องสะอาด ไม่ต้องมาเสียเวลาทำ fsck กันอีกน่ะครับ
ขออธิบายไปทีล่ะคำสั่ง ดังนี้ครับ
  1. swapon -a อันนี้ เป็นการ mount swap file system ให้กับระบบครับ ต้องทำ แล option a  นั้นก็หมายถึงว่า หากเรากำหนดว่าเครื่องนี้มี swap file system กี่ file systems ก็เอามาให้หมดแหละ
  2. adjkerntz -i อันนี้เป็นการกำหนด CMOS clock ให้กับ kernel ครับ เรื่องเวลา สำคัญมาก
  3. mount -a อันนี้ เป็นการ mount ทุก file system ที่เครื่องมีอยู่ option a หมายถึงว่า all ครับ  แต่ในตอนที่เราอยู่ใน mode นี้ ไม่มีการ login ดังนั้น partition home จึงไม่ต้องใช้ ซึ่งเราจะ un-mount เอาทีหลัง ง่ายกว่ากัน
  4. umount /home อธิบายแล้ว ในข้อบนนั้นครับ
  5. alias rm='rm -i' คำสั่งนี้ บังคับว่า หากจะลบ ต้องถามเพื่อยืนยันไปก่อนลบ ทุกครั้งไป ป้องกันไว้ ดีกว่าเสียใจในภายหลังครับ  โดยเนื้อแท้ ต้องทำสำหรับคำสั่ง cp, mv ด้วย แต่เขียนมาเพียง ๑ คำสั่ง ก็พอ
  6. set -o vi เป็นการทำให้ทำงานด้วย keyboard ได้สะดวกขึ้น ปุ่มต่างๆ จะมีหน้าที่แบบเดียวกับที่ใช้งานบน vi เลยครับ
  7. set autolist อันนี้ก็เป็นตัวอำนวยความสะดวกอีกอย่างหนึ่งให้กับการทำงานใน mode นี้ กล่าวคือ เป็น file name completion mechanism นั่นเอง  ก็ลองนึกๆดูว่า ถ้าจะพิมพ์แค่ cd /usr/ports/editors/libreoffice แค่นี้ หากต้องกดแป้นพิมพ์ไปทุกตัว ก็ เหนื่อย ท้อ แล้วครับ  ใช้ปุ้ม tab บนแป้นพิมพ์ครับ สำหรับ เติมเต็มชื่อแฟ้มที่ยังขาดหายไป ซึ่งตามตัวอย่างที่ยกมา เราพิมพ์แค่ cd /u<tab>po<tab>edit<tab>libreoff  ในเมื่อ <tab> คือการกดปุ่ม tab ครับ

ตัวแปรสภาวะแวดล้อมทำงานพวกนี้ สามคำสั่งหลังนั้น เราสามารถกำหนดไว้ในแฟ้มได้เลยครับ ไม่ต้องมาลำบากกรอกคำสั่ง ไปเสียทุกครั้งที่ลงมาทำงานใน mode นี้  ให้นำ ๓ คำสั่งหลังที่ยกมานี้ ไปไว้ในแฟ้ม /root/.profile ครับ เขียนต่อท้ายไปจากของเดิมเลยน่ะครับ

ตัวแปรสภาพแวดล้อมการทำงานอื่นๆ ที่เห็นว่าจะอำนวยความสะดวกให้ ก็ลงไว้ในแฟ้มนี้ได้ด้วยเช่นกันครับ ส่วนใหญ่จะเป็น alias เช่น ll ซึ่งก็เท่ากับเรียกใช้ ls -FlA ครับ

อ้อ สำหรับข้อที่ 6 set -o vi นั้น มันช่วยให้ท่านเรียกคำสั่งเก่าๆขึ้นมาทำงานได้ ด้วยการกด ปุ่มลูกศรขึ้น  ครับ

Friday, April 27, 2012

Zope & python

โซล


ใน ตอนล่าสุด ที่ผ่านไปก็เพียงแต่ลากเพื่อนๆเข้าไปดูรายละเอียดต่างๆในภายในของ ZMS ที่เราได้ Add ไปแล้ว เท่านั้นเอง
ZMI หลัก ที่เพิ่ม ZMS แล้ว
จากงวดที่ผ่านมา ขอให้ท่านใช้ mouse ไปคลิ้กที่ Root Folder ตรงจอภาพด้านซ้าย ที่กินเนื้อที่ส่วนน้อยนั่นแหละ ทำซะ แล้วจะได้รูป ตามที่เห็นเนี่ยแหละ
ในรูปที่เห็น ตรงวงเขียวๆเส้นหนาๆนั้น ถ้าท่านลากสายตาไปทางขวาซึ่งกินเนื้อที่ส่วนมากของภาพ ท่านจะพบกับคำว่า Folder at /
ตรงนี้ ให้สังเกตุตรงเครื่องหมาย slash ให้ดี มันดูว่าเป็นเพียงแค่ขีดเอียงๆขีดเดียวเท่านั้น แต่ มันมีความหมายมากน่ะ เพราะมันคือ 1 object เลยทีเดียว เรื่องนี้ ท่านสมควรไปหาสาระลึกๆเอาเองได้น่ะครับ จะไม่เขียนในนี้หละ

ในรูป ที่นำมาให้ดู คงเห็นวงแดงๆ ทั้ง ๒ วงที่อยู่ใน ๒ ส่วนของภาพน่ะครับ นั่นแหละ ZMS ที่ท่านเพิ่ง Add ไปนั่นแหละครับ อันเดียวกันครับ อันเดียวกัน ทั้ง ๒ เป็น object เดียวกัน

ขอให้สังเกตุว่า ทางซ้ายมือ (ซึ่งต่อไปจะขอเรียกทางซ้ายมือ อันกินเนื้อที่ส่วนน้อยของภาพว่า ส่วนจัดการ ก็แล้วกันน่ะครับ) นั้น มีเครื่องหมายบวก แปะข้างหน้าคำ myInit อยู่ด้วย  ... ลองดูซิครับ ลองกดที่เครื่องหมายบวกนั้นดู

เห็นคำว่า content พร้อมๆกับรูปกระต้อปเล็กๆที่แปะข้างซ้ายไหมครับ เห็นไหม ที่ มะขาม วงดำๆเอาไว้นั่นแหละครับ ..... ถ้าท่านกดตรงคำว่า content ท่านก็จะเห็นของที่เคยผ่านมาแล้ว

ขอให้สังเกตุคำว่า WWW-site เอาไว้ให้ดี
ให้ท่านกดไปที่ Tab Preview น่ะ กดเลยครับ นั่นแหละ zms ที่ท่านมีอยู่
เอาล่ะ ขยับไป ส่วนจัดการ กดตรง content อีกครั้ง แล้วกดที่ WWW-site นั่นครับ

ให้เข้าไปแก้ไขตรง Short title, Title, Description ซึ่งถ้าท่านตั้งใจทำกับงาน กับปัญหาจริงๆ ท่านก็ ได้งานจริง รึอย่ากจะทดลงอไปเรื่อยๆ ก็ไม่ว่ากัน ... กด Save ครับ    แล้ว Preview ดูน่ะ

กด ส่วนจัดการ ที่ content อีกครั้งครับ แล้วเลื่อน scroll bar ทางขวามือ ลงมาด้านล่างๆ ตรง folder ทั้งหลายน่ะ พอเห็นไหมครับ จากรูป ที่เอามาให้ดู ตรงที่วงแดงๆไว้นั้นแหละ จะว่าไป ก็เป็น แถบนำทาง หรือ navigator น่ะ
แถบนำทาง
ลอง Preview ดูอีกครั้งซิ แล้วลองเข้าไปแก้ไขแต่ละส่วนดู ก็ทำอย่างเดียวกับในกรณีของ WWW-site นั่นแหละ ทำเสร็จก็ Preview ดู  มะฃาม  เคยใช้คำว่า

ข่าว  แนวคิดพื้นฐาน  กรณีศึกษา  ดาวโหลด  ความร่วมมือทางวิชาการ   บริการ

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

กับการที่ท่านทำงานด้วย ส่วนจัดการ และ เนื้อหา (เรียกกันรวมๆว่า zmi) ไปพร้อมกันเช่นนี้เอง ทำให้ zope มันพอมีเสน่ห์ในตัวอยู่บ้าง ไม่มากก็น้อยหละ

ส่วนที่เหลือ ก็คงไม่ยากแล้ว กับทุกท่านที่ฉลาดเป็นกรดกันทั้งนั้น

ถ้าท่านอยากเห็นว่า หน้าตามันเป็นอย่างไรก็ใช้ web browser เรียกไปที่ http://localhost:8080/ หรือ port หมายเลขอะไรก็ตามแต่ที่ท่านได้กำหนดไว้ใน zope configuration นั้นๆ

มะฃาม นำ zope มาใช้งานครับ นำมาใช้งาน
อ้อ อย่าลืมไปดู log ด้วยน่ะ

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

มะฃาม ฅนเดินดิน
มะไฟ ฅนเหลิงฟ้า
เอาไว้กลับจากชนบท แล้วจะมาแก้ไขเล็กๆน้อยๆ ในส่วนที่บกพร่อง

Thursday, April 26, 2012

Zope & python

ใน คราวที่แล้ว นั้น ทิ้งท้ายไว้ให้พี่ๆ เพื่อนๆ น้องๆตรง

ให้ท่านทำอย่างใดอย่างหนึ่ง ซึ่งจะให้ผลเหมือนกัน คือ
) ในหน้าที่แสดงผล ให้กดตรงข้อความเข้มๆ Zope Management Interface หรือ
) ปิด web browser แล้วเรียกใหม่อีกครั้ง คราวนี้ไปที่ http://localhost:8080/manage/
ซึ่งทั้งสองเส้นทาง จะบังคับให้ท่านกรอก ชื่อ/ระหัสลับ ที่ท่านได้ตอบไปในคราวสร้าง zope instance นั้นแล้ว ก็ให้กรอกไปให้ถูกต้อง
รูปข้างล่างนี้แสดงให้เห็นถึงหน้า web browser เมื่อท่าเรียกเข้าไปครั้งแรกด้วย http://localhost:8080/

แล้วก็มีรูป เลอะๆเลือนๆทิ้งไว้ ซึ่งหากไม่กดเข้าไปก็คงไม่รู้ว่ารูปอะไรพวกเราจากกัน ณ ตรงนั้น 
เข้าไปทำงานใน ZMI
งวดนี้ เรามาต่อกันอีก ส่วนจะไปได้เท่าไหร่ก็คงต้องขึ้นกับอุณหภูมิห้องว่าสูงหรือต่ำแค่ไหน สูงมากไป เครื่องก็ดับ
เราเริ่มกันด้วยรูป จากวิธีที่ ๒) (หรือวิธีที่ ๑) ก็เหมือนกัน) ดังนี้

และเมื่อท่านกรอกข้อมูลถูกต้องครบถ้วนแล้ว ท่านจะพบกับหน้าเอกสารดังต่อไปนี้


หน้าหลักสำหรับจัดการ zope, ZMI
ในรูปให้สังเกตุว่ามีสองส่วน ที่แยกกันในแนวตั้ง หรือ ซ้าย-ขวา ที่ส่วนซ้ายจะเล็กกว่า ส่วนขวา ซึ่งใหญ่ กินเนื้อที่เกือบทั้งหมด นั่นคือสิ่งที่พวกเรารู้ไปด้วยกันในขณะนี้ จากรูปที่นำมาให้ดูนี้

ตรงบริเวณส่วนใหญ่ของ browser ล่างสุด จะเป็นปุ่มสวิชต์ต่างๆ เช่น Rename, Cut, ... ,Select All 

ข้างบนนั้น เป็นตัวหนังสือ ที่ปิดด้านบนด้วย Type Name, Size, Last Modify 

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

ลำดับถัดมา ขอให้สังเกตุส่วนขวามือ บริเวณบนๆ ขวาสุด ท่านจะเห็น drop down menu ชื่อว่า Acce... , ที่วงแดงๆไว้นี่แหละ, นั้น

ให้นำ mouse ไปกดลงไป แล้วเลื่อนลงไปล่างสุด จนพบกับ ZMS ให้ใช้ mouse กดเลือก ตรงนี้

ZMS ที่เขาให้เรามาใช้งาน
รอสักพัก แล้วท่านจะได้รูปตามนี้

ตามรูปที่แสดงให้ดูนี้ ให้ท่านสังเกตุรายละเอียด บริเวณด้านบน ตรงส่วนขวามืออันเป็นส่วนใหญ่ของภาพ ดดยเฉพาะบรรทัดที่เขียนว่า Folder ID, Lang และ Theme ให้ดูตรงชื่อต่างๆ ที่อยู่ในบรรทัดนั้นๆ

ที่แก้ไขได้ และสมควรแก้ไขคือ Foder ID จากชื่อดูว่าแปลกๆ ให้เป็นชื่อที่สื่อความว่าเป็น ZMS object ของท่าน กับอีกหนึ่งที่สมควรแก้คือ LANG, หากท่านเข้าใจภาษาเยอรมัน ก็ไม่ต้องแก้, ให้แก้เป็น eng, English, English ทั้งสามค่า สำหรับการใช้งานด้วยภาษาอังกฤษ

ให้จำชื่อที่ท่านแก้ตรง Folder ID นั้นเอาไว้ให้ดี เอ้า สมมติว่าชื่อ myInit

ก็ตามรูปที่ได้วงๆ ชี้ๆไว้ให้พอได้แนวทางว่าจักต้องแก้ตรงไหน อย่างไร

จากนั้น ให้เลื่อน scroll bar ลงมาล่างสุด หรือหากจอภาพของท่านสามารถแสดงผลได้หมด ก็ให้เหลือบสายตาลงมายังด้านล่างของจอภาพแสดงผลของ browser

คงเห็นปุ่ม Add น่ะครับ อย่าเพ่อกดลงไป ให้ดูจอภาพด้านซ้ายมือ อันกินบริเวณส่วนน้อยของจอภาพนั้นเอาไว้ ก็คงมีพวก Root folder, Control_panel, acl_user, temp_folder ประดานี้ ดูเอาไว้ก่อนให้พอติดตาครับ



จากนั้นให้ท่านกดตรงปุ่ม Add รูปอะไรก็ไม่รู้ แต่ ถ้าได้อย่างนี้แล้ว นั่นแหละ มันมาแล้ว
นี่เป็นรูปของ template ที่เขาสร้างไว้ให้เราพร้อมใช้งาน


ในรูปถัดมา วงเขียวๆ ด้านซ้ายมือนั่นแหละ ตรงที่กินที่ส่วนน้อยของจอภาพนั่นแหละ ให้สังเกตุว่า จะมีชื่อใหม่เพิ่มเข้ามา ก็ชื่อของ Folder ID ที่ท่านกำหนดขึ้นมาเอง

ทีนี้ พอนึกออกรึยัง ที่เคยบอกว่า ให้สังเกตุไว้ พอให้ติดตาเอาไว้ก่อนน่ะ



เอาล่ะ มองดูอีกด้านที่กินที่ส่วนใหญ่ของจอภาพนั้น ก็ ที่วงน้ำเงินไว้ตรงบริเวณคำ www-site นั้น กับอีกวงแดงๆ ข้างล่าง ตรงกับคำ Image นั่นแหละ

รูปวงแดงของ Image อาจจะไม่ชัดเจนนัก เลยเพิ่มให้อีก ๑ รูป พร้อมกับเพิ่มสิ่งที่อยากชี้ให้เห็นด้วยวงสีเขียวมาให้ดูล่ะกัน
ตรงวงเขียวนั่นแหละ คือ ภาพ ที่ท่านสามารถแทรกลงไปได้ และข้อความบรรยายใต้ภาพก็เช่นกัน ท่านสามารถ เพิ่ม เปลี่ยน ปรับขยับตำแหน่ง เอาได้ ตามใจชอบ และหากสังเกตุให้ดี ในวงเขียวนั้น ตรงมุมขวา จะมีสัญญลักษณะคล้ายแว่นขยายอยู่ แสดงว่า ภาพที่นำเสนอนี้ มี ๒ แบบ คืออย่างที่กำลังชม กำลังเห็นนี้ (ซึ่งถือว่าเป็น รูปย่อ) กับ แบบขยาย เมื่อท่านกดไปที่ภาพนี้

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


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

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

ทีนี้ หากจะกลับมาหน้าเดิม ก็กดตรงคำว่า Back to WWW-site นั่นแหละ

เอากันเท่านี้ก่อนน่ะครับ

View My Stats