THHDH
THHDH
1.1.3 Giao diện đồ họa người dùng - Graphical user interface (GUI)
Linux cũng cung cấp giao diện đồ họa người dùng (GUI) giúp người dùng tương tác với
hệ điều hành một cách thân thiện hơn. GUI bao gồm môi trường máy tính để bàn, trình
quản lý cửa sổ và các công cụ đồ họa khác để quản lý ứng dụng, tệp và cài đặt.
1.1.4 Hệ thống tập tin – File System
Hệ thống tệp Linux được tổ chức dưới dạng cấu trúc thư mục phân cấp, với thư mục gốc ở
trên cùng. Tất cả các tệp và thư mục trong Linux được tổ chức trong thư mục gốc này, với
đường dẫn tệp bắt đầu từ thư mục gốc. Hệ thống tệp phân biệt chữ hoa chữ thường, nghĩa
là “file.txt” và “File.txt” là hai tệp khác nhau.
1.1.4.1 Các loại hệ thống tệp
Có nhiều hệ thống tệp khác nhau có thể được sử dụng với Linux, mỗi hệ thống có bộ tính
năng, đặc điểm hiệu suất và giới hạn riêng. Dưới đây là một số hệ thống tệp phổ biến được
sử dụng trong Linux:
• Ext4 - Hệ thống tệp mở rộng thứ tư là hệ thống tệp được sử dụng phổ biến nhất
trong Linux. Đây là một hệ thống tệp mạnh mẽ và đáng tin cậy, hỗ trợ các tệp và
khối lượng lớn, đồng thời có thể xử lý mức độ phân mảnh cao.
• “Btrfs” - Hệ thống tệp B-tree là một hệ thống tệp mới hơn với sự hỗ trợ tích hợp để
chụp nhanh, nén và kiểm tra tổng. Nó được thiết kế để có khả năng mở rộng cao
hơn và dễ quản lý hơn Ext4, nhưng vẫn được một số người coi là thử nghiệm.
• XFS – Hệ thống tệp XFS được thiết kế cho môi trường hiệu suất cao, hỗ trợ các tệp
lớn, tính đồng thời cao và phục hồi nhanh sau sự cố. Nó thường được sử dụng trong
môi trường doanh nghiệp và đám mây.
• NTFS - Hệ thống tệp công nghệ mới là một hệ thống tệp được sử dụng bởi hệ điều
hành Windows. Linux có thể đọc và ghi vào phân vùng NTFS, nhưng nó yêu cầu
trình điều khiển bổ sung và có thể không hỗ trợ đầy đủ tất cả các tính năng.
1.1.4.2 Cấu trúc thư mục Linux điển hình
Hệ thống tệp Linux có cấu trúc thư mục được xác định rõ ràng, với một số thư mục nhất
định được sử dụng cho các mục đích cụ thể. Dưới đây là một số thư mục phổ biến nhất mà
bạn sẽ gặp trong hệ thống tệp Linux điển hình:
• /bin: Chứa các tệp nhị phân thiết yếu cần thiết để hệ thống hoạt động bình thường.
Các tệp này bao gồm các tiện ích cốt lõi như ls, cp và rm.
• /boot: Chứa các tệp cần thiết để khởi động hệ thống, chẳng hạn như tệp cấu hình
kernel và bộ tải khởi động.
• /dev: Chứa các tệp thiết bị đại diện cho các thiết bị vật lý và ảo, chẳng hạn như ổ
cứng, ổ USB và máy in.
• /etc: Chứa các tệp cấu hình trên toàn hệ thống, bao gồm các tệp kiểm soát các dịch
vụ hệ thống và cài đặt mạng.
• /home: Chứa các thư mục home của người dùng trên hệ thống.
• /lib: Chứa các file thư viện dùng chung cần thiết để chạy các chương trình và thư
viện.
• /media: Chứa các điểm gắn kết cho phương tiện di động, chẳng hạn như ổ USB và
CD.
• /opt: Chứa các gói phần mềm tùy chọn không được cài đặt theo mặc định.
• /proc: Chứa hệ thống tệp ảo cung cấp thông tin về các tiến trình đang chạy và tài
nguyên hệ thống.
• /root: Thư mục chính của người dùng root.
• /run: Chứa dữ liệu thời gian chạy cho các dịch vụ và ứng dụng hệ thống.
• /sbin: Chứa các tệp nhị phân hệ thống được sử dụng cho các tác vụ quản trị hệ
thống, chẳng hạn như cấu hình mạng và bảo trì hệ thống.
• /srv: Chứa dữ liệu về các dịch vụ do hệ thống cung cấp, chẳng hạn như nội dung
web và tệp FTP.
• /tmp: Chứa các tệp tạm thời được sử dụng bởi các ứng dụng và hệ thống.
• /usr: Chứa các tệp người dùng, bao gồm các tệp nhị phân hệ thống, thư viện, tài liệu
và dữ liệu ứng dụng.
• /var: Chứa dữ liệu có thể thay đổi, chẳng hạn như tệp nhật ký, tệp đệm và tệp tạm
thời.
• Quản lý nhóm và người dùng: Linux hỗ trợ nhiều người dùng và nhóm, mỗi nhóm
có bộ quyền và cấp độ truy cập riêng. Bạn có thể sử dụng dòng lệnh để tạo, sửa đổi
và xóa người dùng và nhóm cũng như đặt quyền cho họ.
• Quản lý tệp và thư mục: Hệ thống tệp Linux được tổ chức dưới dạng cấu trúc thư
mục phân cấp và dòng lệnh cung cấp các công cụ mạnh mẽ để tạo, di chuyển, sao
chép và xóa các tệp và thư mục.
• Quản lý tiến trình: Dòng lệnh cho phép quản lý các quy trình đang chạy, bao gồm
khởi động, dừng và khởi động lại chúng, cũng như giám sát hiệu suất và việc sử
dụng tài nguyên của chúng.
• Quản lý gói: Linux sử dụng trình quản lý gói để cài đặt, cập nhật và xóa gói phần
mềm. Bạn có thể sử dụng dòng lệnh để quản lý các gói, bao gồm cài đặt và gỡ bỏ
gói, tìm kiếm gói và cập nhật cơ sở dữ liệu gói.
• Cấu hình mạng: Dòng lệnh cho phép bạn định cấu hình cài đặt mạng, bao gồm địa
chỉ IP, cài đặt DNS và giao diện mạng.
• Giám sát hệ thống: Dòng lệnh cung cấp một loạt công cụ để giám sát hiệu suất hệ
thống và việc sử dụng tài nguyên, bao gồm các công cụ giám sát việc sử dụng CPU,
bộ nhớ và ổ đĩa cũng như lưu lượng mạng và nhật ký hệ thống.
• Quản lý bảo mật: Dòng lệnh cung cấp các công cụ để quản lý bảo mật hệ thống,
bao gồm định cấu hình tường lửa, đặt quyền và kiểm soát truy cập cũng như quản
lý mật khẩu và xác thực người dùng.
• Bảo trì hệ thống: Dòng lệnh cung cấp các công cụ để thực hiện các tác vụ bảo trì
hệ thống, bao gồm chống phân mảnh ổ đĩa, dọn dẹp ổ đĩa và sao lưu hệ thống.
1.2.2 Một số tác vụ quản trị mẫu
Thêm người dùng: Bạn có thể sử dụng lệnh useradd để thêm người dùng mới vào hệ
thống. Ví dụ: để thêm người dùng có tên là “n21cqcn001”, bạn có thể chạy lệnh sau:
Cài đặt gói: Bạn có thể sử dụng lệnh apt (debian) để cài đặt gói từ kho lưu trữ. Ví dụ: để
cài đặt trình soạn thảo văn bản nano, bạn có thể chạy lệnh sau:
Xem nhật ký hệ thống: Bạn có thể sử dụng lệnh tail để xem một vài dòng cuối cùng của
tệp nhật ký. Ví dụ: để xem 10 dòng cuối cùng của nhật ký hệ thống, bạn có thể chạy lệnh
sau:
Giám sát hiệu suất hệ thống: Bạn có thể sử dụng lệnh top để giám sát hiệu suất hệ thống,
bao gồm việc sử dụng CPU, mức sử dụng bộ nhớ và thông tin xử lý. Ví dụ: để theo dõi
việc sử dụng CPU, bạn có thể chạy lệnh sau:
top
htop
Dòng lệnh là một công cụ mạnh mẽ để quản lý và điều hành hệ điều hành Linux, đồng thời
cung cấp nhiều công cụ và tiện ích để thực hiện các tác vụ thông thường. Với sự hiểu biết
về dòng lệnh Linux và các tác vụ quản trị của nó, bạn có thể kiểm soát hệ thống Linux của
mình và quản lý nó hiệu quả hơn.
1.2.3 Các lệnh cơ bản
Dưới đây là một số lệnh Linux cơ bản mà bạn có thể sử dụng để điều hướng hệ thống tệp,
quản lý tệp và thư mục cũng như thực hiện các tác vụ khác:
1.2.3.1 Điều hướng / Navigation
• apt: Công cụ gói nâng cao (được sử dụng bởi các bản phân phối dựa trên Debian)
• yum: Trình cập nhật Yellowdog đã được sửa đổi (được sử dụng bởi các bản phân
phối dựa trên Red Hat).
Bài tập:
Thay đổi hthanhsg thành mã số sinh viên của bạn
Bài 1. Tạo cây thư mục như sau
Sử dụng các lệnh
• ghi_chu_ly_thuyet_1.txt
• ghi_chu_ly_thuyet_2.txt
Tạo các tập tin trong thư mục /home/MSSV/HEDIEUHANH/THUCHANH/BAI_TH_01
• my_code_1_1.c
• my_code_1_2.c
Tạo các tập tin trong thư mục /home/MSSV/HEDIEUHANH/THUCHANH/BAI_TH_02
• my_code_2_1.c
• my_code_2_2.c
Tạo các tập tin trong thư mục /home/MSSV/HEDIEUHANH/THUCHANH/BAI_TH_02
• my_code_3_1.c
• my_code_3_2.c
• touch
• cp
• cp -R
• Đổi tên tập tin my_code_1.1.c trong thư mục /home/MSSV/BACKUP/ thành
my_code_1.1.c.bak
• Đổi tên các tập tin my_code_1_1.c và my_code_1_2.c trong BAI_TH_01 thành
MSSV_my_code_1_1.c và MSSV_my_code_1_2.c
• Di chuyển tập tin ghi_chu_ly_thuyet_1.txt trong thư mục “/home/MSSV/BACKUP
/LYTHUYET” vào thưc mục /home/MSSV/BACKUP
• Di chuyển toàn bộ thư mục BACKUP vào thư mục HEDIEUHANH
Dùng các lệnh
• mv
Bài 5. Nén và giải nén tập tin
• rm
• rm -r
• rm -rf
• rmdir
Bài 7. Thay đổi quyền
Truy cập quyền root
Lệnh:
• sudo -i
• passwd <username>
• groupadd <ten_group>
• usermod -a -G <ten_group> <username>
Bài 8. Quản lý tiến trình
GCC là viết tắt của Bộ sưu tập trình biên dịch GNU được sử dụng để biên dịch chủ yếu
ngôn ngữ C và C++. Nó cũng có thể được sử dụng để biên dịch Objective C và Objective
C++. Tùy chọn quan trọng nhất cần có khi biên dịch tệp mã nguồn là tên của chương trình
nguồn, phần còn lại mọi đối số đều là tùy chọn như cảnh báo, gỡ lỗi, liên kết thư viện, tệp
đối tượng, v.v. Các tùy chọn khác nhau của lệnh gcc cho phép người dùng dừng quá trình
biên dịch quá trình ở các giai đoạn khác nhau.
2.1 Header file
Trong ngôn ngữ C, các tệp tiêu đề chứa một tập hợp các hàm thư viện tiêu chuẩn được xác
định trước. .h là phần mở rộng của tệp tiêu đề trong C và chúng ta yêu cầu sử dụng tệp tiêu
đề trong chương trình của mình bằng cách đưa nó vào chỉ thị tiền xử lý C “#include”. Ngôn
ngữ C có nhiều thư viện bao gồm các hàm được xác định trước để giúp việc lập trình trở
nên dễ dàng hơn.
Ngôn ngữ C++ cũng cung cấp cho người dùng nhiều chức năng khác nhau, một trong số
đó được bao gồm trong các tệp tiêu đề. Trong C++, tất cả các tệp tiêu đề có thể kết thúc
hoặc không kết thúc bằng phần mở rộng “.h”.
Nó cung cấp các tính năng như chức năng thư viện, kiểu dữ liệu, macro, v.v. bằng cách
nhập chúng vào chương trình với sự trợ giúp của chỉ thị tiền xử lý “#include”. Các chỉ thị
tiền xử lý này được sử dụng để hướng dẫn trình biên dịch rằng các tệp này cần được xử lý
trước khi biên dịch.
Cú pháp của Header File trong C/C++
Chúng ta có thể đưa các tệp tiêu đề trong C bằng cách sử dụng một trong hai cú pháp đã
cho cho dù đó là tệp tiêu đề được xác định trước hay do người dùng xác định.
Hầu hết các chương trình trên Linux khi biên dịch sử dụng các tập tin tiêu đề trong thư
mục /usr/include hoặc trong thư mục con.
2.2 Cài đặt gcc
Options:
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...].
and libraries.
executable.
-shared Create a shared library.
Options starting with -g, -f, -m, -O, -W, or --param are automatically
2.3 Cú pháp
gcc [-c|-S|-E] [-std=standard]
Ví dụ: Điều này sẽ biên dịch tệp source.c và cung cấp tệp đầu ra dưới dạng tệp a.out, đây
là tên mặc định của tệp đầu ra do trình biên dịch gcc cung cấp, có thể được thực thi bằng
cách sử dụng ./a.out
Các tùy chọn hữu ích nhất kèm theo ví dụ: Ở đây MSSV_my_code_1_1.c là tệp mã chương
trình C.
• -o MSSV_my_code_1_1: Điều này sẽ biên dịch tệp MSSV_my_code_1_1.c nhưng
thay vì đặt tên mặc định do đó được thực thi bằng ./opt, nó sẽ cung cấp tệp đầu ra
dưới dạng opt. -o dành cho tùy chọn tệp đầu ra.
• -Werror: Điều này sẽ biên dịch nguồn và hiển thị cảnh báo nếu có lỗi trong chương
trình, -W là để đưa ra cảnh báo.
• -Wall: Điều này sẽ không chỉ kiểm tra lỗi mà còn kiểm tra tất cả các loại cảnh báo
như lỗi biến không được sử dụng, cách tốt nhất là sử dụng cờ này trong khi biên
dịch mã.
• -ggdb3: Lệnh này cấp cho chúng tôi quyền gỡ lỗi chương trình bằng gdb, lệnh này
sẽ được mô tả sau, tùy chọn -g dùng để gỡ lỗi.
• -lm : Lệnh này liên kết thư viện math.h với tệp nguồn của chúng ta, tùy chọn -l được
sử dụng để liên kết thư viện cụ thể, đối với math.h chúng ta sử dụng -lm
• -std=c11: Lệnh này sẽ sử dụng phiên bản c11 của tiêu chuẩn để biên dịch chương
trình source.c, cho phép xác định biến trong quá trình khởi tạo vòng lặp cũng như
sử dụng phiên bản tiêu chuẩn mới hơn được ưu tiên.
• -c : Lệnh này biên dịch chương trình và cung cấp tệp đối tượng làm đầu ra, được sử
dụng để tạo thư viện.
• -v : Tùy chọn này được sử dụng cho mục đích verbose.
#include <stdlib.h>
int system( const char (cmdstr) )
Hàm này gọi chuỗi lệnh cmdstr thực thi và chờ lệnh chấm dứt mới quay về nơi gọi hàm.
Nó tương đương với việc bạn gọi shell thực thi lệnh của hệ thống: $sh –c cmdstr
system() sẽ trả về mã lỗi 127 nếu như không khởi động được shell để gọi lệnh cmdstr. Mã
lỗi -1 nếu gặp các lỗi khác. system()
Ví dụ:
/home/MSSV/call_system.c
Thay thế tiến trình hiện tại với các hàm exec
Mỗi tiến trình được hệ điều hành cấp cho 1 không gian nhớ tách biệt để tiến trình tự do
hoạt động. Nếu tiến trình A của chúng ta triệu gọi một chương trình ngoài B (bằng hàm
system()chẳng hạn), hệ điều hành thường thực hiện các thao tác như:
• cấp phát không gian bộ nhớ cho tiến trình mới, điều chỉnh lại danh sách các tiến
trình,
• nạp mã lệnh của chương trình B trên đĩa cứng và không gian nhớ vừa cấp phát cho
tiến trình.
• Đưa tiến trình mới vào danh sách cần điều phối của hệ điều hành.
Những công việc này thường mất thời gian đáng kể và chiếm giữ thêm tài nguyên của hệ
thống.
Nếu tiến trình A đang chạy và nếu chúng ta muốn tiến trình B khởi động chạy trong không
gian bộ nhớ đã có sẵn của tiến trình A thì có thể sử dụng các hàm exec được cung cấp bới
Linux. Các hàm exec sẽ thay thế toàn bộ ảnh của tiến trình A (bao gồm mã lệnh, dữ liệu,
bảng mô tả file) thành ảnh của một tiến trình B hoàn toàn khác. Chỉ có số định danh PID
của tiến trình A là còn giữ lại. Tập hàm exec bao gồm các hàm sau:
#include <unistd.h>
extern char **environ;
int execl( const char *path, const char *arg, ... );
int execlp( const char *file, const char *arg, ... );
int execle( const char *path, const char *arg, ..., char *const envp[]);
int exect( const char *path, char *const argv[] );
int execv( const char *path, char *const argv[] );
int execvp( const char *file, char *const argv[] );
Đa số các hàm này đều yêu cầu chúng ta chỉ đối số path hoặc file là đường dẫn đến tên
chương trình cần thực thi trên đĩa. arg là các đối số cần truyền cho chương trình thực thi,
những đối số này tương tự cách chúng ta gọi chương trình từ dòng lệnh.
3.3 Nhân bản tiến trình với hàm fork
Thay thế tiến trình đôi khi bất lợi. Đó là khi tiến trình mới chiếm giữ toàn bộ không gian
của tiến trình cũ và chúng ta sẽ không có khả năng kiểm soát cũng như điều khiển tiếp
tiến trình hiện hành của mình sau khi gọi hàm exec nữa. Cách đơn giản mà các chương
trình Linux thường dùng đó là sử dụng hàm fork() để nhân bản hay tạo bản sao mới của
tiến trình.
fork() là một hàm khá đặc biệt, khi thực thi, nó sẽ trả về 2 giá trị khác nhau trong lần thực
thi, so với hàm bình thường chỉ trả về 1 giá trị trong lần thực thi. Khai báo của hàm fork()
như sau:
#include <sys/types.h>
#include <unistd.h>
pid_t fork()
Nếu thành công, fork() sẽ tách tiến trình hiện hành 2 tiến trình. Tiến trình ban đầu gọi là
tiến trình cha (parent process) trong khi tiến trình mới gọi là tiến trình con (child process).
Tiến trình con sẽ có một số định danh PID riêng biệt. ngoài ra, tiến trình con còn mang
thêm một định danh PPID (Parent Process ID) là số định danh PID của tiến trình cha.
Sau khi tách tiến trình, mã lệnh thực thi ở cả hai tiến trình được sao chép là hoàn toàn giống
nhau. Chỉ có một dấu hiệu để chúng ta có thể nhận dạng tiến trình cha và tiến trình con, đó
là trị trả về của hàm fork(). Bên trong tiến trình con, hàm fork() sẽ trả về trị 0. Trong khi
bên trong tiến trình cha, hàm fork() sẽ trả về trị số nguyên chỉ là PID của tiến trình con vừa
tạo. Trường hợp không tách được tiến trình, fork() sẽ trả về trị -1. Kiểu pid_t được khai
báo và định nghĩa trong uinstd.h là kiểu số nguyên (int).
Đoạn mã điều khiển và sử dụng hàm fork() thường có dạng chuẩn sau:
pid_t new_pid;
new_pid = fork(); // tách tiến trình
switch (new_pid){
case -1: printf( "Khong the tao tien trinh moi" ); break;
case 0: printf( "Day la tien trinh con" );
// mã lệnh dành cho tiến trình con đặt ở đây
break;
default: printf( "Day la tien trinh cha" );
// mã lệnh dành cho tiến trình cha đặt ở đây
break;
}
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int &stat_loc);
Hàm wait khi được gọi sẽ yêu cầu tiến trình cha dừng lại chờ tiến trình con kết thúc trước
khi thực hiện tiếp các lệnh điều khiển trong tiến trình cha. wait() làm cho sự liên hệ giữa
tiến trình cha và tiến trình con trở nên tuần tự. Khi tiến trình con kết thúc, hàm sẽ trả về số
PID tương ứng của tiến trình con. Nếu chúng ta truyền thêm đối số stat_loc khác NULL
cho hàm thì wait() cũng sẽ trả về trạng thái mà tiến trình con kết thúc trong biến stat_loc.
Chúng ta có thể sử dụng các macro khai báo sẵn trong sys/wait.h như sau:
• WIFEXITED (stat_loc) Trả về trị khác 0 nếu tiến trình con kết thúc bình thường.
• WEXITSTATUS (stat_loc) Nếu WIFEXITED trả về trị khác 0, macro này sẽ trả về
mã lỗi của tiến trình con.
• WIFSIGNALED (stat_loc) Trả về trị khác 0 nếu tiến trình con kết thúc bởi một tín
hiệu gửi đến.
• WTERMSIG(stat_loc) Nếu WIFSIGNALED khác 0, macro này sẽ cho biết số tín
hiệu đã hủy tiến trình con.
• WIFSTOPPED(stat_loc) Trả về trị khác 0 nếu tiến trình con đã dừng.
• WSTOPSIG(stat_loc) Nếu WIFSTOPPED trả về trị khác 0, macro này trả về số
hiệu của signal.
3.5 Bài tập
3.5.1 Bài 1
Sử dụng hàm system(), system_demo.c tạo các tiến trình sau:
#include <unistd.h>
#include <stdio.h>
int main()
{
execlp( "ps", "ps", "–ax", 0 );
exit( 0 );
}
3.5.3 Bài 3
Tạo tập tin fork_demo.c sử dụng hàm fork() trong đó:
• In ra câu thông báo: “Can not create child process”nếu hàm fork() trả về giá trị -1.
• Ngược lại:
- In ra 2 lần câu thông báo: “This is child process” nếu mã trả về là 0.
- In ra 3 lần câu thông báo: “This is parent process” nếu mã trả về là PID của
tiến trình con.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
char * message;
int n;
pid = fork();
……
exit( 0 );
}
3.5.4 Bài 4
Sử dụng hàm wait() để chờ tiến trình con kết thúc sau khi gọi fork(), wait_child.c
3.5.5 Bài 5
Sử dụng hàm wait() để chờ tiến trình con kết thúc sau khi gọi fork(), wait_child2.c, kiểm
tra mã lỗi trả về từ tiến trình con.
Phần 4. Lập lịch CPU
Burst Time: đây là khoảng thời gian mà một tiến trình cần để hoàn thành quá trình thực
thi trên CPU. Nó biểu thị thời gian CPU thực hiện các hướng dẫn của tiến trình cụ thể
đó.
Waiting time - Thời gian chờ: Nó đề cập đến tổng lượng thời gian mà một tiến trình
dành để chờ trong hàng đợi sẵn sàng trước khi nó có cơ hội thực thi trên CPU.
Các thuật toán lập lịch
4.1 First come First serve - FCFS
4.1.1 Các khái niệm
Đến trước phục vụ trước (First Come First Served – viết tắt là FCFS) là phương pháp
điều độ đơn giản nhất, cả về nguyên tắc và cách thực hiện. Tiến trình yêu cầu CPU trước
sẽ được cấp CPU trước.
Hệ điều hành xếp tiến trình sẵn sàng vào hàng đợi FIFO. Tiến trình mới được xếp vào
cuối hàng đợi, khi CPU được giải phóng, hệ điều hành sẽ lấy tiến trình từ đầu hàng đợi
và cấp CPU cho tiến trình đó thực hiện.
Mặc dù đơn giản và đảm bảo tính công bằng, FCFS có thời gian chờ đợi trung bình của
tiến trình lớn do phải chờ đợi tiến trình có chu kỳ CPU dài trong trường hợp những tiến
trình như vậy nằm ở đầu hàng đợi.
Thuật toán FCFS thông thường là thuật toán điều độ không phân phối lại. Sau khi tiến
trình được cấp CPU, tiến trình đó sẽ sử dụng CPU cho đến khi kết thúc hoặc phải
dừng lại để chờ kết quả vào ra. Để có thể sử dụng được trong những hệ thống chia sẻ
thời gian, thuật toán đến trước phục vụ trước được cải tiến để thêm cơ chế phân phối lại.
4.1.2 Thuật toán
Cho n tiến trình với thời gian xử lý và thời gian đến của chúng, nhiệm vụ là tìm thời
gian chờ trung bình (average waiting time - awt) và thời gian quay vòng trung bình (turn
around time - tat) bằng thuật toán lập lịch FCFS.
FIFO chỉ đơn giản xếp hàng các tiến trình theo thứ tự chúng đến trong hàng đợi sẵn
sàng. Ở đây, quy trình đến trước sẽ được thực thi trước và quy trình tiếp theo sẽ chỉ bắt
đầu sau khi quy trình trước đó được thực thi đầy đủ.
• Thời gian hoàn thành (completion time): Thời gian mà tiến trình hoàn thành
quá trình thực thi của nó.
• Thời gian quay vòng: Thời gian chênh lệch giữa thời gian hoàn thành và thời
gian đến.
Thời gian quay vòng = Thời gian hoàn thành – Thời gian đến
• Thời gian chờ: Thời gian Chênh lệch giữa thời gian quay vòng và thời gian xử
lý.
Thời gian chờ đợi = Thời gian quay vòng – Thời gian xử lý.
Thuật toán:
1. Nhập các tiến trình cùng với thời gian thực thi của chúng (bt) và thời gian đến (at)
2. tìm thời gian chờ cho tất cả các tiến trình
wt[i] = bt[0] + bt[1] + … + bt[i-1] – at[i]
3. Tìm Turn Around Time
= wating_time + burst_time cho tất cả các tiến trình
4. Thời gian chờ đợi trung bình = Tổng thời gian chờ đợi / số tiến trình
5. Thời gian quay vòng trung bình = Tổng thời gian quay vòng trung bình / số tiến trình.
4.1.3 Ví dụ FCFS
P1 0 10 3
P2 1 1 1
P3 2.5 2 3
P4 3 1 4
P5 4.5 5 2
Bảng 1. Ví dụ các tiến trình với thời gian đến khác nhau
Bước 1:
bt = [10, 1, 2, 1, 5]
at = [0, 1, 2.5, 3, 4.5]
Bước 2:
wt[0] = 0
wt[1] = bt[0] – at[1] = 10 - 1 = 9
wt[2] = (bt[0] + bt[1]) – at[2] = (10+1) – 2.5 = 8.5
wt[3] = (bt[0] + bt[1] + bt[2]) – at[3] = (10+1+2) – 3 = 10
wt[4] = (bt[0] + bt[1] + bt[2] + bt[3]) – at[4] = (10+1+2+1) – 4.5 = 9.5
ð wt = [0, 9, 8.5, 10, 9.5]
Bước 3: Tìm TAT
tat[0] = wt[0] + bt[0] = 0 + 10 = 10
tat[1] = wt[1] + bt[1] = 9 + 1 = 10
tat[2] = wt[2] + bt[2] = 8.5 + 2 = 10.5
tat[3] = wt[3] + bt[3] = 10 + 1 = 11
tat[4] = wt[4] + bt[4] = 9.5 + 5 = 14.5
Bước 4:
Thời gian chờ đợi trung bình = (0+9+8.5+10+9.5) / 5 = 7.4
Bước 5:
Thời gian quay vòng trung bình = (10+10+10.5+11+14.5) / 5 = 11.2
P1 0 10 10 10 0
P2 1 1 11 10 9
P4 3 1 14 11 10
P1 0 10 3
P2 0 1 1
P3 0 2 3
P4 0 1 4
P5 0 5 2
Bước khởi tạo: Khai báo các giá trị và hàng đợi
bt = [10, 1, 2, 1, 5] //danh sách thời gian thực thi của tiến trình
at = [0, 0, 0, 0, 0] // danh sách thời gian tiến trình đến
rq = [p1] // hàng đợi sẵn sàng
p_current = null
quantum = 4
Vòng lặp thứ nhất với rq khác rỗng
Bước 1:
Lấy P1 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = []
p_current = p1
Bước 2:
Trong thời gian thực thi P1 thì có 6 tiến trình tới là P2, P3, P4, P5. Đưa
các tiến trình này vào hàng đợi RQ.
rq = [p2, p3, p4, p5]
Bước 3:
Sau khi thực hiện xong P1 với thời gian là quantum thì P1 vẫn còn 1 đơn
vị nữa chưa được thực thi và rq khác rỗng. Thêm P1 vào cuối hàng đợi.
rq = [p2, p3, p4, p5, p1]
P1 0 10 19 19 9
P2 0 1 5 5 4
P3 0 2 7 7 5
P4 0 1 8 8 7
P5 0 5 17 17 12
P1 0 5 3
P2 1 6 1
P3 2 3 3
P4 3 1 4
P5 4 5 2
P6 6 4 1
Bước khởi tạo: Khai báo các giá trị và hàng đợi
bt = [5, 6, 4, 1, 5, 4] //danh sách thời gian thực thi của tiến trình
at = [0, 1, 2, 3, 4, 6] // danh sách thời gian tiến trình đến
rq = [p1] // hàng đợi sẵn sàng
p_current = null
quantum = 4
Vòng lặp thứ nhất với rq khác rỗng
Bước 1:
Lấy P1 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = []
p_current = p1
Bước 2:
Trong thời gian thực thi P1 thì có 4 tiến trình tới là P2, P3, P4, P5. Đưa
các tiến trình này vào hàng đợi RQ.
rq = [p2, p3, p4, p5]
Bước 3:
Sau khi thực hiện xong P1 với thời gian là quantum thì P1 vẫn còn 1 đơn
vị nữa chưa được thực thi và rq khác rỗng. Thêm P1 vào cuối hàng đợi.
rq = [p2, p3, p4, p5, p1]
Vòng lặp thứ hai với rq khác rỗng
Bước 1:
Lấy P2 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = [p3, p4, p5, p1]
p_current = p2
Bước 2:
Trong thời gian thực thi P1 thì có 1 tiến trình tới là P6. Đưa các tiến trình
này vào hàng đợi RQ.
rq = [p3, p4, p5, p1, p6]
p_current = p2
Bước 3:
Sau khi thực hiện xong P2 với thời gian là quantum thì P2 vẫn còn 2 đơn
vị nữa chưa được thực thi và rq khác rỗng. Thêm P2 vào cuối hàng đợi.
rq = [p3, p4, p5, p1, p6, p2]
Vòng lặp thứ ba với rq khác rỗng
Bước 1:
Lấy P3 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = [p4, p5, p1, p6, p2]
p_current = p3
Bước 2:
Trong thời gian thực thi P3 thì không có tiến trình nào tới
rq = [p4, p5, p1, p6, p2]
p_current = p3
Bước 3:
Sau khi thực hiện xong P3 với thời gian là quantum thì P3 đã thực hiện
xong.
rq = [p4, p5, p1, p6, p2]
Vòng lặp thứ tư với rq khác rỗng
Bước 1:
Lấy P4 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = [p5, p1, p6, p2]
p_current = p4
Bước 2:
Trong thời gian thực thi P4 thì không có tiến trình nào tới
rq = [p5, p1, p6, p2]
p_current = p4
Bước 3:
Sau khi thực hiện xong P4 với thời gian là quantum thì P4 đã thực hiện
xong.
rq = [p5, p1, p6, p2]
Vòng lặp thứ năm với rq khác rỗng
Bước 1:
Lấy P5 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = [p1, p6, p2]
p_current = p5
Bước 2:
Trong thời gian thực thi P5 thì không có tiến trình nào tới
rq = [p1, p6, p2]
p_current = p5
Bước 3:
Sau khi thực hiện xong P5 với thời gian là quantum thì P5 thì P5 vẫn còn
tồn tại 1 đơn vị thời gian để thực hiện. Đưa P5 vào cuối hàng đợi.
rq = [p1, p6, p2, p5]
Vòng lặp thứ sáu với rq khác rỗng
Bước 1:
Lấy P1 từ RQ để thực thi với thời gian quantum là 4 đơn vị thời gian.
rq = [p6, p2, p5]
p_current = p1
Bước 2:
Trong thời gian thực thi P1 thì không có tiến trình nào tới
rq = [p6, p2, p5]
p_current = p1
Bước 3:
Sau khi thực hiện xong P1 với thời gian là quantum thì P1 đã hoàn tất.
rq = [p6, p2, p5]
P1 0 5 17 17 12
P2 1 6 23 22 16
P3 2 3 11 9 6
P4 3 1 12 9 8
P5 4 5 24 20 15
P6 6 4 21 15 11
P1 0 10 3
P2 1 1 1
P3 2.5 2 3
P4 3 1 4
P5 4.5 5 2
P1 0 10 10 10 0
P2 1 1 11 10 9
P4 3 1 19 16 15
P5 4.5 5 16 11.5 11
P1 0 10 3
P2 1 1 1
P3 2.5 2 3
P4 3 1 4
P5 4.5 5 2
• Tiến trình P1 là quá trình duy nhất có sẵn trong hàng đợi sẵn sàng vì thời gian
đến của nó là 0ms.
• Do đó, tiến trình P1 được thực thi trước tiên trong 1ms, từ 0ms đến 1ms, bất kể
mức độ ưu tiên của nó.
• Thời gian Burst còn lại (B.T) cho P1 = 10-1 = 9 ms.
Ở thời điểm t=1
• Tiến trình P1 là quá trình duy nhất có sẵn trong hàng đợi sẵn sàng
• Do đó, tiến trình P1 được thực thi trước tiên trong 0,5ms, từ 2ms đến 2,5ms, bất
kể mức độ ưu tiên của nó.
• Thời gian Burst còn lại (B.T) cho P1 = 9-0,5 = 8,5 ms.
Ở thời điểm t=2.5
• Có 3 tiến trình có sẵn trong hàng đợi sẵn sàng: P1 và P3, P4.
• Độ ưu tiên của P1 và P3 lớn hơn P4; P1 và P3 là như nhau, tuy nhiên P1 vào
trước nên được ưu tiên hơn.
• Do đó tiến trình P1 được thực thi trong 1,5ms, từ 3ms đến 4,5ms.
• Thời gian Burst còn lại (B.T) cho P1 = 8-1,5 = 6,5 ms.
Ở thời điểm t=4,5
• Có 4 tiến trình có sẵn trong hàng đợi sẵn sàng: P1 và P3, P4, P5.
• Độ ưu tiên của P5 là tốt nhất.
• Do đó tiến trình P5 được thực thi trong 5ms, từ 4,5ms đến 9,5ms.
• Thời gian Burst còn lại (B.T) cho P5 = 5-5 = 0 ms.
Ở thời điểm t=9,5
• Có 3 tiến trình có sẵn trong hàng đợi sẵn sàng: P1 và P3, P4.
• Độ ưu tiên của P1 và P3 lớn hơn P4; P1 và P3 là như nhau, tuy nhiên P1 vào
trước nên được ưu tiên hơn.
• Do đó tiến trình P1 được thực thi trong 6,5ms, từ 9,5ms đến 16ms..
• Thời gian Burst còn lại (B.T) cho P1 = 6,5-5,5 = 0 ms.
Ở thời điểm t=16
• Có 2 tiến trình có sẵn trong hàng đợi sẵn sàng: P3, P4.
• Độ ưu tiên của P3 lớn hơn P4;
• Do đó tiến trình P3 được thực thi trong 2ms, từ 16ms đến 18ms..
• Thời gian Burst còn lại (B.T) cho P3 = 2-2 = 0 ms.
Ở thời điểm t=18
P1 0 10 16 6 6
P2 1 1 2 1 0
P3 2.5 2 18 15.5 13,5
P4 3 1 19 16 15
P5 4.5 5 9,5 5 0
• Nó gắn liền với mỗi công việc như một đơn vị thời gian hoàn thành.
• Phương pháp thuật toán này hữu ích cho việc xử lý theo kiểu hàng loạt, trong đó
việc chờ đợi công việc hoàn thành là không quan trọng.
• Nó có thể cải thiện thông lượng tiến trình bằng cách đảm bảo rằng các công việc
ngắn hơn được thực hiện trước, do đó có thể có thời gian quay vòng ngắn.
• Nó cải thiện sản lượng công việc bằng cách cung cấp các công việc ngắn hơn,
cần được thực hiện trước, hầu hết có thời gian quay vòng ngắn hơn.
4.3.3.2 Thuật toán
Tính thời gian t: thời gian xử lý mà tiến trình còn yêu cầu
!
Độ ưu tiên 𝑝 = "
Ưu tiên xử lý tiến trình có 𝑝 lớn nhất. Không tính toán lại 𝑝 khi có tiến trình mới đến.
P1 0 10 3
P2 1 1 1
P3 2.5 2 3
P4 3 1 4
P5 4.5 5 2
P1 0 10 10 10 0
P2 1 1 11 10 9
P4 3 1 12 9 8
P1 0 10 3
P2 1 1 1
P3 2.5 2 3
P4 3 1 4
P5 4.5 5 2
P1 0 10 19 19 9
P2 1 1 2 1 1
P3 2.5 2 5.5 3 1
P4 3 1 4 1 0
P5 4.5 5 10.5 6 1
• Firt-come First-serve
• Round Robin với q = 2
• Thuật toán độ ưu tiên
• Shortest Job First (SJF)
• Shortest Remaining Time First (SRTF)
Bài 2. Hiện thực các thuật toán điều phối sau dùng ngôn ngữ lập trình Python:
• Firt-come First-serve
• Round Robin với q = 2
• Thuật toán độ ưu tiên
• Shortest Job First (SJF)
• Shortest Remaining Time First (SRTF)
P1 0 7 2
P2 1 1 1
P3 2.5 2 3
P4 3 1 4
P5 4.5 4 2
Hãy cho biết kết quả điều phối theo các chiến lược
• Firt-come First-serve
• Round Robin với q = 2
• Thuật toán độ ưu tiên
• Shortest Job First (SJF)
• Shortest Remaining Time First (SRTF)
Tính thời gian chờ cho từng tiến trình và thời gian chờ trung bình trong các chiến lược
trên.
Bài 4. Xét tập hợp các tiến trình sau:
P1 0 4 2
P2 1 1 1
P3 2 2 3
P4 3.5 1 4
P5 4 6 2
Hãy cho biết kết quả điều phối theo các chiến lược
• FCFS
• Round Robin với q = 2
• Thuật toán độ ưu tiên
• Shortest Job First
• Shortest Remaining Time First (SRTF)
Tính thời gian chờ cho từng tiến trình và thời gian chờ trung bình trong các chiến lược
trên.