Các lệnh của Linux thực tế l à nh ững lệnh ri êng l ẻ có khả năng kết hợp v à truy ền dữ liệu cho nhau thông qua các cơ chế như : đườ ng ống pipe, chuyển hướng xuất nhập (redirect), ph[r]
(1)Bài 3: XỬ LÝ TIẾN TRÌNH TRONG LINUX I Lý Thuyết
1 Khái quát
- Một đặc điểm bật Linux khả chạy đồng thời nhiều chương trình Hệ Điều Hành xem đơn thể mã lệnh mà điều khiển tiến trình (process) Một chương trình bao gồm nhiều
tiến trình kết hợp với
- Đối với Hệ Điều Hành, tiến trình hoạt động chia sẻ tốc độ xử lý CPU, dùng chung vùng nhớ tài nguyên hệ thống khác Các tiến trình điều phối xoay vịng Hệ Điều Hành Một chương
trình mở rộng dần ra, có lúc cần phải tách thành nhiều tiến trình để xử lý công
việc độc lập với Các lệnh Linux thực tế lệnh riêng lẻ có khả kết hợp truyền liệu cho thông qua chế : đường ống pipe, chuyển hướng xuất nhập (redirect), phát sinh tín hiệu (signal), … Chúng gọi chế giao tiếp liên tiến trình (IPC – Inter Process Comunication) Đối với
tiến trình, tìm hiểu cách tạo, hủy, tạm dừng tiến trình, đồng hóa tiến trình giao tiếp
tiến trình với
- Xây dựng ứng dụng môi trường đa tiến trình Linux cơng việc khó khăn Không môi trường đơn nhiệm, môi trường đa nhiệm tiến trình có tài ngun hạn hẹp Tiến trình hoạt động phải ln trạng thái tôn trọng sẵn sàng nhường quyền xử lý CPU cho tiến trình khác
thời điểm nào, hệ thống có yêu cầu Nếu tiến trình xây dựng khơng tốt, đổ vỡ gây lỗi, làm treo tiến trình khác hệ thống hay chí phá vỡ (crash) Hệ Điều Hành - Định nghĩa tiến trình: một thực thể điều khiển đoạn mã lệnh có riêng khơng gian địa chỉ, có ngăn xếp stack riêng rẽ, có bảng chứa thông số mô tả file mở tiến trình đặc biệt có định
danh PID (Process Identify) toàn hệ thống vào thời điểm tiến trình chạy
Như thấy, tiến trình khơng phải chương trình (tuy đơi lúc chương trình đơn giản
cấn tiến trình để hồn thành tác vụ, trường hợp xem tiến trình
chương trình một) Rất nhiều tiến trình thực thi máy với Hệ Điều Hành, người dùng nhiều người dùng đăng nhập khác Ví dụ shell bash tiến trình
thực thi lệnh ls hay cp Bản thân ls, cp lại tiến trình hoạt động tách biệt khác
- Trong Linux, tiến trình cấp không gian địa nhớ phẳng 4GB Dữ liệu tiến trình khơng thể đọc truy xuất tiến trình khác Hai tiến trình khác xâm phạm biến
Tuy nhiên, muốn chia sẻ liệu hai tiến trình, Linux cung cấp cho vùng không gian địa chung để làm điều
2 Cách hoạt động tiến trình
- Khi chương trình chạy từ dịng lệnh, nhấn phím Ctrl+z để tạm dùng chương trình
và đưa vào hoạt động phía hậu trường (background) Tiến trình Linux có trạng thái:
+ Đang chạy (running) : lúc tiến trình chiếm quyền xử lý CPU dùng tính tốn hay thực cơng việc
của
+ Chờ (waiting) : tiến trình bị Hệ Điều Hành tước quyền xử lý CPU, chờ đến lược cấp phát khác
+ Tạm dừng (suspend) : Hệ Điều Hành tạm dừng tiến trình Tiến trình đưa vào trạng thái ngủ (sleep)
Khi cần thiết có nhu cầu, Hệ Điều Hành đánh thức (wake up) hay nạp lại mã lệnh tiến trình vào
nhớ Cấp phát tài nguyên CPU để tiến trình tiếp tục hoạt động
- Trên dịng lệnh, thay dùng lệnh Ctrl+z, sử dụng lệnh bg để đưa tiến trình vào hoạt động phía hậu trường Chúng ta yêu cầu tiến trình chạy cú pháp & Ví dụ: $ls –R & Lệnh fg sẽ đem tiến trình trở hoạt động ưu tiên phía trước Thực tế đăng nhập vào hệ thống tương tác dòng lệnh, lúc tiến trình shell bash Khi gọi lệnh có
nghĩa yêu cầu bash tạo thêm tiến trình thực thi khác Về mặt lập trình,
dùng lệnh fork() để nhân tiến trình từ tiến trình cũ Hoặc dùng lệnh system() để triệu gọi
tiến trình Hệ Điều Hành Hàm exec() có khả tạo tiến trình khác
3 Cấu trúc tiến trình
- Chúng ta xem Hệ Điều Hành quản lý tiến trình nào? Nếu có hai người dùng: user1 user2 đăng nhập vào chạy chương trình grep đồng thời, thực tế, Hệ Điều Hành
quản lý nạp mã chương trình grep vào hai vùng nhớ khác
nhau gọi phân vùng tiến trình Hình sau cho thấy cách phân chia chương trình grep thành hai tiến trình cho hai người khác sử dụng
Trong hình này, user1 chạy chương trình grep tìm chuỗi abc tập tin file1
$grep abc file1
user2 chạy chương trình grep tìm chuỗi cde tập tin
user1 $grep abc file1
PID 101
Code
Data s=abc
Library
filede
file1
user2 $grep cde file2
PID 102
Code
Data s=cde
Library
filede
file2 mã lệnh grep
Thư viện C
(2)2 file2
$grep cde file2
Chúng ta cần ta cần nhớ hai người dùng user1 user2 hai máy tính khác đăng nhập vào máy chủ Linux gọi grep chạy đồng thời Hình trạng không gian nhớ Hệ Điều Hành Linux
khi chương trình grep phục vụ người dùng
- Nếu dùng lệnh ps, hệ thống liệt kê cho thơng tin tiến trình mà Hệ Điều Hành kiểm
sốt, Ví dụ: $ps –af
Mỗi tiến trình gán cho định danh để nhận dạng gọi PID (process identify) PID thường số nguyên dương có giá trị từ 2-32768 Khi tiến trình yêu cầu khởi động, Hệ Điều Hành chọn lấy
số (chưa bị tiến trình chạy chiếm giữ) khoảng số nguyên cấp phát cho tiến trình
Khi tiến trình chấm dứt, hệ thống thu hồi số PID để cấp phát cho tiến trình khác lần sau PID bắt đầu
từ giá trị giá trị dành cho tiến trình gọi init Tiến trình init chạy
khi khởi động Hệ Điều Hành init tiến trình quản lý tạo tiến trình khác Ở ví dụ
trên, thấy lệnh ps –af sẽ hiển thị tiến trình grep chạy user1 user2 với số PID lần lượt 101 102
- Mã lệnh thực thi lệnh grep chứa tập tin chương trình nằm đĩa cứng Hệ Điều Hành nạp
vào vùng nhớ Như thấyở lược đồ trên, tiến trình Hệ Điều hành phân chia rõ ràng: vùng chứa mã lệnh (code) vùng chứa liệu (data) Mã lệnh thường giống sử dụng chung
Linux quản lý cho phép tiến trình chương trình sử dụng chung mã lệnh
Thư viện Trừ thư viện đặc thù cịn thư viện chuẩn Hệ Điều Hành cho phép chia sẻ dùng chung tiến trình hệ thống Bằng cách chia sẻ thư viện, kích thước chương trình giảm đáng kể Mã lệnh chương trình chạy hệ thống dạng tiến trình đỡ tốn nhớ
- Trừ mã lệnh thư viện chia sẻ, cịn liệu khơng thể chia sẻ tiến trình Mỗi tiến trình sở
hữu phân đoạn liệu riêng Ví dụ tiến trình grep user1 nắm giữ lưu giữ biến s có giá trị 'abc', grep user2 nắm giữ lại có biến s với giá trị 'cde'
Mỗi tiến trình hệ thống dành riêng cho bảng mô tả file (file description table) Bảng chứa
các số mô tả áp đặt cho file mở Khi tiến trình khởi động, thường Hệ Điều Hành mở sẳn
cho file : stdin (số mô tả 0), stdout (số mô tả 1), stderr (số mô tả 2) Các file tượng trưng cho thiết bị nhập, xuất, thơng báo lỗi Chúng ta mở thêm file khác Ví dụ user1 mở
file file1, user2 mở file file2 Hệ Điều Hành cấp phát số mơ tả file cho tiến trình lưu riêng
chúng bảng mô tả file tiến trình
- Ngồi ra, tiến trình có riêng ngăn xếp stack để lưu biến cục giá trị trả sau lời gọi hàm Tiến
trình dành cho khoảng khơng gian riêng để lưu biến môi trường Chúng ta dùng lệnh
putenv getenv để đặt riêng biến mơi trường cho tiến trình a) Bảng thơng tin tiến trình
- Hệ Điều Hành lưu giữ cấu trúc danh sách bên hệ thống gọi bảng tiến trình (process table) Bảng
tiến trình quản lý tất PID của hệ thống với thơng tin chi tiết tiến trình chạy Ví dụng
chúng ta gọi lệnh ps, Linux thường đọc thơng tin bảng tiến trình hiển thị lệnh hay tên tiến
trình gọi: thời gian chiếm giữ CPU tiến trình, tên người sử dụng tiến trình, … b) Xem thơng tin tiến trình
- Lệnh ps của Hệ Điều Hành dùng để hiển thị thông tin chi tiết tiến trình Tùy theo tham số, ps sẽ cho biết
thơng tin tiến trình người dùng, tiến trình hệ thống tất tiến trình chạy Ví dụ ps sẽ đưa chi tiết tham số -af
- Trong thông tin ps trả về, UID tên của người dùng gọi tiến trình, PID số định danh mà hệ
thống cấp cho tiến trình, PPID số định danh tiến trình cha (parent PID) Ở gặp số
tiến trình có định danh PPID 1, định danh tiến trình init, gọi chạy hệ thống khởi động
Nếu hủy tiến trình init Hệ Điều Hành chấm dứt phiên làm việc STIME thời điểm tiến
trình đưa vào sử dụng TIME thời gian chiếm dụng CPU tiến trình CMD tồn dịng lệnh
tiến trình triệu gọi TTY hình terminal ảo nơi gọi thực thi tiến trình Như biết, người
dùng đăng nhập vào hệ thống Linux từ nhiều terminal khác để gọi tiến trình Để liệt kê tiến
trình hệ thống, sử dụng lệnh: $ps –ax 4 Tạo lập tiến trình
a) Gọi tiến trình hàm system()
- Chúng ta gọi tiến trình khác bên chương trình thực thi hàm system() Có nghĩa tạo tiến trình từ tiến trình chạy Hàm system() khai báo sau:
#include <stdlib.h>
int system( const char (cmdstr) )
(3)Khởi tạo tiến trình
Gọi fork()
Mã lệnh tiếp tiến trình ban đầu (tiến trình cha)
Mã lệnh thực thi tiến trình (tiến
trình con)
Cơ chế phân chia tiến trình fork() Trả PID tiến trình
con Trả trị
với việc bạn gọi shell thực thi lệnh hệ thống: $sh –c cmdstr
system()sẽ trả mã lỗi 127 không khởi động shell để gọi lệnh cmdstr Mã lỗi -1 gặp
các lỗi khác Còn lại, mã trả system() mã lỗi cmdstr sau lệnh gọi trả Ví dụ sử dụng hàm system(), system.c
#include <stdlib.h> #include <stdio.h> int main()
{
printf( "Thuc thi lenh ps voi system\n" ); system( "ps –ax" );
system(“mkdir daihoc”); system(“mkdir caodang”);
printf( "Thuc hien xong \n" ); exit( );
}
Hàm system() sử dụng để gọi lệnh “ps –ax” của Hệ Điều Hành b) Thay tiến trình hành với hàm exec
- Mỗi tiến trình Hệ Điều Hành cấp cho khơng gian nhớ tách biệt để tiến trình tự hoạt động Nếu tiến
trình A triệu gọi chương trình ngồi B (bằng hàm system()chẳng hạn), Hệ Điều Hành
thường thực thao tác như: cấp phát không gian 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 chương trình B đĩa cứng không gian nhớ vừa cấp phát cho tiến trình
Đưa tiến trình vào danh sách cần điều phối Hệ Điều Hành Những công việc thường thời gian đáng kể chiếm giữ thêm tài nguyên hệ thống
Nếu tiến trình A chạy muốn tiến trình B khởi động chạy khơng gian nhớ có sẵn tiến trình A sử dụng hàm exec cung cấp bới Linux Các hàm exec thay
toàn ảnh tiến trình A (bao gồm mã lệnh, liệu, bảng mơ tả file) thành ảnh tiến trình B hồn tồn khác Chỉ có số định danh PID của tiến trình A cịn giữ lại Tập hàm exec bao gồm 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ố hàm yêu cầu đối số path hoặc file đường dẫn đến tên chương trình cần
thực thi đĩa arg đối số cần truyền cho chương trình thực thi, đối số tương tự cách
chúng ta gọi chương trình từ dịng lệnh
c) Nhân tiến trình với hàm fork()
- Thay tiến trình đơi bất lợi với Đó tiến trình chiếm giữ tồn khơng gian tiến
trình cũ khơng có khả kiểm sốt
cũng điều khiển tiếp tiến trình hành sau gọi hàm exec nữa Cách đơn giản mà
chương trình Linux thường dùng sử dụng hàm fork() để nhân hay tạo tiến trình fork() một hàm đặc biệt, thực thi, trả
về giá trị khác lần thực thi, so với hàm bình
thường trả giá trị lần thực thi Khai báo
của hàm fork() sau:
#include <sys/types.h> #include <unistd.h> pid_t fork()
- Nếu thành công, fork() tách tiến trình hành tiến trình (dĩ nhiên Hệ Điều Hành phải cấp phát thêm không gian nhớ để tiến trình hoạt động) Tiến
trình ban đầu gọi tiến trình cha (parent process) tiến trình gọi tiến trình (child process) Tiến trình có số định danh PID riêng biệt
ngồi ra, tiến trình cịn mang thêm định danh
(4)4
- Sau tách tiến trình, mã lệnh thực thi hai tiến trình chép hồn tồn giống Chỉ có
một dấu hiệu để nhận dạng tiến trình cha tiến trình con, trị trả hàm fork() Bên tiến trình con, hàm fork() trả trị Trong bên tiến trình cha, hàm fork() trả
về trị số nguyên PID tiến trình vừa tạo Trường hợp khơng tách tiến trình, fork()sẽ trả
về trị -1 Kiểu pid_t khai báo định nghĩa uinstd.h kiểu số nguyên (int) - Đoạn mã điều khiển 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 đặt đây break;
default: printf( "Day la tien trinh cha" ); // mã lệnh dành cho tiến trình cha đặt đây break;
}
d) Kiểm sốt đợi tiến trình
- Khi fork() tách tiến trình thành hai tiến trình cha con, thực tế hai tiến trình cha lẫn tiến
trình hoạt động độc lập Đơi lúc tiến trình cha cần phải đợi tiến trình thực xong tác vụ tiếp tục thực thi Ở ví dụ trên, thực thi, thấy tiến trình cha kết thúc mà tiến trình in thơng báo tiến trình cha tiến trình tranh gởi kết hình Chúng ta không muốn điều này, muốn tiến trình cha kết thúc tiến trình hồn tất thao tác
của Hơn nữa, chương trình cần thực xong tác vụ đến chương trình cha Để làm
được việc này, sử dụng hàm wait() #include <sys/types.h>
#include <sys/wait.h> pid_t wait(int &stat_loc);
Hàm wait gọi yêu cầu tiến trình cha dừng lại chờ tiến trình kết thúc trước thực tiếp
các lệnh điều khiển tiến trình cha wait() làm cho liên hệ tiến trình cha tiến trình trở
nên Khi tiến trình kết thúc, hàm trả số PID tương ứng tiến trình Nếu
truyền thêm đối số stat_loc khác NULL cho hàm wait() cũng trả trạng thái mà tiến trình kết thúc biến stat_loc Chúng ta có thể sử dụng macro khai báo sẵn sys/wait.h
sau:
WIFEXITED (stat_loc) Trả trị khác tiến trình kết thúc bình thường
WEXITSTATUS (stat_loc) Nếu WIFEXITED trả trị khác 0, macro trả mã lỗi tiến
trình
WIFSIGNALED (stat_loc) Trả trị khác tiến trình kết thúc tín hiệu gửi đến WTERMSIG(stat_loc) Nếu WIFSIGNALED khác 0, macro sẽ cho biết số tín hiệu hủy tiến
trình
WIFSTOPPED(stat_loc) Trả trị khác tiến trình dừng
(5)II Thực Hành
Bài Sử dụng hàm system(), system_demo.c tạo tiến trình sau: Tạo thư mục ThucHanh1 ThucHanh2
Tạo tập tin Tho.c thư mục ThucHanh1 ghi chuỗi “troi hom that dep !” vào tập
tin vừa tạo (sử dụng lệnh echo để ghi chuỗi vào tập tin: echo noi_dung_chuoi >ten_tap_tin) Sao chép tập tin vừa tạo sang thư mục ThucHanh2 hiển thị lên hình
Bài Sử dụng hàm execlp để thay tiến trình tiến trình ps –af Hệ Điều Hành #include <unistd.h>
#include <stdio.h> int main()
{
printf( "Thuc thi lenh ps voi execlp\n" ); execlp( "ps", "ps", "–ax", );
printf( "Thuc hien xong! Nhung chung ta se khong thay duoc dong nay.\n" );
exit( ); }
Bài Tạo tập tin fork_demo.c sử dụng hàm fork() trong đó:
In câu thơng báo: “Khong the tao tien trinh !” hàm fork() trả giá trị -1 Ngược lại:
- In lần câu thông báo: “Day la tien trinh !” nếu mã trả
- In lần câu thông báo: “Day la tien trinh cha !” nếu mã trả PID tiến
trình
#include <sys/types.h> #include <unistd.h> #include <stdio.h> int main()
{
pid_t pid; char * message; int n;
pid = fork(); switch ( pid )
{
case -1:
printf( "Khong the tao tien trinh !" ); exit(1);
case 0:
message = "Day la tien trinh !";
n = 0;
for ( ; n < 5; n++ ) {
printf( "%s", message );
sleep( );
}
break;
default:
message = "Day la tien trinh cha !";
n = 0;
for ( ; n < 3; n++ ) {
printf( "%s", message );
sleep( );
}
break;
}
exit( ); }
Biên dịch thực thi chương trình này, thấy tiến trình hoạt động đồng thời in kết
quả đan xen Nếu muốn xem liên quan PID PPID của tiến trình cha lệnh
fork() phát sinh, có thể thực chương trình sau:
(6)6
Bài Sử dụng hàm wait() để chờ tiến trình kết thúc sau gọi fork(), wait_child.c #include <unistd.h>
#include <stdio.h> #include <sys/wait.h> #include <sys/types.h> int main()
{
pid_t pid;
int child_status; int n;
// nhân tiến trình, tạo pid = fork();
switch ( pid ) {
case -1: // fork khơng tạo tiến trình mới
printf("Khong the tao tien trinh moi");
exit( );
case 0: // fork thành công, đang ở tiến trình con
n = 0;
for ( ; n < 5; n++ ) {
printf( "Tien trinh con" );
sleep( );
}
exit( ); // Mã lỗi trả tiến trình default: // fork thành công, tiến trình cha
printf("Tien trinh cha, cho tien trinh hoan thanh.\n”); // Chờ tiến trình kết thúc
wait( &child_status );
printf("Tien trinh cha – tien trinh hoan thanh.\n"); }
return ( ); }
Bài Sử dụng hàm wait() để chờ tiến trình kết thúc sau gọi fork(), wait_child2.c, kiểm
tra mã lỗi trả từ tiến trình #include <unistd.h> #include <stdio.h> #include <sys/wait.h> #include <sys/types.h> int main()
{
pid_t pid;
int child_status; int n;
// nhân bản tiến trình, tạo pid = fork();
switch ( pid ) {
case -1: // fork khơng tạo tiến trình mới
printf("Khong the tao tien trinh moi");
exit( );
case 0: // fork thành công, tiến trình con
n = 0;
for ( ; n < 5; n++ ) {
printf( "Tien trinh con" );
sleep( );
}
exit( 37 ); // Mã lỗi trả tiến trình default: // fork thành công, đang ở tiến trình cha
n = 3;
for ( ; n > 0; n ) {
printf( "Tien trinh cha" );
sleep( );
(7)// Chờ tiến trình kết thúc wait( &child_status );
// Kiểm tra in mã lỗi trả tiến trình con
printf( "Tien trinh hoan thanh: PID = %d\n", pid ); if ( WIFEXITED( child_status ))
printf( "Tien trinh thoat voi ma %d\n",
WEXITSTATUS( child_status ) );
else
printf( "Tien trinh ket thuc binh thuong\n" );
break;
}