หลักการทำงานในระบบปฏิบัติการคล้าย UNIX โดยใช้ Linux เป็นตัวอย่าง สร้างกระบวนการน้ำหนักเบาของคุณเอง

หนึ่งในช่วงเวลาที่น่ารำคาญที่สุดเมื่อต้องย้ายจากสิ่งแวดล้อมไปสู่ ใช้ระบบปฏิบัติการ Windowsสำหรับการใช้งาน บรรทัดคำสั่ง– สูญเสียการทำงานหลายอย่างพร้อมกันอย่างง่ายดาย แม้แต่บน Linux หากคุณใช้ระบบ X Window คุณสามารถใช้เมาส์เพื่อคลิกได้ โปรแกรมใหม่และเปิดมัน อย่างไรก็ตาม บนบรรทัดคำสั่ง คุณค่อนข้างติดอยู่กับการทำงานแบบเดี่ยวๆ ในบทความนี้เราจะแสดงให้คุณเห็น วิธีมัลติทาสก์ใน Linux โดยใช้บรรทัดคำสั่ง

การจัดการกระบวนการเบื้องหลังและลำดับความสำคัญ

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

ไม่มีการป้องกัน

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

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

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

เนื่องจากข้อบกพร่องเหล่านี้ จึงเกิดปัญหาใหญ่ในการจัดการกระบวนการเบื้องหลังและเบื้องหน้า การตัดสินใจที่ดีที่สุด– ใช้ยูทิลิตีบรรทัดคำสั่ง “หน้าจอ” ดังที่แสดงด้านล่าง

แต่ก่อนอื่น คุณจะเปิดเซสชัน SSH ใหม่

อย่าลืมว่าคุณเพิ่งเปิดเซสชัน SSH ใหม่

การเปิดเซสชันใหม่ตลอดเวลาอาจไม่สะดวก และนั่นคือเวลาที่คุณต้องการ "หน้าจอ"

คุณประโยชน์ หน้าจอช่วยให้คุณสร้างเวิร์กโฟลว์หลายรายการที่เปิดพร้อมกันซึ่งเป็นอะนาล็อกที่ใกล้เคียงที่สุดกับ "windows" โดยค่าเริ่มต้นจะมีอยู่ในที่เก็บ Linux ทั่วไป ติดตั้งบน CentOS/RHEL โดยใช้คำสั่งต่อไปนี้:

หน้าจอการติดตั้ง Sudo yum

การเปิดหน้าจอใหม่

ตอนนี้เริ่มเซสชันของคุณโดยพิมพ์ "หน้าจอ"

สิ่งนี้จะสร้าง หน้าต่างว่างเปล่าภายในเซสชัน SSH ที่มีอยู่ และระบุหมายเลขที่แสดงในส่วนหัวดังนี้:

หน้าจอของเราที่นี่มีหมายเลข "0" ดังที่แสดง ในภาพหน้าจอนี้ เราใช้คำสั่งจำลอง “อ่าน” เพื่อบล็อกเทอร์มินัลและรอให้อินพุต ตอนนี้สมมติว่าเราต้องการทำอย่างอื่นในขณะที่เรารอ

เพื่อเปิด หน้าจอใหม่และทำอย่างอื่นเราพิมพ์:

Ctrl+ก

“ctrl+a” เป็นคีย์ผสมเริ่มต้นสำหรับควบคุมหน้าจอในโปรแกรมหน้าจอ สิ่งที่คุณพิมพ์หลังจากนั้นจะกำหนดการดำเนินการ ตัวอย่างเช่น:

  • Ctrl+ค – เปิดใช้งานหน้าจอใหม่
  • Ctrl+ก [ตัวเลข]– ข้ามไปยังหมายเลขหน้าจอเฉพาะ
  • Ctrl+ก – เคปิดหน้าจอปัจจุบัน
  • ctrl+a n – ไปที่หน้าจอ n
  • Ctrl+a “- แสดงหน้าจอที่ใช้งานอยู่ทั้งหมดในเซสชัน

ถ้าเรากด “ctrl+ac” เราจะได้หน้าจอใหม่พร้อมตัวเลขใหม่

คุณสามารถใช้ปุ่มเคอร์เซอร์เพื่อเลื่อนดูรายการและไปยังหน้าจอที่คุณต้องการได้
หน้าจอจะอยู่ใกล้ "windows" มากที่สุด เช่นเดียวกับระบบในคำสั่ง สตริงลินุกซ์. แน่นอนว่ามันไม่ง่ายเหมือนการคลิกเมาส์ แต่ระบบย่อยกราฟิกนั้นใช้ทรัพยากรมาก ด้วยหน้าจอ คุณจะได้รับฟังก์ชันการทำงานที่เกือบจะเหมือนเดิมและเปิดใช้งานมัลติทาสก์ได้เต็มรูปแบบ!

กระบวนการใน UNIX

ใน UNIX วิธีการหลักในการจัดองค์กรและหน่วยของมัลติทาสก์คือกระบวนการ ระบบปฏิบัติการจะจัดการอิมเมจกระบวนการ ซึ่งแสดงถึงโค้ดโปรแกรม รวมถึงส่วนข้อมูลกระบวนการที่กำหนดสภาพแวดล้อมการดำเนินการ

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

หน่วยความจำเสมือนถูกนำมาใช้และบำรุงรักษาโดยอัตโนมัติโดยเคอร์เนล UNIX

ประเภทกระบวนการ

กระบวนการในระบบปฏิบัติการ UNIX มีสามประเภท: เป็นระบบ, กระบวนการภูตและ กระบวนการสมัคร.

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

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

ปีศาจ- กระบวนการเหล่านี้เป็นกระบวนการที่ไม่โต้ตอบซึ่งเปิดตัวในลักษณะปกติ - โดยการโหลดโปรแกรมที่เกี่ยวข้องลงในหน่วยความจำและดำเนินการในเบื้องหลัง โดยทั่วไป daemons จะถูกเรียกใช้ในระหว่างการเตรียมใช้งานระบบ แต่หลังจากที่เคอร์เนลถูกเตรียมใช้งานแล้ว และรับประกันการทำงานของระบบย่อย UNIX ต่างๆ: ระบบการเข้าถึงเทอร์มินัล ระบบการพิมพ์ บริการเครือข่ายฯลฯ ปีศาจไม่เกี่ยวข้องกับผู้ใช้ใดๆ โดยส่วนใหญ่ daemons จะรอกระบวนการใดกระบวนการหนึ่งเพื่อขอ บริการเฉพาะ.



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

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

คุณสมบัติกระบวนการ

กระบวนการ UNIX มีคุณลักษณะจำนวนหนึ่งที่อนุญาต ระบบปฏิบัติการจัดการงานของมัน คุณสมบัติหลัก:

· รหัสกระบวนการ (PID)ทำให้เคอร์เนลของระบบสามารถแยกแยะระหว่างกระบวนการต่างๆ ได้ เมื่อสร้างขึ้น กระบวนการใหม่เคอร์เนลจะกำหนดตัวระบุอิสระถัดไป (เช่น ไม่เกี่ยวข้องกับกระบวนการใดๆ) การกำหนดตัวระบุมักจะเกิดขึ้นตามลำดับจากน้อยไปมาก เช่น ID ของกระบวนการใหม่มากกว่า ID ของกระบวนการที่สร้างขึ้นก่อนหน้านั้น หาก ID ถึงค่าสูงสุด (ปกติคือ 65737) กระบวนการถัดไปจะได้รับ PID ฟรีขั้นต่ำและวงจรจะเกิดซ้ำ เมื่อกระบวนการออก เคอร์เนลจะปล่อยตัวระบุที่ใช้อยู่

· รหัสกระบวนการหลัก (PPID)– ตัวระบุกระบวนการที่ทำให้เกิดกระบวนการนี้ กระบวนการทั้งหมดในระบบยกเว้น กระบวนการของระบบและกระบวนการ ในนั้นซึ่งเป็นต้นกำเนิดของกระบวนการที่เหลือ ถูกสร้างขึ้นโดยกระบวนการใดกระบวนการหนึ่งที่มีอยู่หรือที่มีอยู่ก่อนหน้านี้

· การแก้ไขลำดับความสำคัญ (NI)– ลำดับความสำคัญสัมพัทธ์ของกระบวนการ คำนึงถึงโดยผู้กำหนดตารางเวลาเมื่อพิจารณาลำดับการเริ่มต้น การกระจายทรัพยากรตัวประมวลผลตามจริงถูกกำหนดโดยลำดับความสำคัญในการดำเนินการ (แอตทริบิวต์ ปรีดี) ขึ้นอยู่กับปัจจัยหลายประการ โดยเฉพาะอย่างยิ่งลำดับความสำคัญสัมพัทธ์ที่กำหนด ระบบจะไม่เปลี่ยนลำดับความสำคัญสัมพัทธ์ตลอดอายุของกระบวนการ แม้ว่าผู้ใช้หรือผู้ดูแลระบบสามารถเปลี่ยนแปลงได้เมื่อเริ่มกระบวนการโดยใช้คำสั่ง ดี. ช่วงของค่าการเพิ่มลำดับความสำคัญในระบบส่วนใหญ่คือ -20 ถึง 20 หากไม่มีการระบุการเพิ่มขึ้นจะใช้ค่าเริ่มต้นที่ 10 การเพิ่มขึ้นที่เป็นบวกหมายถึงการลดลงของลำดับความสำคัญปัจจุบัน ผู้ใช้ทั่วไปสามารถตั้งค่าการเพิ่มขึ้นที่เป็นบวกเท่านั้น ดังนั้นจึงลดเฉพาะลำดับความสำคัญเท่านั้น ผู้ใช้ รากสามารถตั้งค่าการเพิ่มขึ้นเชิงลบซึ่งจะเพิ่มลำดับความสำคัญของกระบวนการและด้วยเหตุนี้จึงมีส่วนทำให้กระบวนการสูงขึ้น ทำงานเร็ว. ไม่เหมือนกับลำดับความสำคัญแบบสัมพัทธ์ ลำดับความสำคัญในการดำเนินการของกระบวนการจะถูกเปลี่ยนแปลงแบบไดนามิกโดยตัวกำหนดตารางเวลา

· เทอร์มินัลไลน์ (TTY)– เทอร์มินัลหรือเทอร์มินัลเทียมที่เกี่ยวข้องกับกระบวนการ เทอร์มินัลนี้เชื่อมโยงกับมาตรฐาน ลำธาร: ป้อนข้อมูล, วันหยุดและ การไหลของข้อความเกี่ยวกับข้อผิดพลาด สตรีม ( ช่องรายการ) เป็น วิธีการมาตรฐานการสื่อสารระหว่างกระบวนการใน UNIX OS กระบวนการ Daemon ไม่เกี่ยวข้องกับเทอร์มินัล

· ตัวระบุผู้ใช้จริง (UID) และมีผลบังคับใช้ (EUID). ID ผู้ใช้ที่แท้จริงของกระบวนการที่กำหนดคือ ID ของผู้ใช้ที่เริ่มต้นกระบวนการ ตัวระบุที่มีประสิทธิภาพใช้เพื่อกำหนดสิทธิ์ในการเข้าถึงทรัพยากรระบบของกระบวนการ (โดยหลักแล้วคือทรัพยากร ระบบไฟล์). โดยปกติแล้วตัวระบุที่แท้จริงและมีประสิทธิภาพจะเหมือนกันนั่นคือ กระบวนการนี้มีสิทธิ์ในระบบเช่นเดียวกับผู้ใช้ที่เปิดใช้งาน อย่างไรก็ตาม เป็นไปได้ที่จะให้สิทธิ์แก่กระบวนการมากกว่าผู้ใช้โดยการตั้งค่า ซูดบิตเมื่อตัวระบุที่มีประสิทธิภาพถูกตั้งค่าเป็นตัวระบุของเจ้าของไฟล์ปฏิบัติการ (เช่น ผู้ใช้ ราก).

· ตัวระบุกลุ่มจริง (GID) และประสิทธิผล (EGID) ID กลุ่มจริงจะเท่ากับ ID กลุ่มหลักหรือปัจจุบันของผู้ใช้ที่เริ่มกระบวนการ ตัวระบุที่มีประสิทธิภาพใช้เพื่อกำหนดสิทธิ์การเข้าถึงทรัพยากรระบบในนามของกลุ่ม โดยปกติแล้ว ID กลุ่มที่มีผลจะเหมือนกับรหัสจริง แต่หากไฟล์ปฏิบัติการถูกตั้งค่าเป็น SGID บิตไฟล์ดังกล่าวจะถูกดำเนินการด้วย ID กลุ่มเจ้าของที่มีผล

งานห้องปฏิบัติการหมายเลข 3

การเขียนโปรแกรมแบบมัลติทาสกิ้งในลินุกซ์

1. เป้าหมายของงาน:ทำความคุ้นเคยกับคอมไพลเลอร์ gcc เทคนิคการดีบักโปรแกรม และฟังก์ชันสำหรับการทำงานกับกระบวนการ

2. ข้อมูลทางทฤษฎีโดยย่อ

ชุดสวิตช์คอมไพเลอร์ gcc ขั้นต่ำคือ - ผนัง (แสดงข้อผิดพลาดและคำเตือนทั้งหมด) และ - o (ไฟล์เอาต์พุต):

gcc - ผนัง - o print_pid print_pid ค

ทีมงานจะสร้าง ไฟล์ปฏิบัติการ print_pid.

ไลบรารีมาตรฐาน C (libc ใช้งานใน Linux ในรูปแบบ glibc) ใช้ประโยชน์จากความสามารถมัลติทาสก์ของ Unix System V (ต่อไปนี้จะเรียกว่า SysV) ใน libc ประเภท pid_t ถูกกำหนดให้เป็นจำนวนเต็มที่สามารถบรรจุ pid ได้ ฟังก์ชันที่รายงาน pid ของกระบวนการปัจจุบันมีต้นแบบของ pid_t getpid(void) และถูกกำหนดพร้อมกับ pid_t ใน unistd h และ sys/types ชม).

หากต้องการสร้างกระบวนการใหม่ ให้ใช้ฟังก์ชัน fork:

pid_t ส้อม (เป็นโมฆะ)

ด้วยการแทรกความล่าช้าของความยาวสุ่มโดยใช้ฟังก์ชัน sleep และ rand คุณจะเห็นผลของการทำงานหลายอย่างพร้อมกันได้ชัดเจนยิ่งขึ้น:

สิ่งนี้จะทำให้โปรแกรม "สลีป" เป็นเวลาสุ่มจำนวนวินาที: ตั้งแต่ 0 ถึง 3

หากต้องการเรียกใช้ฟังก์ชันเป็นกระบวนการลูก เพียงเรียกมันหลังจากการแตกแขนง:

// ถ้ากระบวนการลูกกำลังทำงานอยู่ ให้เรียกใช้ฟังก์ชัน

pid=กระบวนการ(หาเรื่อง);

//ออกจากกระบวนการ

บ่อยครั้งที่จำเป็นต้องรันโปรแกรมอื่นเป็นกระบวนการลูก เมื่อต้องการทำเช่นนี้ ให้ใช้ฟังก์ชันตระกูล exec:

// หากกระบวนการลูกกำลังทำงานอยู่ ให้เรียกใช้โปรแกรม


ถ้า (execl("./file","file",arg, NULL)<0) {

printf("ข้อผิดพลาดขณะเริ่มกระบวนการ\n");

else printf("กระบวนการเริ่มต้นแล้ว (pid=%d)\n", pid);

//ออกจากกระบวนการ

บ่อยครั้งที่กระบวนการหลักจำเป็นต้องแลกเปลี่ยนข้อมูลกับลูกๆ ของมัน หรืออย่างน้อยก็ซิงโครไนซ์กับพวกเขา เพื่อดำเนินการในเวลาที่เหมาะสม วิธีหนึ่งในการซิงโครไนซ์กระบวนการคือการใช้ฟังก์ชัน wait และ waitpid:

#รวม

#รวม

pid_t wait(int *status) - ระงับการดำเนินการของกระบวนการปัจจุบันจนกว่ากระบวนการย่อยใด ๆ จะยุติลง

pid_t waitpid (pid_t pid, int *status, int options) - ระงับการดำเนินการของกระบวนการปัจจุบันจนกว่ากระบวนการที่ระบุจะเสร็จสมบูรณ์หรือตรวจสอบความสมบูรณ์ของกระบวนการที่ระบุ

หากคุณต้องการค้นหาสถานะของกระบวนการลูกเมื่อสิ้นสุดและค่าที่ส่งคืน ให้ใช้มาโคร WEXITSTATUS โดยส่งสถานะของกระบวนการลูกเป็นพารามิเตอร์

สถานะ=waitpid(pid,&สถานะ, WNOHANG);

ถ้า (pid == สถานะ) (

printf("PID: %d, ผลลัพธ์ = %d\n", pid, WEXITSTATUS(สถานะ)); )

หากต้องการเปลี่ยนลำดับความสำคัญของกระบวนการที่เกิด จะใช้ setpriority และฟังก์ชัน ลำดับความสำคัญถูกกำหนดในช่วงตั้งแต่ -20 (สูงสุด) ถึง 20 (ต่ำสุด) ค่าปกติคือ 0 โปรดทราบว่ามีเพียงผู้ใช้ขั้นสูงเท่านั้นที่สามารถเพิ่มลำดับความสำคัญเหนือปกติได้!

#รวม

#รวม

กระบวนการ int (int i) (

setpriority(PRIO_PROCESS, getpid(),ผม);

printf("กระบวนการ %d ThreadID: %d ทำงานกับลำดับความสำคัญ %d\n",i, getpid(),getpriority(PRIO_PROCESS, getpid()));

กลับ(getpriority(PRIO_PROCESS, getpid()));

หากต้องการฆ่ากระบวนการ ให้ใช้ฟังก์ชัน kill:

#รวม

#รวม

int ฆ่า (pid_t pid, int sig);

หาก pid > 0 แสดงว่า PID ของกระบวนการที่ส่งสัญญาณไป ถ้า pid = 0 สัญญาณจะถูกส่งไปยังกระบวนการทั้งหมดของกลุ่มที่มีกระบวนการปัจจุบันอยู่

sig - ประเภทสัญญาณ สัญญาณบางประเภทใน Linux:

SIGKILL สัญญาณนี้ทำให้กระบวนการยุติทันที กระบวนการนี้ไม่สามารถละเลยสัญญาณนี้ได้

SIGTERM สัญญาณนี้เป็นการร้องขอให้ยุติกระบวนการ

SIGCHLD ระบบจะส่งสัญญาณนี้ไปยังกระบวนการเมื่อกระบวนการลูกใดกระบวนการหนึ่งยุติลง ตัวอย่าง:

ถ้า (pid[i] == สถานะ) (

printf("ThreadID: %d เสร็จสิ้นด้วยสถานะ %d\n", pid[i], WEXITSTATUS(สถานะ));

อย่างอื่นฆ่า (pid [i], SIGKILL);

3. คำแนะนำที่เป็นระบบ

3.1. เพื่อทำความคุ้นเคยกับตัวเลือกคอมไพเลอร์ gcc และคำอธิบายฟังก์ชันภาษา C ให้ใช้คำสั่ง man และ info

3.2. ในการดีบักโปรแกรมจะสะดวกในการใช้โปรแกรมแก้ไขในตัวของตัวจัดการไฟล์ ผู้บัญชาการเที่ยงคืน(MC) ซึ่งเน้นโครงสร้างภาษาต่างๆ ด้วยสี และระบุตำแหน่งของเคอร์เซอร์ในไฟล์ (แถว คอลัมน์) ในบรรทัดบนสุดของหน้าจอ

3.3. ตัวจัดการไฟล์ Midnight Commander มีบัฟเฟอร์คำสั่งที่สามารถเรียกได้โดยใช้แป้นพิมพ์ลัด - H ซึ่งสามารถเคลื่อนย้ายได้โดยใช้ลูกศรเคอร์เซอร์ (ขึ้นและลง) หากต้องการแทรกคำสั่งจากบัฟเฟอร์ลงในบรรทัดคำสั่ง ให้ใช้คีย์ เพื่อแก้ไขคำสั่งจากบัฟเฟอร์ - คีย์<- и ->, และ .


3.4. โปรดจำไว้ว่าไดเร็กทอรีปัจจุบันไม่มีอยู่ในพาธ ดังนั้นคุณต้องรันโปรแกรมเป็น "./print_pid" จากบรรทัดคำสั่ง ใน MC เพียงวางเมาส์เหนือไฟล์แล้วคลิก .

3.5. หากต้องการดูผลลัพธ์ของการทำงานของโปรแกรม ให้ใช้แป้นพิมพ์ลัด - O. พวกเขายังทำงานในโหมดแก้ไขไฟล์ด้วย

3.6. หากต้องการบันทึกผลลัพธ์การทำงานของโปรแกรม ขอแนะนำให้ใช้การเปลี่ยนเส้นทางเอาต์พุตจากคอนโซลไปยังไฟล์: ./test > result ข้อความ

3.7. เพื่อเข้าถึงไฟล์ที่สร้างขึ้นบน เซิร์ฟเวอร์ลินุกซ์ให้ใช้โปรโตคอล ftp ซึ่งเป็นโปรแกรมไคลเอนต์ซึ่งมีอยู่ใน Windows 2000 และมีอยู่แล้วในตัว ตัวจัดการไฟล์ไกล. โดยที่ บัญชีและรหัสผ่านจะเหมือนกับเมื่อเชื่อมต่อผ่าน ssh

4.1. ทำความคุ้นเคยกับตัวเลือกคอมไพเลอร์ gcc และวิธีการดีบักโปรแกรม

4.2. สำหรับงานประเภทต่างๆ จากงานในห้องปฏิบัติการหมายเลข 1 ให้เขียนและดีบักโปรแกรมที่ใช้กระบวนการที่สร้างขึ้น

4.3. สำหรับตัวเลือกงานจาก งานห้องปฏิบัติการหมายเลข 1 เขียนและดีบักโปรแกรมที่ใช้กระบวนการพาเรนต์ที่เรียกและตรวจสอบสถานะของกระบวนการลูก - โปรแกรม (รอให้พวกมันดำเนินการหรือทำลายพวกมัน ขึ้นอยู่กับตัวเลือก)

4.4. สำหรับตัวแปรของงานจากงานในห้องปฏิบัติการหมายเลข 1 ให้เขียนและดีบักโปรแกรมที่ใช้กระบวนการหลักที่เรียกใช้และติดตามสถานะของกระบวนการย่อย - ฟังก์ชัน (รอให้เสร็จสิ้นหรือทำลายกระบวนการ ขึ้นอยู่กับตัวแปร)

5. ตัวเลือกสำหรับงานดูตัวเลือกสำหรับงานจากงานในห้องปฏิบัติการหมายเลข 1

6. เนื้อหาของรายงาน

6.1. เป้าหมายของการทำงาน

6.2. ตัวเลือกงาน

6.3. รายการโปรแกรม

6.4. โปรโตคอลการดำเนินการโปรแกรม

7. คำถามควบคุม

7.1. คุณสมบัติของการคอมไพล์และรันโปรแกรม C บน Linux

7.2. pid คืออะไร จะตรวจสอบได้อย่างไรในระบบปฏิบัติการและโปรแกรม?

7.3. ฟังก์ชัน Fork - วัตถุประสงค์ แอปพลิเคชัน ค่าส่งคืน

7.4. จะรันฟังก์ชันในกระบวนการที่เกิดได้อย่างไร? โปรแกรม?

7.5. วิธีซิงโครไนซ์กระบวนการพาเรนต์และลูก

7.6. จะทราบสถานะของกระบวนการที่เกิดเมื่อสิ้นสุดและค่าที่ส่งคืนได้อย่างไร

7.7. จะจัดการลำดับความสำคัญของกระบวนการได้อย่างไร?

7.8. จะฆ่ากระบวนการในระบบปฏิบัติการและโปรแกรมได้อย่างไร?

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

บทความในชุด:

  1. มัลติทาสกิ้งในเคอร์เนล Linux: คิวงาน

คิวงาน

คิวงาน- สิ่งเหล่านี้เป็นเอนทิตีที่ซับซ้อนและมีน้ำหนักมากกว่างาน ฉันจะไม่พยายามอธิบายความซับซ้อนทั้งหมดของการใช้งานที่นี่ แต่ฉันหวังว่าฉันจะวิเคราะห์สิ่งที่สำคัญที่สุดโดยละเอียดไม่มากก็น้อย
Workqueues เช่นเดียวกับ Tasklet ทำหน้าที่สำหรับการประมวลผลการขัดจังหวะแบบเลื่อนออกไป (แม้ว่าจะสามารถใช้เพื่อวัตถุประสงค์อื่นได้) แต่ต่างจาก Tasklet ตรงที่คิวงานจะถูกดำเนินการในบริบทของกระบวนการเคอร์เนล ดังนั้น คิวงานจึงไม่จำเป็นต้องเป็นแบบอะตอมมิกและสามารถใช้โหมดสลีปได้ () ฟังก์ชั่น เครื่องมือการซิงโครไนซ์ต่างๆ ฯลฯ

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

มีหน่วยงานหลายแห่งที่เกี่ยวข้องกับสสารมืดนี้
ประการแรก รายการงาน(เพียงทำงานสั้น ๆ ) คือโครงสร้างที่อธิบายฟังก์ชัน (เช่น Interrupt Handler) ที่เราต้องการกำหนดเวลา ถือได้ว่าเป็นอะนาล็อกของโครงสร้าง Tasklet เมื่อกำหนดเวลา Tasklets จะถูกเพิ่มเข้าไปในคิวที่ซ่อนไม่ให้ผู้ใช้เห็น แต่ตอนนี้เราจำเป็นต้องใช้คิวพิเศษ - คิวงาน.
งานต่างๆ จะถูกกวาดล้างโดยฟังก์ชันตัวกำหนดเวลา และคิวงานจะถูกประมวลผลโดยเธรดพิเศษที่เรียกว่าผู้ปฏิบัติงาน
คนงานให้การดำเนินการงานแบบอะซิงโครนัสจากคิวงาน แม้ว่าพวกเขาจะเรียกงานตามลำดับการหมุนเวียน แต่ในกรณีทั่วไปไม่มีคำถามเกี่ยวกับการดำเนินการที่เข้มงวดและต่อเนื่อง: หลังจากทั้งหมดการจองการนอนหลับการรอ ฯลฯ เกิดขึ้นที่นี่

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

เพื่อสรุปความสามารถหลักของกลไกคิวงาน ฉันขอแนะนำให้สำรวจ API

เกี่ยวกับคิวและการสร้าง

alloc_workqueue (fmt, แฟล็ก, max_active, args...)
พารามิเตอร์ fmt และ args คือรูปแบบ printf สำหรับชื่อและอาร์กิวเมนต์ พารามิเตอร์ max_activate รับผิดชอบจำนวนงานสูงสุดที่สามารถดำเนินการจากคิวนี้สามารถดำเนินการแบบขนานบน CPU ตัวเดียว
สามารถสร้างคิวได้ด้วยแฟล็กต่อไปนี้:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_เข้มข้น
  • WQ_ฟรีซเอเบิล
  • WQ_MEM_เรียกคืน
ควรให้ความสนใจเป็นพิเศษกับธง WQ_UNBOUND. ขึ้นอยู่กับการมีอยู่ของแฟล็กนี้ คิวจะถูกแบ่งออกเป็นแบบผูกและไม่มีการเชื่อมต่อ
ในคิวที่เชื่อมโยงกันเมื่อเพิ่ม งานจะถูกผูกไว้กับ CPU ปัจจุบัน นั่นคือในคิวดังกล่าว งานจะถูกดำเนินการบนคอร์ที่กำหนดเวลาไว้ ในเรื่องนี้ คิวที่ถูกผูกไว้จะมีลักษณะคล้ายกับงาน
ในคิวที่ไม่ได้แนบงานสามารถดำเนินการบนคอร์ใดก็ได้

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

โดยการเปรียบเทียบกับงาน งานสามารถได้รับมอบหมายลำดับความสำคัญในการดำเนินการ ปกติหรือสูง ลำดับความสำคัญเป็นเรื่องธรรมดาสำหรับทั้งคิว ตามค่าเริ่มต้น คิวจะมีลำดับความสำคัญตามปกติ และหากคุณตั้งค่าสถานะไว้ WQ_HIGHPRIดังนั้นจึงสูงตามไปด้วย

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

ธง WQ_ฟรีซเอเบิลและ WQ_MEM_เรียกคืนมีความเฉพาะเจาะจงและอยู่นอกเหนือขอบเขตของหัวข้อ ดังนั้นเราจะไม่เจาะลึกรายละเอียดเหล่านั้น

บางครั้งมันก็สมเหตุสมผลที่จะไม่สร้างคิวของคุณเอง แต่ควรใช้คิวทั่วไป สิ่งสำคัญ:

  • system_wq - คิวที่ถูกผูกไว้เพื่อการทำงานที่รวดเร็ว
  • system_long_wq - คิวที่ถูกผูกไว้สำหรับงานที่คาดว่าจะใช้เวลานานในการดำเนินการ
  • system_unbound_wq - คิวที่ไม่ได้ผูกไว้

เกี่ยวกับงานและการวางแผนของพวกเขา

ตอนนี้เรามาจัดการกับผลงานกันดีกว่า อันดับแรก มาดูที่การเริ่มต้น การประกาศ และมาโครการเตรียมการ:
ประกาศ(_DELAYED)_WORK(ชื่อ, โมฆะ (*ฟังก์ชั่น)(struct work_struct *งาน)); /* ณ เวลาคอมไพล์ */ INIT(_DELAYED)_WORK(_work, _func); /* ระหว่างดำเนินการ */ PREPARE(_DELAYED)_WORK(_work, _func); /* เพื่อเปลี่ยนฟังก์ชั่นที่กำลังดำเนินการ */
งานจะถูกเพิ่มเข้าไปในคิวโดยใช้ฟังก์ชัน:
บูลคิว_งาน (struct workqueue_struct *wq, struct work_struct *งาน); bool Queue_delayed_work (struct workqueue_struct *wq, structล่าช้า_work *dwork, ล่าช้านานที่ไม่ได้ลงนาม); /* งานจะถูกเพิ่มเข้าไปในคิวหลังจากหมดเวลาหน่วงแล้วเท่านั้น */
นี่เป็นสิ่งที่ควรค่าแก่การดูรายละเอียดเพิ่มเติม แม้ว่าเราจะระบุคิวเป็นพารามิเตอร์ แต่ในความเป็นจริง งานไม่ได้อยู่ในคิวงานอย่างที่คิด แต่อยู่ในเอนทิตีที่แตกต่างไปจากเดิมอย่างสิ้นเชิง - ในรายการคิวของโครงสร้าง worker_pool โครงสร้าง คนงาน_พูลอันที่จริงแล้ว เป็นเอนทิตีที่สำคัญที่สุดในการจัดกลไกคิวงาน แม้ว่าสำหรับผู้ใช้จะยังคงอยู่เบื้องหลังก็ตาม คนงานทำงานอยู่กับพวกเขา และข้อมูลพื้นฐานทั้งหมดอยู่ในนั้น

ทีนี้มาดูกันว่ามีพูลอะไรบ้างในระบบ
เริ่มต้นด้วยพูลสำหรับคิวที่ถูกผูกไว้ (ในภาพ) สำหรับ CPU แต่ละตัว พูลผู้ปฏิบัติงานสองพูลจะถูกจัดสรรแบบคงที่: พูลหนึ่งสำหรับงานที่มีลำดับความสำคัญสูง และอีกพูลหนึ่งสำหรับงานที่มีลำดับความสำคัญปกติ นั่นคือถ้าเรามีสี่คอร์ก็จะมีพูลที่แนบมาเพียงแปดพูลแม้ว่าคิวงานจะมากเท่าที่ต้องการก็ตาม
เมื่อเราสร้างคิวงานจะมีการจัดสรรบริการให้กับ CPU แต่ละตัว pool_workqueue(pwq). แต่ละ pool_workqueue ดังกล่าวเชื่อมโยงกับพูลผู้ปฏิบัติงาน ซึ่งได้รับการจัดสรรบน CPU เดียวกัน และสอดคล้องกับลำดับความสำคัญของประเภทคิว คิวงานจะโต้ตอบกับพูลผู้ปฏิบัติงานผ่านสิ่งเหล่านี้
ผู้ปฏิบัติงานดำเนินงานจากกลุ่มผู้ปฏิบัติงานโดยไม่เลือกปฏิบัติ โดยไม่แยกแยะว่าคิวงานใดเดิมเป็นของ

สำหรับคิวที่ไม่ได้แนบ พูลผู้ปฏิบัติงานจะถูกจัดสรรแบบไดนามิก คิวทั้งหมดสามารถแบ่งออกเป็นคลาสที่เทียบเท่าตามพารามิเตอร์ และพูลผู้ปฏิบัติงานของตัวเองจะถูกสร้างขึ้นสำหรับแต่ละคลาสดังกล่าว เข้าถึงได้โดยใช้ตารางแฮชพิเศษ โดยที่คีย์คือชุดของพารามิเตอร์ และค่าตามลำดับคือพูลผู้ปฏิบัติงาน
ในความเป็นจริงสำหรับคิวที่ไม่ได้ผูกไว้ทุกอย่างจะซับซ้อนกว่าเล็กน้อย: ถ้าสำหรับคิวที่ถูกผูกไว้ pwq และคิวถูกสร้างขึ้นสำหรับ CPU แต่ละตัว ที่นี่พวกเขาจะถูกสร้างขึ้นสำหรับแต่ละโหนด NUMA แต่นี่เป็นการปรับให้เหมาะสมเพิ่มเติมที่เราจะไม่พิจารณาในรายละเอียด

สิ่งเล็กๆ น้อยๆ ทุกประเภท

ฉันจะให้ฟังก์ชันบางอย่างจาก API เพื่อทำให้รูปภาพสมบูรณ์ แต่ฉันจะไม่พูดถึงรายละเอียดเหล่านี้:
/* บังคับให้เสร็จสิ้น */ bool flush_work(struct work_struct *work); บูลflush_delayed_work(structล่าช้า_work *dwork); /* ยกเลิกการทำงาน */ bool cancel_work_sync(struct work_struct *work); บูล cancel_delayed_work (struct ล่าช้า_งาน *dwork); บูล cancel_delayed_work_sync (struct Delayed_work *dwork); /* ลบคิว */ void destroy_workqueue(struct workqueue_struct *wq);

คนงานทำงานอย่างไร

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

ผู้ปฏิบัติงานสามารถอยู่ในสถานะตรรกะสามสถานะตามเงื่อนไขได้: พวกเขาสามารถไม่ได้ใช้งาน กำลังดำเนินการ หรือจัดการได้
คนทำงานก็ได้ ยืนเฉยๆและไม่ทำอะไรเลย ตัวอย่างเช่น เมื่องานทั้งหมดกำลังดำเนินการอยู่ เมื่อคนงานเข้าสู่สถานะนี้ พนักงานจะเข้าสู่โหมดสลีป และจะไม่ดำเนินการใดๆ จนกว่าจะตื่นขึ้น
หากไม่จำเป็นต้องมีการจัดการพูลและรายการงานที่จัดกำหนดการไว้ไม่ว่างเปล่า ผู้ปฏิบัติงานจะเริ่มดำเนินการงานเหล่านั้น เราจะเรียกคนงานดังกล่าวตามอัตภาพ วิ่ง.
หากจำเป็น ผู้ปฏิบัติงานจะรับบทบาทนี้ ผู้จัดการสระน้ำ. พูลสามารถมีผู้ปฏิบัติงานที่เป็นผู้จัดการเพียงคนเดียวหรือไม่มีผู้ปฏิบัติงานเลยก็ได้ หน้าที่ของมันคือการรักษาจำนวนพนักงานที่เหมาะสมที่สุดต่อพูล เขาทำอย่างไร? ประการแรก พนักงานที่ไม่ได้ใช้งานเป็นเวลานานจะถูกลบออก ประการที่สอง จะมีการสร้างผู้ปฏิบัติงานใหม่หากตรงตามเงื่อนไขสามประการพร้อมกัน:

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

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

ผู้ที่สนใจสามารถดูฟังก์ชันของคนงานในเคอร์เนลได้ ซึ่งเรียกว่า worker_thread()

ฟังก์ชันและโครงสร้างที่อธิบายทั้งหมดมีรายละเอียดเพิ่มเติมในไฟล์ รวม/linux/workqueue.h, เคอร์เนล/workqueue.cและ เคอร์เนล/workqueue_internal.h. นอกจากนี้ยังมีเอกสารเกี่ยวกับคิวงานอีกด้วย เอกสารประกอบ/workqueue.txt.

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

ดังนั้นเราจึงดูกลไกในการจัดการการขัดจังหวะแบบเลื่อนออกไปในเคอร์เนล Linux - ทาสก์เล็ตและคิวงานซึ่งเป็นรูปแบบพิเศษของการทำงานหลายอย่างพร้อมกัน คุณสามารถอ่านเกี่ยวกับการขัดจังหวะ งาน และคิวงานได้ในหนังสือ “Linux Device Drivers” โดย Jonathan Corbet, Greg Kroah-Hartman, Alessandro Rubini แม้ว่าบางครั้งข้อมูลในนั้นจะล้าสมัยก็ตาม

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

บทความในชุด:

  1. มัลติทาสกิ้งในเคอร์เนล Linux: คิวงาน

คิวงาน

คิวงาน- สิ่งเหล่านี้เป็นเอนทิตีที่ซับซ้อนและมีน้ำหนักมากกว่างาน ฉันจะไม่พยายามอธิบายความซับซ้อนทั้งหมดของการใช้งานที่นี่ แต่ฉันหวังว่าฉันจะวิเคราะห์สิ่งที่สำคัญที่สุดโดยละเอียดไม่มากก็น้อย
Workqueues เช่นเดียวกับ Tasklet ทำหน้าที่สำหรับการประมวลผลการขัดจังหวะแบบเลื่อนออกไป (แม้ว่าจะสามารถใช้เพื่อวัตถุประสงค์อื่นได้) แต่ต่างจาก Tasklet ตรงที่คิวงานจะถูกดำเนินการในบริบทของกระบวนการเคอร์เนล ดังนั้น คิวงานจึงไม่จำเป็นต้องเป็นแบบอะตอมมิกและสามารถใช้โหมดสลีปได้ () ฟังก์ชั่น เครื่องมือการซิงโครไนซ์ต่างๆ ฯลฯ

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

มีหน่วยงานหลายแห่งที่เกี่ยวข้องกับสสารมืดนี้
ประการแรก รายการงาน(เพียงทำงานสั้น ๆ ) คือโครงสร้างที่อธิบายฟังก์ชัน (เช่น Interrupt Handler) ที่เราต้องการกำหนดเวลา ถือได้ว่าเป็นอะนาล็อกของโครงสร้าง Tasklet เมื่อกำหนดเวลา Tasklets จะถูกเพิ่มเข้าไปในคิวที่ซ่อนไม่ให้ผู้ใช้เห็น แต่ตอนนี้เราจำเป็นต้องใช้คิวพิเศษ - คิวงาน.
งานต่างๆ จะถูกกวาดล้างโดยฟังก์ชันตัวกำหนดเวลา และคิวงานจะถูกประมวลผลโดยเธรดพิเศษที่เรียกว่าผู้ปฏิบัติงาน
คนงานให้การดำเนินการงานแบบอะซิงโครนัสจากคิวงาน แม้ว่าพวกเขาจะเรียกงานตามลำดับการหมุนเวียน แต่ในกรณีทั่วไปไม่มีคำถามเกี่ยวกับการดำเนินการที่เข้มงวดและต่อเนื่อง: หลังจากทั้งหมดการจองการนอนหลับการรอ ฯลฯ เกิดขึ้นที่นี่

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

เพื่อสรุปความสามารถหลักของกลไกคิวงาน ฉันขอแนะนำให้สำรวจ API

เกี่ยวกับคิวและการสร้าง

alloc_workqueue (fmt, แฟล็ก, max_active, args...)
พารามิเตอร์ fmt และ args คือรูปแบบ printf สำหรับชื่อและอาร์กิวเมนต์ พารามิเตอร์ max_activate รับผิดชอบจำนวนงานสูงสุดที่สามารถดำเนินการจากคิวนี้สามารถดำเนินการแบบขนานบน CPU ตัวเดียว
สามารถสร้างคิวได้ด้วยแฟล็กต่อไปนี้:
  • WQ_HIGHPRI
  • WQ_UNBOUND
  • WQ_CPU_เข้มข้น
  • WQ_ฟรีซเอเบิล
  • WQ_MEM_เรียกคืน
ควรให้ความสนใจเป็นพิเศษกับธง WQ_UNBOUND. ขึ้นอยู่กับการมีอยู่ของแฟล็กนี้ คิวจะถูกแบ่งออกเป็นแบบผูกและไม่มีการเชื่อมต่อ
ในคิวที่เชื่อมโยงกันเมื่อเพิ่ม งานจะถูกผูกไว้กับ CPU ปัจจุบัน นั่นคือในคิวดังกล่าว งานจะถูกดำเนินการบนคอร์ที่กำหนดเวลาไว้ ในเรื่องนี้ คิวที่ถูกผูกไว้จะมีลักษณะคล้ายกับงาน
ในคิวที่ไม่ได้แนบงานสามารถดำเนินการบนคอร์ใดก็ได้

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

โดยการเปรียบเทียบกับงาน งานสามารถได้รับมอบหมายลำดับความสำคัญในการดำเนินการ ปกติหรือสูง ลำดับความสำคัญเป็นเรื่องธรรมดาสำหรับทั้งคิว ตามค่าเริ่มต้น คิวจะมีลำดับความสำคัญตามปกติ และหากคุณตั้งค่าสถานะไว้ WQ_HIGHPRIดังนั้นจึงสูงตามไปด้วย

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

ธง WQ_ฟรีซเอเบิลและ WQ_MEM_เรียกคืนมีความเฉพาะเจาะจงและอยู่นอกเหนือขอบเขตของหัวข้อ ดังนั้นเราจะไม่เจาะลึกรายละเอียดเหล่านั้น

บางครั้งมันก็สมเหตุสมผลที่จะไม่สร้างคิวของคุณเอง แต่ควรใช้คิวทั่วไป สิ่งสำคัญ:

  • system_wq - คิวที่ถูกผูกไว้เพื่อการทำงานที่รวดเร็ว
  • system_long_wq - คิวที่ถูกผูกไว้สำหรับงานที่คาดว่าจะใช้เวลานานในการดำเนินการ
  • system_unbound_wq - คิวที่ไม่ได้ผูกไว้

เกี่ยวกับงานและการวางแผนของพวกเขา

ตอนนี้เรามาจัดการกับผลงานกันดีกว่า อันดับแรก มาดูที่การเริ่มต้น การประกาศ และมาโครการเตรียมการ:
ประกาศ(_DELAYED)_WORK(ชื่อ, โมฆะ (*ฟังก์ชั่น)(struct work_struct *งาน)); /* ณ เวลาคอมไพล์ */ INIT(_DELAYED)_WORK(_work, _func); /* ระหว่างดำเนินการ */ PREPARE(_DELAYED)_WORK(_work, _func); /* เพื่อเปลี่ยนฟังก์ชั่นที่กำลังดำเนินการ */
งานจะถูกเพิ่มเข้าไปในคิวโดยใช้ฟังก์ชัน:
บูลคิว_งาน (struct workqueue_struct *wq, struct work_struct *งาน); bool Queue_delayed_work (struct workqueue_struct *wq, structล่าช้า_work *dwork, ล่าช้านานที่ไม่ได้ลงนาม); /* งานจะถูกเพิ่มเข้าไปในคิวหลังจากหมดเวลาหน่วงแล้วเท่านั้น */
นี่เป็นสิ่งที่ควรค่าแก่การดูรายละเอียดเพิ่มเติม แม้ว่าเราจะระบุคิวเป็นพารามิเตอร์ แต่ในความเป็นจริง งานไม่ได้อยู่ในคิวงานอย่างที่คิด แต่อยู่ในเอนทิตีที่แตกต่างไปจากเดิมอย่างสิ้นเชิง - ในรายการคิวของโครงสร้าง worker_pool โครงสร้าง คนงาน_พูลอันที่จริงแล้ว เป็นเอนทิตีที่สำคัญที่สุดในการจัดกลไกคิวงาน แม้ว่าสำหรับผู้ใช้จะยังคงอยู่เบื้องหลังก็ตาม คนงานทำงานอยู่กับพวกเขา และข้อมูลพื้นฐานทั้งหมดอยู่ในนั้น

ทีนี้มาดูกันว่ามีพูลอะไรบ้างในระบบ
เริ่มต้นด้วยพูลสำหรับคิวที่ถูกผูกไว้ (ในภาพ) สำหรับ CPU แต่ละตัว พูลผู้ปฏิบัติงานสองพูลจะถูกจัดสรรแบบคงที่: พูลหนึ่งสำหรับงานที่มีลำดับความสำคัญสูง และอีกพูลหนึ่งสำหรับงานที่มีลำดับความสำคัญปกติ นั่นคือถ้าเรามีสี่คอร์ก็จะมีพูลที่แนบมาเพียงแปดพูลแม้ว่าคิวงานจะมากเท่าที่ต้องการก็ตาม
เมื่อเราสร้างคิวงานจะมีการจัดสรรบริการให้กับ CPU แต่ละตัว pool_workqueue(pwq). แต่ละ pool_workqueue ดังกล่าวเชื่อมโยงกับพูลผู้ปฏิบัติงาน ซึ่งได้รับการจัดสรรบน CPU เดียวกัน และสอดคล้องกับลำดับความสำคัญของประเภทคิว คิวงานจะโต้ตอบกับพูลผู้ปฏิบัติงานผ่านสิ่งเหล่านี้
ผู้ปฏิบัติงานดำเนินงานจากกลุ่มผู้ปฏิบัติงานโดยไม่เลือกปฏิบัติ โดยไม่แยกแยะว่าคิวงานใดเดิมเป็นของ

สำหรับคิวที่ไม่ได้แนบ พูลผู้ปฏิบัติงานจะถูกจัดสรรแบบไดนามิก คิวทั้งหมดสามารถแบ่งออกเป็นคลาสที่เทียบเท่าตามพารามิเตอร์ และพูลผู้ปฏิบัติงานของตัวเองจะถูกสร้างขึ้นสำหรับแต่ละคลาสดังกล่าว เข้าถึงได้โดยใช้ตารางแฮชพิเศษ โดยที่คีย์คือชุดของพารามิเตอร์ และค่าตามลำดับคือพูลผู้ปฏิบัติงาน
ในความเป็นจริงสำหรับคิวที่ไม่ได้ผูกไว้ทุกอย่างจะซับซ้อนกว่าเล็กน้อย: ถ้าสำหรับคิวที่ถูกผูกไว้ pwq และคิวถูกสร้างขึ้นสำหรับ CPU แต่ละตัว ที่นี่พวกเขาจะถูกสร้างขึ้นสำหรับแต่ละโหนด NUMA แต่นี่เป็นการปรับให้เหมาะสมเพิ่มเติมที่เราจะไม่พิจารณาในรายละเอียด

สิ่งเล็กๆ น้อยๆ ทุกประเภท

ฉันจะให้ฟังก์ชันบางอย่างจาก API เพื่อทำให้รูปภาพสมบูรณ์ แต่ฉันจะไม่พูดถึงรายละเอียดเหล่านี้:
/* บังคับให้เสร็จสิ้น */ bool flush_work(struct work_struct *work); บูลflush_delayed_work(structล่าช้า_work *dwork); /* ยกเลิกการทำงาน */ bool cancel_work_sync(struct work_struct *work); บูล cancel_delayed_work (struct ล่าช้า_งาน *dwork); บูล cancel_delayed_work_sync (struct Delayed_work *dwork); /* ลบคิว */ void destroy_workqueue(struct workqueue_struct *wq);

คนงานทำงานอย่างไร

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

ผู้ปฏิบัติงานสามารถอยู่ในสถานะตรรกะสามสถานะตามเงื่อนไขได้: พวกเขาสามารถไม่ได้ใช้งาน กำลังดำเนินการ หรือจัดการได้
คนทำงานก็ได้ ยืนเฉยๆและไม่ทำอะไรเลย ตัวอย่างเช่น เมื่องานทั้งหมดกำลังดำเนินการอยู่ เมื่อคนงานเข้าสู่สถานะนี้ พนักงานจะเข้าสู่โหมดสลีป และจะไม่ดำเนินการใดๆ จนกว่าจะตื่นขึ้น
หากไม่จำเป็นต้องมีการจัดการพูลและรายการงานที่จัดกำหนดการไว้ไม่ว่างเปล่า ผู้ปฏิบัติงานจะเริ่มดำเนินการงานเหล่านั้น เราจะเรียกคนงานดังกล่าวตามอัตภาพ วิ่ง.
หากจำเป็น ผู้ปฏิบัติงานจะรับบทบาทนี้ ผู้จัดการสระน้ำ. พูลสามารถมีผู้ปฏิบัติงานที่เป็นผู้จัดการเพียงคนเดียวหรือไม่มีผู้ปฏิบัติงานเลยก็ได้ หน้าที่ของมันคือการรักษาจำนวนพนักงานที่เหมาะสมที่สุดต่อพูล เขาทำอย่างไร? ประการแรก พนักงานที่ไม่ได้ใช้งานเป็นเวลานานจะถูกลบออก ประการที่สอง จะมีการสร้างผู้ปฏิบัติงานใหม่หากตรงตามเงื่อนไขสามประการพร้อมกัน:

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

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

ผู้ที่สนใจสามารถดูฟังก์ชันของคนงานในเคอร์เนลได้ ซึ่งเรียกว่า worker_thread()

ฟังก์ชันและโครงสร้างที่อธิบายทั้งหมดมีรายละเอียดเพิ่มเติมในไฟล์ รวม/linux/workqueue.h, เคอร์เนล/workqueue.cและ เคอร์เนล/workqueue_internal.h. นอกจากนี้ยังมีเอกสารเกี่ยวกับคิวงานอีกด้วย เอกสารประกอบ/workqueue.txt.

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

ดังนั้นเราจึงดูกลไกในการจัดการการขัดจังหวะแบบเลื่อนออกไปในเคอร์เนล Linux - ทาสก์เล็ตและคิวงานซึ่งเป็นรูปแบบพิเศษของการทำงานหลายอย่างพร้อมกัน คุณสามารถอ่านเกี่ยวกับการขัดจังหวะ งาน และคิวงานได้ในหนังสือ “Linux Device Drivers” โดย Jonathan Corbet, Greg Kroah-Hartman, Alessandro Rubini แม้ว่าบางครั้งข้อมูลในนั้นจะล้าสมัยก็ตาม