Nguyên lý hệ điều hành - 7

2.1.4 Hợp tác giữa các tiến trình

Các tiến trình đồng thời thực hiện trong hệ điều hành có thể là những tiến trình độc lập hay những tiến trình hợp tác. Một tiến trình là độc lập (independent) nếu nó không thể ảnh hưởng hay bị ảnh hưởng bởi các tiến trình khác thực hiện trong hệ thống. Rò ràng, bất kỳ một tiến trình không chia sẻ bất cứ dữ liệu (tạm thời hay cố định) với tiến trình khác là độc lập. Ngược lại, một tiến trình là hợp tác (cooperating) nếu nó có thể ảnh hưởng hay bị ảnh hưởng bởi các tiến trình khác trong hệ thống. Hay nói cách khác, bất cứ tiến trình chia sẻ dữ liệu với tiến trình khác là tiến trình hợp tác.

Chúng ta có thể cung cấp một môi trường cho phép các tiến trình hợp tác với nhiều lý do:

- Chia sẻ thông tin: vì nhiều người dùng có thể quan tâm cùng thông tin (thí dụ tập tin chia sẻ), hệ điều hành phải cung cấp một môi trường cho phép truy xuất đồng hành tới những loại tài nguyên này.

- Gia tăng tốc độ tính toán: nếu chúng ta muốn một công việc chạy nhanh hơn, chúng ta phải chia nó thành những công việc nhỏ hơn, mỗi công việc sẽ thực hiện song song với các tác vụ khác. Việc tăng tốc như thế có thể đạt được chỉ nếu máy tính có nhiều thành phần đa xử lý (như nhiều CPU hay các kênh I/O).

- Tính module hóa: chúng ta muốn xây dựng hệ thống thành các module, chia các chức năng hệ thống thành những tiến trình hay luồng.

- Tính tiện dụng: một người sử dụng có thể có nhiều công việc thực hiện tại cùng thời điểm. Thí dụ, một người dùng có thể đang soạn thảo, in, và biên dịch cùng một lúc.

Việc thực hiện đồng thời của các tiến trình hợp tác sẽ yêu cầu các cơ chế cho phép các tiến trình giao tiếp với các tiến trình khác và đồng bộ hóa các hoạt động của chúng.

Để minh họa khái niệm của các tiến trình cộng tác, chúng ta xem xét bài toán người sản xuất - người tiêu thụ, là mô hình chung cho các tiến trình hợp tác. Tiến trình người sản xuất cung cấp thông tin được tiêu thụ bởi tiến trình người tiêu thụ. Thí dụ, một chương trình in sản xuất các ký tự được tiêu thụ bởi trình điều khiển máy in. Một trình biên dịch có thể sản xuất mã hợp ngữ được tiêu thụ bởi trình hợp ngữ. Sau đó, trình hợp ngữ có sản xuất module đối tượng, được tiêu thụ bởi bộ nạp.

Để cho phép người sản xuất và người tiêu thụ chạy đồng hành, chúng ta phải có sẵn một vùng đệm chứa các sản phẩm có thể được điền vào bởi người sản xuất và được lấy đi bởi người tiêu thụ. Người sản xuất có thể sản xuất một sản phẩm trong khi người tiêu thụ đang tiêu thụ một sản phẩm khác. Người sản xuất và người tiêu thụ phải được đồng bộ để người tiêu thụ không tiêu thụ một sản phẩm mà chưa được sản xuất. Trong trường hợp này, người tiêu thụ phải chờ cho tới khi các sản phẩm mới được tạo ra.

Có thể bạn quan tâm!

Xem toàn bộ 306 trang tài liệu này.

Bài toán người sản xuất - người tiêu thụ với vùng đệm không bị giới hạn (unbounded-buffer) thiết lập không giới hạn kích thước của vùng đệm. Người tiêu thụ có thể phải chờ các sản phẩm mới nhưng người sản xuất có thể luôn tạo ra sản phẩm mới. Vấn đề người sản xuất - người tiêu thụ với vùng đệm có kích thước giới hạn (bounded-buffer) đảm bảo một kích thước cố định cho vùng đệm. Trong trường hợp này, người tiêu thụ phải chờ nếu vùng đệm rỗng, và người sản xuất phải chờ nếu vùng đệm đầy.

Vùng đệm có thể được cung cấp bởi hệ điều hành thông qua việc sử dụng phương tiện giao tiếp liên tiến trình (interprocess communication - IPC), hay được mã hóa cụ thể bởi người lập trình ứng dụng với việc sử dụng bộ nhớ được chia sẻ. Để chúng ta hiển thị một giải pháp chia sẻ bộ nhớ đối với vấn đề vùng đệm bị giới hạn (bounded-buffer). Tiến trình người sản xuất và người tiêu thụ chia sẻ các biến sau:

#define BUFFER_SIZE 10 typedef struct{

} item;

item buffer[BUFFER_SIZE]; int in = 0;

int out = 0;

Vùng đệm được chia sẻ được cài đặt như một mảng vòng với hai con trỏ luận lý: in out. Biến in chỉ tới vị trí trống kế tiếp trong vùng đệm; out chỉ tới vị trí đầy đầu tiên trong vùng đệm. Vùng đệm là rỗng khi in==out; vùng đệm là đầy khi ((in + 1)%BUFFER_SIZE) ==out.

Mã lệnh cho tiến trình người sản xuất và người tiêu thụ được trình bày dưới đây. Tiến trình người sản xuất có một biến nextProduced trong đó sản phẩm mới được tạo ra và được lưu trữ:

while (1) {

/*produce an item in nextProduced*/ while (((in + 1)%BUFFER_SIZE) ==out)

; /*do nothing*/

buffer[in] = nextProduced;

in = (in + 1) % BUFFER_SIZE;

}

Tiến trình người tiêu thụ có biến cục bộ nextConsumed trong đó sản phẩm được tiêu thụ và được lưu trữ:

while (1){

while (in==out)

; //nothing

nextConsumed = buffer[out];

out = (out + 1) % BUFFER_SIZE;

/*consume the item in nextConsumed*/

}

Cơ chế này cho phép nhiều nhất BUFFER_SIZE –1 trong vùng đệm tại cùng một thời điểm.

Trong phần trên chúng ta đã hiển thị cách mà các tiến trình hợp tác có thể giao tiếp với nhau trong một môi trường chia sẻ bộ nhớ. Cơ chế yêu cầu các tiến trình này chia sẻ nhóm vùng đệm chung và mã cho việc cài đặt vùng đệm được viết trực tiếp bởi người lập trình ứng dụng. Một cách khác đạt được cùng ảnh hưởng cho hệ điều hành là cung cấp phương tiện cho các tiến trình hợp tác giao tiếp với nhau bằng một phương tiện giao tiếp liên tiến trình (IPC). IPC cung cấp một cơ chế cho phép một tiến trình giao tiếp và đồng bộ các hoạt động của chúng mà không chia sẻ cùng không gian địa chỉ. IPC đặc biệt có ích trong môi trường phân tán nơi các tiến trình giao tiếp có thể thường trú trên các máy tính khác được nối kết qua mạng. Thí dụ chương trình chat được dùng trên World Wide Web.

IPC được cung cấp bởi hệ thống truyền thông điệp, và các hệ thống truyền thông điệp có thể được định nghĩa trong nhiều cách. Trong phần này chúng ta sẽ xem xét những vấn đề khác nhau khi thiết kế các hệ thống truyền thông điệp.

2.1.5 Luồng

1) Tổng quan

Một luồng là đơn vị cơ bản của việc sử dụng CPU; nó bao gồm: một định danh luồng (thread ID), một bộ đếm chương trình, tập thanh ghi và ngăn xếp. Nó chia sẻ với các luồng khác thuộc cùng một tiến trình phần mã, phần dữ liệu, và tài nguyên hệ điều hành như các tập tin đang mở. Một tiến trình truyền thống có một luồng điều khiển đơn. Nếu tiến trình có nhiều luồng điều khiển, nó có thể thực hiện nhiều hơn một công việc tại một thời điểm. Hình 2.9 hiển thị sự khác nhau giữa tiến trình đơn luồng và tiến trình đa luồng.

- Sự cơ động

Nhiều phần mềm chạy trên các máy tính là đa luồng. Một ứng dụng có thể được cài đặt như một tiến trình riêng rẽ với nhiều luồng điều khiển, điển hình như trình duyệt Web có thể có một luồng hiển thị hình ảnh, văn bản trong khi một luồng khác lấy dữ liệu từ mạng. Một trình soạn thảo văn bản có thể có một luồng hiển thị đồ họa, luồng thứ hai đọc sự bấm phím trên bàn phím từ người dùng, một luồng thứ ba thực hiện việc kiểm tra chính tả và từ vựng chạy trong chế độ nền.

Hình 2 9 Tiến trình đơn và đa luồng Trong những trường hợp cụ thể một ứng 1

Hình 2.9 Tiến trình đơn và đa luồng

Trong những trường hợp cụ thể một ứng dụng đơn có thể được yêu cầu thực hiện nhiều công việc đơn. Thí dụ, một trình phục vụ web chấp nhận các yêu cầu khách hàng như trang web, hình ảnh, âm thanh, ... Một trình phục vụ web có thể có

nhiều (hàng trăm) khách hàng truy xuất đồng thời nó. Nếu trình phục vụ web chạy như một tiến trình đơn luồng truyền thống thì nó sẽ có thể chỉ phục vụ một khách hàng tại cùng thời điểm. Lượng thời gian mà khách hàng phải chờ yêu cầu của nó được phục vụ là rất lớn.

Một giải pháp là có một trình phục vụ chạy như một tiến trình đơn chấp nhận các yêu cầu. Khi trình phục vụ nhận một yêu cầu, nó sẽ tạo một tiến trình riêng để phục vụ yêu cầu đó. Phương pháp tạo ra tiến trình này là cách sử dụng thông thường trước khi luồng trở nên phổ biến. Tạo ra tiến trình có ảnh hưởng rất lớn như được trình bày ở chương trước. Nếu tiến trình mới sẽ thực hiện cùng công việc như tiến trình đã có thì tại sao lại gánh chịu tất cả chi phí đó? Thường sẽ hiệu quả hơn cho một tiến trình chứa nhiều luồng phục vụ cùng một mục đích. Tiếp cận này sẽ đa luồng tiến trình trình phục vụ web. Trình phục vụ sẽ tạo một luồng riêng kiểm tra các yêu cầu người dùng; khi yêu cầu được thực hiện nó không tạo ra tiến trình khác mà sẽ tạo một luồng khác phục vụ yêu cầu.

Luồng cũng đóng một vai trò quan trọng trong hệ thống lời gọi thủ tục từ xa (Remote Process Call - RPC). RPCs cho phép giao tiếp liên tiến trình bằng cách cung cấp cơ chế giao tiếp tương tự như các lời gọi hàm hay thủ tục thông thường. Điển hình, các trình phục vụ RPCs là đa luồng. Khi một trình phục vụ nhận một thông điệp, nó phục vụ thông điệp dùng một luồng riêng. Điều này cho phép phục vụ nhiều yêu cầu đồng thời.

- Thuận lợi

+ Sự đáp ứng: đa luồng một ứng dụng giao tiếp cho phép một chương trình tiếp tục chạy thậm chí nếu một phần của nó bị khóa hay đang thực hiện một thao tác có thời gian thực hiện dài, do đó gia tăng sự đáp ứng đối với người dùng. Thí dụ, một trình duyệt web vẫn có thể đáp ứng người dùng bằng một luồng trong khi một ảnh đang được nạp bằng một luồng khác.

+ Chia sẻ tài nguyên: các luồng của một tiến trình chia sẻ bộ nhớ và các tài nguyên đã được cấp cho tiến trình đó. Thuận lợi của việc chia sẻ mã lệnh là nó cho phép một ứng dụng có nhiều hoạt động của các luồng khác nhau nằm trong cùng không gian địa chỉ.

+ Kinh tế: cấp phát bộ nhớ và các tài nguyên cho việc tạo các tiến trình là rất tốn kém. Vì các luồng trong một tiến trình chia sẻ chung tài nguyên của tiến trình nên sẽ kinh tế hơn để tạo và chuyển trạng thái giữa các luồng. Rất khó để đánh giá sự khác biệt chi phí cho việc tạo và duy trì một tiến trình so với một luồng, nhưng thường sẽ mất nhiều thời gian để tạo và quản lý một tiến trình hơn một luồng. Trong Solaris 2, tạo một tiến trình chậm hơn khoảng 30 lần tạo một luồng và chuyển đổi trạng thái chậm hơn 5 lần.

+ Sử dụng kiến trúc đa xử lý: các ưu điểm của đa luồng có thể phát huy rất tốt trong kiến trúc đa xử lý, ở đó mỗi luồng song song thực hiện trên một bộ xử lý khác nhau. Một tiến trình đơn luồng chỉ có thể chạy trên một CPU. Đa luồng trên một máy nhiều CPU gia tăng tính đồng hành. Trong kiến trúc đơn xử lý, CPU thường chuyển đổi qua lại giữa mỗi luồng quá nhanh để tạo ra hình ảnh của sự song song nhưng trong thực tế chỉ một luồng đang chạy tại một thời điểm.

2) Luồng người sử dụng và luồng nhân

- Luồng người sử dụng: được hỗ trợ bởi nhân và được cài đặt thư viện luồng tại cấp người dùng. Thư viện cung cấp hỗ trợ cho việc tạo luồng, lập lịch, và quản lý mà không có sự hỗ trợ từ nhân. Vì nhân không biết các luồng cấp người dùng, tất cả việc tạo luồng và lập thời biểu được thực hiện trong không gian người dùng mà không cần sự can thiệp của nhân. Do đó, các luồng cấp người dùng thường tạo và quản lý nhanh chóng, tuy nhiên chúng cũng có những trở ngại. Thí dụ, nếu nhân là đơn luồng thì bất cứ luồng cấp người dùng thực hiện một lời gọi hệ thống nghẽn sẽ làm cho toàn bộ tiến trình bị nghẽn, thậm chí nếu các luồng khác sẵn dùng để chạy trong ứng dụng. Các thư viện luồng người dùng gồm các luồng POSIX Pthreads, Mach C-threads và Solaris 2 UI-threads.

- Luồng nhân: được hỗ trợ trực tiếp bởi hệ điều hành. Nhân thực hiện việc tạo luồng, lập lịch, và quản lý không gian nhân. Vì quản lý luồng được thực hiện bởi hệ điều hành, luồng nhân thường tạo và quản lý chậm hơn luồng người dùng. Tuy nhiên, vì nhân được quản lý các luồng nếu một luồng thực hiện lời gọi hệ thống bế tắc, nhân có thể lập lịch một luồng khác trong ứng dụng thực hiện. Trong môi trường đa xử lý, nhân có thể lập thời biểu luồng trên một bộ xử lý khác. Hầu hết các hệ điều hành hiện

nay như Windows NT, Windows 2000, Solaris 2, BeOS và Tru64 UNIX đều hỗ trợ các luồng nhân.

3) Mô hình đa luồng

- Mô hình nhiều-một

Mô hình nhiều-một (Hình 2.10) ánh xạ nhiều luồng cấp người dùng tới một luồng cấp nhân. Quản lý luồng được thực hiện trong không gian người dùng vì thế nó hiệu quả nhưng toàn bộ tiến trình sẽ bị khóa nếu một luồng thực hiện lời gọi hệ thống khóa. Vì chỉ một luồng có thể truy xuất nhân tại một thời điểm nên nhiều luồng không thể chạy song song trên nhiều bộ xử lý.

Hình 2 10 Mô hình nhiều một Mô hình một một Mô hình một một Hình 2 11 ánh 2

Hình 2.10 Mô hình nhiều-một

- Mô hình một-một

Mô hình một-một (Hình 2.11) ánh xạ mỗi luồng người dùng tới một luồng nhân. Nó cung cấp khả năng thực hiện đồng thời tốt hơn mô hình nhiều-một bằng cách cho một luồng khác chạy khi một luồng thực hiện lời gọi hệ thống; nó cũng cho phép nhiều luồng chạy song song trên các bộ xử lý khác nhau. Chỉ có một trở ngại trong mô hình này là cứ tạo một luồng người dùng sẽ cần tạo một luồng nhân tương ứng. Vì chi phí cho việc tạo luồng nhân có thể làm giảm năng lực thực hiện của ứng dụng, các cài đặt cho mô hình này giới hạn số luồng được hỗ trợ bởi hệ thống. Windows NT, Windows 2000 và OS/2 cài đặt mô hình một-một này.

Hình 2 11 Mô hình một một Mô hình nhiều nhiều Mô hình nhiều nhiều Hình 2 12 3

Hình 2.11 Mô hình một-một

- Mô hình nhiều-nhiều

Mô hình nhiều-nhiều (Hình 2.12) đa hợp nhiều luồng cấp người dùng tới số lượng nhỏ hơn hay bằng các luồng nhân. Số lượng các luồng nhân có thể được xác định hoặc một ứng dụng cụ thể hay một máy cụ thể (một ứng dụng có thể được cấp nhiều luồng nhân trên một bộ đa xử lý hơn trên một bộ đơn xử lý). Trong khi mô hình nhiều-một cho phép người phát triển tạo nhiều luồng người dùng như họ muốn, thì đồng hành thật sự là không đạt được vì nhân có thể lập thời biểu chỉ một luồng tại một thời điểm. Mô hình một-một cho phép đồng hành tốt hơn nhưng người phát triển phải cẩn thận không tạo ra quá nhiều luồng trong một ứng dụng. Mô hình nhiều-nhiều gặp phải một trong hai vấn đề là: người phát triển có thể tạo nhiều luồng người dùng khi cần thiết và các luồng nhân tương ứng có thể chạy song song trên một bộ đa xử lý. Khi một luồng thực hiện một lời gọi hệ thống khóa, nhân có thể lập thời biểu một luồng khác thực hiện. Solaris 2, IRIX, HP-UX, và Tru64 UNIX hỗ trợ mô hình này.

Hình 2 12 Mô hình nhiều nhiều 4

Hình 2.12 Mô hình nhiều-nhiều

..... Xem trang tiếp theo?
⇦ Trang trước - Trang tiếp theo ⇨

Ngày đăng: 16/07/2022