- Đọc/Ghi ngày, tháng, năm
HỆ ĐIỀU HÀNH NHIỀU BỘ VI XỬ LÝ
6.3. ĐỒNG BỘ TRONG HỆ THỐNG ĐA XỬ LÝ
Các CPU trong một hệ thống cần được đồng bộ thường xuyên. Chúng ta đã thấy điều này thơng qua việc các miền găng và bảng được bảo vệ bởi các biến mutex. Bây giờ chúng ta tìm hiểu xem việc đồng bộ thực sựđược thực hiện như thế nào trong một hệ thống cĩ nhiều bộ xử lý.
Cũng cần nhắc lại rằng, đồng bộ là tính chất cơ bản trong bất kỳ một hệ thống máy tính nào. Nếu một tiến trình trên một hệ thống cĩ một bộ xử lý tạo một lời gọi hệ thống yêu cầu truy xuất một
vài miền găng hay bảng trong kernel của hệđiều hành, thì code trong kernel chỉ cĩ thể thực hiện cấm các ngắt trước khi cho phép truy xuất bảng. Lời gọi hệ thống sau đĩ thực thi cơng việc của nĩ và cũng biết rằng nĩ cĩ thể sẽ kết thúc mà khơng cĩ sự truy xuất bảng của bất kỳ tiến trình nào khác. Trên một hệ thống nhiều bộ xử lý, việc cấm ngắt chỉ gây ảnh hưởng đối với một CPU nào đĩ thơi. Các CPU khác tiếp tục làm việc và cĩ thể thực hiện việc truy xuất bảng trong kernel của hệđiều hành. Kết quả là, một giao thức sử dụng mutex thích hợp phải được sử dụng bởi tất cả các CPU đểđảm bảo rằng việc ngăn chặn truy xuất đồng thời làm việc tốt.
Phần chính của một giao thức dùng biến mutex là một chỉ thị lệnh. Chỉ thị này cho phép một từ nhớđược kiểm tra và thiết đặt lại giá trị. Chúng ta đã thấy cách mà giao thức TSL (Test and Set Lock) được sử dụng trong chương 3 để cài đặt các miền găng như thế nào. Như chúng ta đã thấy trước đây, những gì mà một chỉ thị làm đĩ là đọc một từ nhớ ra và ghi nĩ vào một thanh ghi. Đồng thời, nĩ ghi một giá trị 1 (hoặc một giá trị khác 0 nào đĩ) vào từ nhớ. Tất nhiên, nĩ phải tốn hai vịng truy xuất bus riêng biệt để thực hiện việc đọc và ghi bộ nhớ. Trên một hệ thống cĩ một bộ xử lý, miễn là một chỉ thị khơng bị ngắt giữa chừng, giao thức TSL luơn làm việc đúng chức năng của nĩ.
Bây giờ, chúng ta tìm hiểu vấn đề này trên một hệ thống cĩ nhiều bộ xử lý. Trong hình 6-9, từ nhớ 1000 được dùng như một biến lock cĩ giá trị khởi tạo là 0. Ở bước 1, CPU 1 đọc ra từ nhớ và lấy được giá trị 0. Ở bước 2, trước khi CPU 1 cĩ cơ hội để ghi ngược lại từ nhớ một giá trị mới mang giá trị 1, thì CPU 2 truy xuất vào và cũng đọc ra từ nhớ cĩ giá trị 0. Ở bước 3, CPU 1 ghi giá trị 1 vào từ nhớ. Bước 4, CPU 2 cũng ghi giá trị 1 vào từ nhớ. Vì cả hai đều lấy giá trị 0 từ chỉ thị TSL, nên cả hai đều cĩ quyền truy xuất đến miền găng và vấn đề giải quyết tương tranh bị thất bại.
Hình 6.8: Chỉ thị lệnh TSL cĩ thể bị lỗi nếu Bus khơng thể bị khĩa. Bốn bước trong hình minh họa dãy các sự kiện gây ra lỗi.
Để ngăn chặn vấn đề này, chỉ thị TSL đầu tiên phải khĩa bus, ngăn chặn các CPU khác truy xuất
bus. Sau đĩ mới cho phép cả hai CPU truy xuất đến bộ nhớ. Và sau cùng mới giải phĩng bus. Chỉ thị này chỉ cĩ thểđược cài đặt trên một bus cĩ hỗ trợ giao thức phần cứng đặc biệt. Các bus hiện nay đều cĩ khả năng này, cịn trước đây, việc cài đặt chỉ thị dạng này là khơng thực hiện được. Đây cũng là lý do tại sao Peterson đã đưa ra giao thức đồng bộ bằng phần mềm.
Nếu TSL được cài đặt và sử dụng đúng, nĩ đảm bảo giải quyết tốt vấn đề tương tranh. Tuy nhiên, phương pháp này vẫn cĩ hạn chế nhất định. Vì biến lock sẽ bị chiếm dụng theo cơ chế quay trịn (spinning). Nghĩa là các CPU phải lần lượt thực hiện vịng lặp kiểm tra biến này và chiếm dụng
bus một cách chặt chẽ. Điều này khơng chỉ làm lãng phí thời gian yêu cầu CPU mà cịn gây ra một lượng tải lớn cho bus hoặc bộ nhớ, làm giảm nghiêm trọng tốc độ làm việc của các CPU khác.
Thống qua, ta cĩ thể nghĩ rằng nếu dùng cơ chếcaching sẽ giải quyết được vấn đề tranh giành
bus, nhưng thực sự là khơng. Về lý thuyết, khi CPU cần đọc giá trị của biến lock, nĩ nên cĩ một bản sao trong cache của mình. Miễn là khơng cĩ CPU nào khác cố gắng sử dụng lock, CPU sẽ loại bỏ hết cache của nĩ. Khi CPU sở hữu lock ghi giá trị 1 vào, cơ chếcaching sẽ tự động vơ hiệu tất cả các bản sao của CPU đĩ trong các cacheở xa, và các cache này sẽ nạp lại một giá trị hợp lệ mới. Nhưng vấn đề là ở chỗ, cache làm việc với các block 32 hoặc 64 byte. Thơng thường, các từ nhớ xung quanh lock là rất cần cho CPU đang nắm giữlockđĩ. Vì chỉ thị TSL là một lệnh ghi, nên nĩ cần thực hiện truy xuất cĩ chọn lọc đến khối cache đang chứa lockđĩ. Bởi vậy, mọi chỉ thị TSL đều làm vơ hiệu khối cache bên trong cache của CPU đang lưu giữ lockđĩ và lấy ra một bản sao riêng cĩ chọn lọc cho từng yêu cầu của CPU. Ngay khi CPU đang lưu giữlock truy xuất đến một từ nhớ kế bên lock đĩ, khối cache được chuyển đến CPU đĩ. Kết quả là, tồn bộ khối cache chứa lockđĩ được chuyển qua lại liên tục giữa CPU giữlock và CPU yêu cầu lock, tạo ra nhiều lưu lượng bus hơn việc đọc một biến lock bình thường.
Nếu chúng ta cĩ thể loại bỏđược tất cả các chỉ thị ghi do TSL đưa ra, thì cĩ thể giảm đáng kể vấn đềcache thrashing. Đểđạt được điều này, CPU đầu tiên sẽ thực hiện việc đọc chỉđể kiểm tra xem
lock cĩ rỗi hay khơng. Chỉ nếu lock rỗi, thì CPU mới thực hiện một TSL để thực sự chiếm lấy
lock. Kết quả của sự thay đổi nhỏ này thể hiện ở chỗ hầu hết các thăm dị (poll) bây giờ sẽ chỉ là đọc mà khơng ghi. Nếu CPU giữ lockđang chỉđọc các biến trong cùng khối cache, thì mỗi CPU cĩ một bản sao của khối cache trong chếđộ chỉđọc, loại bỏ tất cả các chuyển đổi khối cache. Khi
lock cuối cùng rỗi, CPU sở hữu nĩ thực hiện chỉ thị ghi, cho phép việc truy xuất dành riêng, vì vậy nĩ làm vơ hiệu tất cả các bản sao khác trong các cache ở xa. Trong lần đọc tiếp theo, khối cache sẽđược nạp lại. Nếu hai hoặc nhiều CPU đang cạnh tranh vì muốn chiếm dụng cùng một
lock, cĩ thể xảy ra trường hợp cả hai CPU sẽ cùng đồng thời thấy lock này rỗi, và cả hai thực hiện chỉ thị TSL để chiếm lấy lockđĩ đồng thời. Sẽ chỉ cĩ một trong hai thành cơng, vì vậy sẽ khơng cĩ bất kỳđiều kiện cạnh tranh nào ởđây vì việc giành được lock được thực hiện bởi chỉ thị TSL và chỉ thị này là khơng thể chia nhỏđược nữa.
Một cách khác để giảm lưu lượng bus là sử dụng giải thuật Ethernet binary exponential backoff do Anderson đưa ra năm 1990. Thay vì phải thăm dị liên tục, một vịng lặp trễ (delay loop) sẽđược thêm vào giữa các biến thăm dị. Ban đầu, delay là một chỉ thị lệnh. Nếu lock vẫn bận, delayđược nhân đơi thành hai chỉ thị lệnh, sau đĩ là 4 chỉ thị lệnh … đến một con số tối đa nào đĩ. Nếu giá trị tối đa này thấp sẽ cho phép thực hiện hồi đáp nhanh khi lockđược giải phĩng, nhưng nĩ lại làm lãng phí nhiều vịng bus trong vấn đề cache thrashing. Cịn nếu giá trị tối đa này cao thì sẽ làm giảm cache thrashing nhưng việc thơng tin về trạng thái lock bị chậm đi. Binary Exponential Backoff cĩ thểđược sử dụng trong cả hai trường hợp cĩ hoặc khơng cĩ các chỉ thịđọc kiểm tra trước chỉ thị TSL thực sự.
Một ý tưởng hay hơn đĩ là cho phép mỗi CPU chiếm lấy một mutex (biến lock riêng của nĩ) để kiểm tra như minh họa trong hình 6-10. Biến lock này nên nằm trong một khối cache chưa sử dụng để tránh xung đột. Khi CPU đang giữ lock thĩat ra khỏi miền găng, nĩ giải phĩng biến lock
riêng mà CPU đầu tiên trên danh sách đang kiểm tra (trên cache của nĩ). CPU này sau đĩ đi vào miền găng. Khi nĩ được thực thi, nĩ giải phĩng lock mà CPU trước nĩ sử dụng ... Mặc dù giao thức này là tương đối phức tạp (để tránh việc hai CPU cùng đồng thời gắn chúng vào cuối danh sách), nĩ thật sự là giao thức hiệu quả và giải quyết được vấn đềđĩi tài nguyên.
Hình 6.9. Sử dụng nhiều biến lockđể tránh cache thrashing