Các luồng nhân được lập lịch bởi bộ lập thời biểu của nhân và thực hiện trên một hay nhiều CPU trong hệ thống. Nếu một luồng nhân khoá (trong khi chờ một thao tác nhập/xuất hoàn thành), thì bộ xử lý rảnh để thực hiện luồng nhân khác. Nếu một luồng bị khoá đang chạy trên một phần của LWP thì LWP cũng khoá. Ở trên vòng, luồng cấp người dùng hiện được gán tới LWP cũng bị khoá. Nếu một tiến trình có nhiều hơn một LWP thì nhân có thể lập lịch một LWP khác.
Các nhà phát triển dùng những cấu trúc dữ liệu cài đặt luồng trên Solaris 2:
- Luồng cấp người dùng chứa một luồng ID; tập thanh ghi (gồm một bộ đếm chương trình và con trỏ ngăn xếp); ngăn xếp; và độ ưu tiên (được dùng bởi thư viện cho mục đích lập lịch). Không có cấu trúc dữ liệu nào là tài nguyên nhân; tất cả chúng tồn tại trong không gian người dùng.
- Một LWP có một tập thanh ghi cho luồng cấp nhân nó đang chạy cũng như bộ nhớ và thông tin tính toán. Một LWP là một cấu trúc dữ liệu nhân và nó nằm trong không gian nhân
- Một luồng nhân chỉ có một cấu trúc dữ liệu nhân và ngăn xếp. Cấu trúc dữ liệu gồm bản sao các thanh ghi nhân, con trỏ tới LWP mà nó được gán, độ ưu tiên và thông tin lập lịch.
Mỗi tiến trình trong Solaris 2 gồm nhiều thông tin được mô tả trong khối điều khiển tiến trình (Process Control Block - PCB). Trong thực tế, một tiến trình Solaris 2 chứa một định danh tiến trình (Process ID - PID); bản đồ bộ nhớ; danh sách các tập tin đang mở, độ ưu tiên; và con trỏ của các luồng nhân với tiến trình.
Hình 2.15 Tiến trình Solaris 2
6) Luồng Windows 2000
Có thể bạn quan tâm!
- Biểu Diễn Lưu Đồ Hàng Đợi Của Lập Lịch Tiến Trình
- Nguyên lý hệ điều hành - 7
- Chương Trình C Đa Luồng Dùng Pthread Api
- Thay Đổi Thứ Tự Của Chu Kỳ Cpu Và Chu Kỳ I/o
- Hiển Thị Một Định Mức Thời Gian Nhỏ Hơn Tăng Chuyển Đổi Trạng Thái Như Thế Nào
- Các Hàng Đợi Phản Hồi Nhiều Cấp
Xem toàn bộ 306 trang tài liệu này.
Windows 2000 cài đặt Win32 API. Win32 API là một API chủ yếu cho họ hệ điều hành Windows (Windows 95/98/NT và Windows 2000).
Ứng dụng Windows chạy như một tiến trình riêng rẽ nơi mỗi tiến trình có thể chứa một hay nhiều luồng. Windows 2000 dùng ánh xạ một-một, nên mỗi luồng cấp người dùng ánh xạ tới luồng nhân được liên kết tới.Tuy nhiên, Windows cũng cung cấp sự hỗ trợ cho một thư viện có cấu trúc (fiber library) cung cấp chức năng của mô hình nhiều-nhiều. Mỗi luồng thuộc về một tiến trình có thể truy xuất một không gian địa chỉ ảo của tiến trình.
Những thành phần thông thường của một luồng gồm:
- ID của luồng định danh duy nhất luồng
- Tập thanh ghi biểu diễn trạng thái của bộ xử lý
- Ngăn xếp người dùng khi luồng đang chạy ở chế độ người dùng. Tương tự, mỗi luồng cũng có một ngăn xếp nhân được dùng khi luồng đang chạy trong chế độ nhân
- Một vùng lưu trữ riêng được dùng bởi nhiều thư viện thời gian thực và thư viện liên kết động (DLLs).
Tập thanh ghi, ngăn xếp và vùng lưu trữ riêng được xem như trạng thái của luồng và được đặc tả kiến trúc tới phần cứng mà hệ điều hành chạy trên đó. Cấu trúc dữ liệu chủ yếu của luồng gồm:
- RTHREAD (executive thread block - khối luồng thực hiện).
- KTHREAD (kernel thread - khối luồng nhân)
- TEB (thread environment block - khối môi trường luồng)
Các thành phần chủ yếu của RTHREAD gồm một con trỏ chỉ tới tiến trình nào luồng thuộc về và địa chỉ của thủ tục mà luồng bắt đầu điều khiển trong đó. ETHREAD cũng chứa một con trỏ chỉ tới KTHREAD tương ứng.
KTHREAD gồm thông tin lập lịch và đồng bộ hóa cho luồng. Ngoài ra, KTHREAD chứa ngăn xếp nhân hệ điều hành (được dùng khi luồng đang chạy trong chế độ nhân) và con trỏ chỉ tới TEB.
ETHREAD và KTHREAD tồn tại hoàn toàn ở không gian nhân hệ điều hành; điều này có nghĩa chỉ nhân có thể truy xuất chúng. TEB là cấu trúc dữ liệu trong
không gian người dùng được truy xuất khi luồng đang chạy ở chế độ người dùng. Giữa những trường khác nhau, TEB chứa ngăn xếp người dùng và một mảng cho dữ liệu đặc tả luồng (mà Windows gọi là lưu trữ cục bộ luồng)
7) Luồng Linux
Nhân Linux được giới thiệu trong phiên bản 2.2. Linux cung cấp một lời gọi hệ thống fork với chức năng truyền thống là tạo bản sao một tiến trình. Linux cũng cung cấp lời gọi hệ thống clone mà nó tương tự như tạo một luồng. clone có hành vi rất giống như fork, chỉ khác là thay cho việc tạo một bản sao của tiến trình gọi, nó sẽ tạo một tiến trình riêng chia sẻ không gian địa chỉ của tiến trình gọi. Nó chấm dứt việc chia sẻ không gian địa chỉ của tiến trình cha mà một tác vụ được nhân bản đối xử giống rất nhiều một luồng riêng rẻ.
Chia sẻ không gian địa chỉ được cho phép vì việc biểu diễn của một tiến trình trong nhân Linux. Một cấu trúc dữ liệu nhân duy nhất tồn tại cho mỗi tiến trình trong hệ thống. Một cấu trúc dữ liệu nhân duy nhất tồn tại cho mỗi tiến trình trong hệ thống. Tuy nhiên, tốt hơn lưu trữ dữ liệu cho mỗi tiến trình trong cấu trúc dữ liệu là nó chứa các con trỏ chỉ tới các cấu trúc dữ liệu khác nơi dữ liệu này được được lưu. Thí dụ, cấu trúc dữ liệu trên tiến trình chứa các con trỏ chỉ tới các cấu trúc dữ liệu khác hiện diện danh sách tập tin đang mở, thông tin quản lý tín hiệu, và bộ nhớ ảo. Khi fork được gọi, một tiến trình mới được tạo cùng với một bản sao của tất cả cấu trúc dữ liệu của tiến trình cha được liên kết tới. Khi lời gọi hệ thống clone được thực hiện, một tiến trình mới chỉ tới cấu trúc dữ liệu của tiến trình cha, do đó cho phép tiến trình con chia sẻ bộ nhớ và tài nguyên của tiến trình cha. Một tập hợp cờ được truyền như một tham số tới lời gọi hệ thống clone. Tập hợp cờ này được dùng để hiển thị bao nhiêu tiến trình cha được chia sẻ với tiến trình con. Nếu không có cờ nào được đặt, không có chia sẻ xảy ra và clone hoạt động giống như fork. Nếu tất cả năm cờ được đặt, tiến trình con chia sẻ mọi thứ với tiến trình cha. Sự kết hợp khác của cờ cho phép các cấp độ chia sẻ khác nhau giữa hai mức độ cao nhất này.
Điều thú vị là Linux không phân biệt giữa tiến trình và luồng. Thật vậy, Linux thường sử dụng thuật ngữ công việc - hơn là tiến trình hay luồng - khi tham chiếu tới dòng điều khiển trong chương trình. Ngoài tiến trình được nhân bản, Linux không hỗ
trợ đa luồng, cấu trúc dữ liệu riêng hay thủ tục nhân. Tuy nhiên, những cài đặt Pthreads là sẵn dùng cho đa luồng cấp người dùng.
8) Luồng Java
Như chúng ta đã thấy, hỗ trợ cho luồng có thể được cung cấp tại cấp người dùng với một thư viện như Pthread. Hơn nữa, hầu hết hệ điều hành cung cấp sự hỗ trợ cho luồng tại cấp nhân. Java là một trong số nhỏ ngôn ngữ cung cấp sự hỗ trợ tại cấp ngôn ngữ cho việc tạo và quản lý luồng. Tuy nhiên, vì các luồng được quản lý bởi máy ảo Java, không bởi một thư viện cấp người dùng hay nhân, rất khó để phân cấp luồng Java như cấp độ người dùng hay cấp độ nhân. Trong phần này chúng ta trình bày các luồng Java như một thay đổi đối với mô hình người dùng nghiêm ngặt hay mô hình cấp nhân. Phần sau chúng ta sẽ thảo luận một luồng Java có thể được ánh xạ tới luồng nhân bên dưới như thế nào.
Tất cả chương trình tạo ít nhất một luồng điều khiển đơn. Thậm chí một chương trình Java chứa chỉ một hàm main chạy như một luồng đơn trong máy ảo Java. Ngoài ra, Java cung cấp các lệnh cho phép người phát triển tạo và thao tác các luồng điều khiển bổ sung trong chương trình.
- Tạo luồng
Một cách để tạo một luồng rò ràng là tạo một lớp mới được phát sinh từ lớp Thread và viết đè phương thức run của lớp Thread. Tiếp cận này được hiển thị trong đoạn chương trình, phiên bản Java của chương trình đa luồng xác định tổng các số nguyên không âm.
Một đối tượng của lớp phát sinh sẽ chạy như một luồng điều khiển đơn trong máy ảo Java. Tuy nhiên, tạo một đối tượng được phát sinh từ lớp Thread không tạo một luồng mới, trái lại phương thức start mới thật sự tạo luồng mới. Gọi phương thức start cho đối tượng mới thực hiện hai thứ:
- Nó cấp phát bộ nhớ và khởi tạo một luồng mới trong máy ảo Java.
- Nó gọi phương thức run, thực hiện luồng thích hợp để được chạy bởi máy ảo Java. (chú ý rằng không thể gọi phương thức run trực tiếp, khi gọi phương thức start sẽ gọi đến phương thức run)
class Summation extends thread
{
public Summation (int n){ upper = n;
}
public void run(){ int sum = 0;
if (upper>0){
for(int i = 1; i<= upper; i++){ sum+=i;
}
system.out.println(“The sum of ”+upper+ “ is “ + sum);
}
private int upper;
}
public class ThreadTester
{
public static void main(String args[]){ if(args.length>0){ if(Integer.parseInt(args[0])<0) System.err.println(args[0] + “ must be >= 0.”); else{
Summation thrd = new Summation (Integer.parseInt(args[0])); Thrd.start();
}
}
Else
system.out.println(“Usage: summation < integer value”);
}
}
Hình 2.16 Chương trình Java để tính tổng số nguyên không âm
Khi chương trình tính tổng thực hiện, hai luồng được tạo bởi JVM. Luồng đầu tiên là luồng được nối kết với ứng dụng, luồng này bắt đầu thực hiện tại phương thức
main. Luồng thứ hai là luồng Summation được tạo rò ràng với phương thức start. Luồng Summation bắt đầu thực hiện trong phương thức run của nó. Luồng kết thúc khi nó thoát khỏi phương thức run của nó.
- JVM và hệ điều hành chủ
Cài đặt điển hình của JVM ở trên hệ điều hành thực đang điều khiển máy tính(host operating system). Thiết lập này cho phép JVM che giấu chi tiết cài đặt của hệ điều hành ảo bên dưới và cung cấp môi trường không đổi, trừu tượng cho phép chương trình Java hoạt động trên bất kỳ phần cứng nào hỗ trợ JVM. Đặc tả cho JVM không hiển thị các luồng Java được ánh xạ tới hệ điều hành bên dưới như thế nào để có thể bỏ qua cài đặt cụ thể của JVM. Windows 95/98/NT và Windows 2000 dùng mô hình một-một; do đó, mỗi luồng Java cho một JVM chạy trên các hệ điều hành này ánh xạ tới một luồng nhân. Solaris 2 khởi đầu cài đặt JVM dùng mô hình nhiều- một. Tuy nhiên, phiên bản 1.1 của JVM với Solaris 2.6 được cài đặt dùng mô hình nhiều-nhiều.
2.1.6 Truyền thông giữa các tiến trình
Chức năng của hệ thống truyền thông điệp là cho phép các tiến trình giao tiếp với các tiến trình khác mà không cần sắp xếp lại dữ liệu chia sẻ. Chúng ta xem truyền thông điệp được dùng như một phương pháp giao tiếp trong vi nhân. Trong cơ chế này, các dịch vụ được cung cấp như các tiến trình người dùng thông thường. Nghĩa là, các dịch vụ hoạt động bên ngoài nhân hệ điều hành. Giao tiếp giữa các tiến trình người dùng được thực hiện thông qua truyền thông điệp. Một phương tiện IPC cung cấp ít nhất hai hoạt động: send(message) và receive(message).
Các thông điệp được gửi bởi một tiến trình có thể có kích thước cố định hoặc biến đổi. Nếu chỉ các thông điệp có kích thước cố định được gửi, việc cài đặt cấp hệ thống là đơn giản hơn. Tuy nhiên, hạn chế này làm cho công việc lập trình sẽ phức tạp hơn. Ngoài ra, các thông điệp có kích thước thay đổi yêu cầu việc cài đặt mức hệ thống phức tạp hơn nhưng công việc lập trình trở nên đơn giản hơn.
Nếu tiến trình P và Q muốn giao tiếp, chúng phải gửi và nhận các thông điệp với nhau; một liên kết giao tiếp phải tồn tại giữa chúng. Liên kết này có thể được cài đặt bằng những cách khác nhau. Ở đây chúng ta quan tâm đến cài đặt logic hơn là cài đặt vật lý. Có vài phương pháp cài đặt một liên kết và các hoạt động send/receive:
• Giao tiếp trực tiếp hay gián tiếp
• Giao tiếp đối xứng hay bất đối xứng
• Gửi bằng bản sao hay tham chiếu
• Thông điệp có kích thước cố định hay thay đổi
1) Đặt tên
Các tiến trình muốn giao tiếp phải có cách tham chiếu với nhau. Chúng có thể dùng giao tiếp trực tiếp hay gián tiếp.
2) Giao tiếp trực tiếp
Với giao tiếp trực tiếp, mỗi tiến trình muốn giao tiếp phải đặt tên rò ràng người gửi và người nhận của giao tiếp. Trong cơ chế này, các hàm cơ sở send và receive được định nghĩa như sau:
- Send(P, message): gửi một thông điệp tới tiến trình P
- Receive(Q, message): nhận một thông điệp từ tiến trình Q Một liên kết giao tiếp trong cơ chế này có những thuộc tính sau:
- Một liên kết được thiết lập tự động giữa mỗi cặp tiến trình muốn giao tiếp. Các tiến trình cần biết định danh của nhau khi giao tiếp.
- Một liên kết được nối kết với chính xác hai tiến trình
- Chính xác một liên kết tồn tại giữa mỗi cặp tiến trình.
Cơ chế này hiển thị tính đối xứng trong việc đánh địa chỉ: nghĩa là, cả hai tiến trình gửi và nhận phải biết tên nhau để giao tiếp. Một thay đổi trong cơ chế này thực hiện tính bất đối xứng trong việc đánh địa chỉ. Chỉ người gửi biết tên của người nhận; người nhận không yêu cầu tên của người gửi. Trong cơ chế này các hàm cơ sở được định nghĩa như sau:
- Send(P, message): gửi một thông điệp tới tiến trình P
- Receive(id, message): nhận một thông điệp từ bất kỳ tiến trình nào; id khác nhau được đặt tên của tiến trình mà giao tiếp xảy ra.
Sự bất lợi trong cả hai cơ chế đối xứng và không đối xứng là tính điều chỉnh của việc định nghĩa tiến trình bị giới hạn. Thay đổi tên của một tiến trình có thể cần xem xét tất cả định nghĩa tiến trình khác. Tất cả tham chiếu tới tên cũ phải được tìm thấy để mà chúng có thể được thay đổi thành tên mới. Trường hợp này là không mong muốn từ quan điểm biên dịch riêng.
3) Giao tiếp gián tiếp
Với giao tiếp gián tiếp, một thông điệp được gửi và nhận từ các hộp thư (mailboxes), hay cổng (ports). Một hộp thư có thể được hiển thị trừu tượng như một đối tượng trong đó các thông điệp có thể được đặt bởi các tiến trình và sau đó các thông điệp này có thể được xóa đi. Mỗi hộp thư có một định danh duy nhất. Trong cơ chế này, một tiến trình có thể giao tiếp với một vài tiến trình khác bằng một số hộp thư khác nhau. Hai tiến trình có thể giao tiếp chỉ nếu chúng chia sẻ cùng một hộp thư. Hàm cơ sở send và receive được định nghĩa như sau:
- Send(A, message): gửi một thông điệp tới hộp thư A.
- Receive(A, message): nhận một thông điệp từ hộp thư A. Trong cơ chế này, một liên kết giao tiếp có các thuộc tính sau:
- Một liên kết được thiết lập giữa một cặp tiến trình chỉ nếu cả hai thành viên của cặp có một hộp thư được chia sẻ.
- Một liên kết có thể được nối kết với nhiều hơn hai tiến trình.
- Số các liên kết khác nhau có thể tồn tại giữa mỗi cặp tiến trình giao tiếp với mỗi liên kết tương ứng với một hộp thư
Giả sử các tiến trình P1, P2 và P3 chia sẻ một hộp thư A. Tiến trình P1 gửi một thông điệp tới A trong khi P2 và P3 thực hiện việc nhận từ A. Tiến trình nào sẽ nhận thông điệp được gửi bởi P1? Câu trả lời phụ thuộc cơ chế mà chúng ta chọn:
- Cho phép một liên kết được nối kết với nhiều nhất hai tiến trình
- Cho phép nhiều nhất một tiến trình tại một thời điểm thực hiện thao tác nhận.
- Cho phép hệ thống chọn bất kỳ tiến trình nào sẽ nhận thông điệp (nghĩa là, hoặc P1 hoặc P3 nhưng không phải cả hai sẽ nhận thông điệp). Hệ thống này có thể xác định người nhận tới người gửi.
Một hộp thư có thể được sở hữu bởi một tiến trình hay bởi hệ điều hành. Nếu hộp thư được sở hữu bởi một tiến trình (nghĩa là, hộp thư là một phần không gian địa chỉ của tiến trình), sau đó chúng ta phân biệt giữa người sở hữu (người chỉ nhận thông điệp thông qua hộp thư này) và người dùng (người có thể chỉ gửi thông điệp tới hộp thư). Vì mỗi hộp thư có một người sở hữu duy nhất nên không có sự lẫn lộn về người nhận thông điệp được gửi tới hộp thư này. Khi một tiến trình sở hữu một hộp thư kết