Yêu cầuTriển khai bộ lập lịch hàng đợi phản hồi đa cấp Multi-level feedbackqueue scheduler tương tự như bộ lập lịch 4.4BSD để giảm thời gian phảnhồi trung bình để chạy các công việc trên
Trang 1TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI
VIỆN ĐIỆN TỬ - VIỄN THÔNG
BÀI TẬP LỚN
HỆ ĐIỀU HÀNH
Người thực hiện: Nguyễn Trọng Hưng - 20182565
Giảng viên hướng dẫn: TS PHẠM VĂN TIẾN
Hà Nội, 6-2023
Trang 2LỜI NÓI ĐẦU
Ngày nay, máy tính và các đồ dùng điện tử đều đang phát triển công nghệ với tốc
độ chóng mặt Các vật dụng đó gắn liền với cuộc sống của chúng ta
Với mỗi một chiếc máy tính của chúng ta bây giờ thì hầu như rất dễ sử dụng, thân thiện với người dùng Chúng ta có thể học cách sử dụng chúng một cách dễ dàng Điều này nhờ những hệ điều hành thân thiện với người dùng mà chúng ta đã quá quen như window hoặc Mac OS
Để tìm hiểu cách một hệ điều hành như thế nào, phục vụ cho muc đích hiểu thêm
về môn học Hệ Điều Hành của TS Phạm Văn Tiến giảng dạy, em đã làm đề tài tìm hiểu này qua Pintos Phần mà em muốn tìm hiểu là phần advanced scheduler
2
Trang 3ADVANCED SCHEDULER
1 Tìm hiểu đề tài
1.1 Yêu cầu
Triển khai bộ lập lịch hàng đợi phản hồi đa cấp( Multi-level feedback queue scheduler) tương tự như bộ lập lịch 4.4BSD để giảm thời gian phản hồi trung bình để chạy các công việc trên hệ thống của bạn
Giống như bộ lập lịch ưu tiên( Priority scheduler), bộ lập lịch nâng cao chọn luồng để chạy dựa trên các ưu tiên Tuy nhiên, bộ lập lịch nâng cao không thực hiện quyên góp ưu tiên Do đó, chúng tôi khuyên bạn nên có
bộ lập lịch ưu tiên làm việc, ngoại trừ có thể là quyên góp ưu tiên, trước khi bạn bắt đầu làm việc trên bộ lập lịch nâng cao
1.2 Bộ lập lịch 4.4BSD
Mục tiêu của bộ lập lịch có mục đích chung là cân bằng các nhu cầu lập lịch trình khác nhau của các luồng Các luồng thực hiện nhiều I / O yêu cầu thời gian phản hồi nhanh để giữ cho các thiết bị đầu vào và đầu ra bận rộn, nhưng cần ít thời gian CPU Mặt khác, các luồng bị ràng buộc tính toán cần nhận được nhiều thời gian CPU để hoàn thành công việc của chúng, nhưng không yêu cầu thời gian phản hồi nhanh Các luồng khác nằm ở đâu đó ở giữa, với các chu kỳ I / O được ngắt quãng bởi các giai đoạn tính toán và do đó có các yêu cầu thay đổi theo thời gian Một bộ lập lịch được thiết kế tốt thường có thể chứa các luồng với tất cả các yêu cầu này cùng một lúc
Đối với dự án 1, bạn phải thực hiện bộ lập lịch được mô tả trong phụ lục này Bộ lập lịch của chúng tôi giống với bộ lập lịch được mô tả trong [McKusick], đây là một ví dụ về bộ lập lịch hàng đợi phản hồi đa cấp Loại bộ lập lịch này duy trì một số hàng đợi các luồng sẵn sàng chạy, trong đó mỗi hàng đợi chứa các luồng với mức độ ưu tiên khác nhau Tại bất kỳ thời điểm nào, bộ lập lịch chọn một chuỗi từ hàng đợi không trống
có mức độ ưu tiên cao nhất Nếu hàng đợi ưu tiên cao nhất chứa nhiều luồng, thì chúng sẽ chạy theo thứ tự "round robin"
Trang 4Nhiều khía cạnh của bộ lập lịch yêu cầu dữ liệu được cập nhật sau một số lần tích tắc hẹn giờ nhất định Trong mọi trường hợp, các bản cập nhật này sẽ xảy ra trước khi bất kỳ luồng hạt nhân thông thường nào có cơ hội chạy, do đó không có khả năng luồng hạt nhân có thể thấy giá trị timer_ticks() mới tăng mà là giá trị dữ liệu bộ lập lịch cũ
1.3 Bộ định thời multi-level feedback queue
Bộ định thời multilevel feedback queue gồm có nhiều hàng đợi khác nhau, mỗi hàng đợi sẽ chứa các tiểu trình có độ ưu tiên khác nhau Bộ định thời này sẽ luôn chọn tiểu trình từ hàng đợi không trống có độ ưu tiên cao nhất Nếu hàng đợi có độ ưu tiên cao nhất có nhiều tiểu trình, mỗi tiểu trình sẽ được định thời bằng giải thuật “round robin" Các phần của bộ định thời này đòi hỏi dữliệu phải được cập nhật sau một khoảng thời gian nhất định (là bội số của timer ticks)
1.4 Các thông số cần chú ý
1.4.1 Niceness
Mỗi tiểu trình có một giá trị số nguyên gọi là nice, giá trị này xác định mức độ “mới” của tiểu trình so với các tiểu trình khác Giá trị nice bằng 0 không ảnh hưởng tới độ ưu tiên của tiểu trình Giá trị nice dương (tối đa 20) sẽ làm giảm độ ưu tiên của tiểu trình và sẽ khiến nó
từ bỏ giờ CPU mà nó nhận được Ngược lại, giá trị nice âm (tối thiểu
là -20) sẽ giúp tiểu trình có xu hướng nhận được CPU từ các tiểu trình khác
Giá trị nice của tiểu trình đầu tiên là 0 Các tiểu trình khác sẽ có giá trị nice được kế thừa từ tiểu trình cha của nó Các hàm liên quan đến nice sau đây cần được cài đặt (nguyên mẫu hàm của chúng được định nghĩa sẵn trong tập tin threads/thread.c):
• int thread_get_nice (void): Trả về giá trị nice của tiểu trình hiện tại
• void thread_set_nice (int new_nice): Thiết lập giá trị nice mới cho tiểu trình hiện tại, đồng thời tính lại độ ưu tiên của tiểu trình dựa trên giá trị nice mới Nếu tiểu trình đang chạy không có độ ưu tiên cao nhất, nó cần từ bỏ việc sử dụng CPU
4
Trang 51.4.2 Priority
Bộ định thời cần cài đặt có 64 độ ưu tiên, cũng đồng thời có 64 hàng đợi được đánh số từ 0 (PRI_MIN) đến 63 (PRI_MAX) Độ ưu tiên 0 là nhỏ nhất, 63 là cao nhất
Độ ưu tiên của tiểu trình được xác định tại thời điểm khởi tạo Sau đó, nó sẽ được xác định lại sau mỗi 4 chu kỳ xung clock Độ
ưu tiên mới được xác định bằng công thức:
priority = PRI_MAX − (recent_cpu/4) − (nice × 2)
Trong đó, recent_cpu là thời gian CPU ước tính mà tiểu trình đã
sử dụng gần đây, còn nice là giá trị nice của tiểu trình Kết quả của biểu thức trên trên sẽ được làm tròn xuống số nguyên gần nhất Các giá trị ¼ và 2 trong biểu thức trên được xác định thông qua thực nghiệm Kết quả độ ưu tiên tính được phải luôn nằm trong phạm vi
từ PRI_MIN đến PRI_MAX
Công thức trên được thiết kế để các tiểu trình đã nhận được CPU gần đây sẽ có độ ưu tiên thấp hơn ở lần lập lịch kế tiếp Đây chính là chìa khóa để ngăn chặn tình trạng bỏ đói (starvation): một tiểu trình chưa được nhận CPU gần đây sẽ có recent_cpu bằng 0, sẽ luôn đảm bảo sẽ được nhận CPU sớm
1.4.3 Recent CPU
Giá trị recent_cpu được sử dụng để đo lường lượng giờ CPU mỗi tiểu trình đã nhận được gần đây Để ước tính giá trị này, hàm exponentially weighted moving average được sử dụng, hàm này
có dạng tổng quát:
x(0) = f(0)
x(t) = a × x(t − 1) + f(t)
a = k/(k + 1)
Trong biểu thức này, x(t) là moving average tại thời điểm t ≥ 0, f(t) là hàm trung bình và k điều khiển tỷ lệ
Trang 6Giá trị khởi tạo của recent_cpu là 0 khi tiểu trình đầu tiên được tạo Nếu là các tiểu trình khác, giá trị này sẽ là giá trị recent_cpu của tiểu trình cha Mỗi lần một ngắt timer diễn ra, recent_cpu sẽ tăng lên 1 đối với tiểu trình đang thực thi Thêm vào đó, mỗi giây, giá trị recent_cpu của mỗi tiểu trình được xác định( bất kể tiểu trình đó đang chạy, ready hay blocked) qua công thức sau: recent_cpu = (2 × load_avg)/(2 × load_avg + 1) × recent_cpu + nice
Trong công thức này, load_avg là moving average của số tiểu trình sẵn sàng được chạy
Giá trị recent_cpu có thể âm nếu tiểu trình có nice âm, khi đó không được làm tròn recent_cpu về 0
Khi thực hiện biểu thức này, cần tính giá trị (2 × load_avg)/(2× load_avg + 1) trước, sau đó nhân với recent_cpu Thực hiện khác thứ tự có thể dẫn đến lỗi tràn bộ nhớ
Cần phải cài đặt hàm thread_get_recent_cpu() trong threads/thread.c:
• int thread_get_recent_cpu(void): Trả về giá trị recent_cpu của tiểu trình hiện tại (tính theo phần trăm), được làm tròn đến số nguyên gần nhất
1.4.4 Load Average
Giá trị load_avg, thường được gọi là thời gian tải trung bình, dùng để ước tính số tiểu trình trung bình được thực thi trong vòng một phút trước đó Giống như recent_cpu, giá trị này cũng là exponentially weighted moving average
Tuy nhiên, khác với độ ưu tiên và recent_cpu, load_avg được xác định trên phạm vi toàn hệ thống, không phải đặc trưng theo từng tiểu trình Tại thời điểm hệ thống bắt đầu hoạt động, giá trị này được khởi tạo bằng 0 Mỗi một giây trôi qua, nó được cập nhật lại theo công thức sau:
load_avg = (59/60) × load_avg + (1/60) × ready_threads
6
Trang 7Trong công thức này, ready_threads là tổng số tiểu trình đang chạy hoặc đang ở trạng thái sẵn sàng (ready) tại thời điểm cập nhật Giá trị này được trả về thông qua hàm thread_get_load_avg(), hàm này đã được định nghĩa trong threads/thread.c nhưng chưa được cài đặt:
• int thread_get_load_avg(void): Trả về thời gian tải trung bình của hệ thống (tính theo phần trăm), làm tròn tới số nguyên gần nhất
2 Triển khai đề tài
2.1 Thao tác với file
- Các file sẽ thao tác trong đề tài này: threads/thread.c, threads/thread.h, Devices/timer.c
Như ta đã thấy, các công để tính toán các thông số trên đều ở dưới dạng số thực mà không phải ở dạng số nguyên Tuy nhiên mã nguồn Pintos lại không
hỗ trợ các phép toán với số thực có dấu chấm động Vì vậy, cần tạo một tập tin “ fixed_point.h” nằm trong thư mục pitos/src/threads/ Tập tin này có cấu trúc và các hàm cần thiết để tính toán số thực
Dưới đây tóm tắt cách thực hiện các phép toán số học dấu phẩy động trong
C Trong bảng, x và y là các số dấu phẩy động, n là một số nguyên, các số dấu phẩy động ở định dạng p.q có dấu trong đó p + q = 31, và f là 1 << q: Convert n to fixed point: n * f
Convert x to integer (rounding toward zero): x / f
Convert x to integer (rounding to nearest): (x + f / 2) / f if x >= 0,
(x - f / 2) / f if x <= 0
Add x and y: x + y
Subtract y from x: x - y
Add x and n: x + n * f
Subtract n from x: x - n * f
Multiply x by y: ((int64_t) x) * y / f
Trang 8Multiply x by n: x * n
Divide x by y: ((int64_t) x) * f / y
Divide x by n: x / n
2.2 Các hàm và biến
Thêm biến thoi dõi tải trung bình
fixed_t load_avg;
Thêm các biến trong hàm struct thread
int nice;
fixed_t recent_cpu;
Thêm 2 khởi tạo của nice và recent_cpu trong init_thread:
t->nice = 0;
Thêm vào struct_thread trong thread.h, 2 biến sau:
Int nice;
fixed_t recent_cpu;
Cập nhật CPU gần đây:
void mlfqs_recent_cpu_update (struct thread* t,void * aux)
Cập nhật mức ưu tiên mỗi 4 timer ticker
void mlfqs_thread_priority_update (struct thread* t, void * aux)
Cập nhật CPU gần đây và Load Average mỗi giây:
void mlfqs_rc_and_la_update (void)
Cập nhật các hàm so sánh
- threadlock_cmp_priority
- cmp_priority
Cập nhật các hàm xử lý lock
- thread_release_lock
8
Trang 9Cập nhật các hàm thông số:
- thread_set_priority (int new_priority)
- int thread_get_priority (void)
- void thread_set_nice (int nice UNUSED)
- int thread_get_nice (void)
- int thread_get_load_avg (void)
- int thread_get_recent_cpu (void)
Cập nhật theo công thức và thuật toán
Chỉnh sửa timer_interrupt để phục vụ tính toán load_avg và recent_cpu:
if (ticks % TIMER_FREQ == 0)
nếu đúng thì trả về recent_cpu và load_avg mỗi giây
if (ticks % 4 == 0)
Nếu đúng thì trả về priority mỗi 4 timer ticks
3 Kết quả sau khi chạy thử
Trang 104 Link Github và các link khác
Github:
https://github.com/Hung-NT-4320/Pintos_Project.git
Khác:
Pintos Projects: Project 1 Threads (stanford.edu)
Pintos Projects: 4.4BSD Scheduler (stanford.edu)
https://stackoverflow.com/
https://www.studocu.com/vn/document/truong-dai-hoc-cong-nghe-thong- tin-dai-hoc-quoc-gia-thanh-pho-ho-chi-minh/operating-system/lab-7/8609644
CS318 - Pintos: src/threads/thread.c File Reference (jhu-cs318.github.io)
10