Lưu ý
Mục đích của file này là để độc giả tiếng Việt có thể đọc và hiểu tài liệu nhân kernel dễ dàng hơn, không phải để tạo ra một nhánh tài liệu riêng. Nếu bạn có bất kỳ nhận xét hoặc cập nhật nào cho file này, vui lòng thử cập nhật file tiếng Anh gốc trước. Nếu bạn thấy có sự khác biệt giữa bản dịch và bản gốc, hoặc có vấn đề về bản dịch, vui lòng gửi góp ý hoặc patch cho người dịch của file này, hoặc nhờ người bảo trì và người review tài liệu tiếng Việt giúp đỡ.
- Bản gốc:
- Người dịch:
Google Translate (machine translation)
- Phiên bản gốc:
8541d8f725c6
Cảnh báo
Tài liệu này được dịch tự động bằng máy và chưa được review bởi người dịch. Nội dung có thể không chính xác hoặc khó hiểu ở một số chỗ. Khi có sự khác biệt với bản gốc, bản gốc luôn là chuẩn. Bản dịch chất lượng cao (được review) được đặt trong thư mục vi_VN/.
RCU trên hệ thống bộ xử lý đơn¶
Một quan niệm sai lầm phổ biến là, trên các hệ thống UP, hàm nguyên thủy call_rcu()
có thể ngay lập tức gọi chức năng của nó. Cơ sở của quan niệm sai lầm này
là vì chỉ có một CPU nên không cần thiết phải
đợi mọi việc khác được thực hiện vì không có CPU nào khác để thực hiện
bất cứ điều gì khác đang xảy ra. Mặc dù cách tiếp cận này sẽ ZZ0000ZZ
làm việc trong một khoảng thời gian đáng ngạc nhiên, nói chung đó là một ý tưởng rất tồi.
Tài liệu này trình bày ba ví dụ chứng minh chính xác mức độ tồi tệ của
đây là một ý tưởng
Ví dụ 1: softirq Tự sát¶
Giả sử thuật toán dựa trên RCU quét danh sách liên kết có chứa
các phần tử A, B và C trong ngữ cảnh quy trình và có thể xóa các phần tử khỏi
danh sách tương tự này trong bối cảnh softirq. Giả sử rằng việc quét bối cảnh quy trình
đang tham chiếu phần tử B khi nó bị gián đoạn bởi quá trình xử lý softirq,
xóa phần tử B và sau đó gọi call_rcu() để giải phóng phần tử B
sau một thời gian ân hạn.
Bây giờ, nếu call_rcu() gọi trực tiếp các đối số của nó thì khi quay lại
từ softirq, quá trình quét danh sách sẽ tự tham chiếu đến một thư mục mới được giải phóng
yếu tố B. Tình trạng này có thể làm giảm đáng kể tuổi thọ của
hạt nhân của bạn.
Vấn đề tương tự này có thể xảy ra nếu call_rcu() được gọi từ phần cứng
trình xử lý ngắt.
Ví dụ 2: Lỗi gọi hàm¶
Tất nhiên, người ta có thể ngăn chặn vụ tự tử được mô tả trong ví dụ trước
bằng cách gọi call_rcu() trực tiếp chỉ khi nó được gọi
từ bối cảnh quá trình. Tuy nhiên, điều này có thể thất bại theo cách tương tự.
Giả sử thuật toán dựa trên RCU quét lại danh sách liên kết có chứa
các phần tử A, B và C trong ngữ cảnh tiến trình, nhưng nó gọi một hàm
trên mỗi phần tử khi nó được quét. Giả sử thêm rằng hàm này
xóa phần tử B khỏi danh sách, sau đó chuyển nó tới call_rcu() để trì hoãn
giải phóng. Điều này có thể hơi khác thường nhưng nó hoàn toàn hợp pháp
Việc sử dụng RCU, vì call_rcu() phải đợi thời gian gia hạn trôi qua.
Vì vậy, trong trường hợp này, cho phép call_rcu() gọi ngay lập tức
lập luận của nó sẽ khiến nó không thể đưa ra sự đảm bảo cơ bản
RCU cơ bản, cụ thể là call_rcu() trì hoãn việc gọi các đối số của nó cho đến khi
tất cả các phần quan trọng phía đọc RCU hiện đang thực thi đã hoàn thành.
- Câu đố nhanh #1:
Tại sao ZZ0000ZZ gọi đồng bộ hó
a_rcu()trong trường hợp này là hợp pháp?
ZZ0000ZZ
Ví dụ 3: Chết do bế tắc¶
Giả sử rằng call_rcu() được gọi trong khi đang giữ một khóa và
hàm gọi lại phải có cùng khóa này. Trong trường hợp này, nếu
call_rcu() gọi trực tiếp lệnh gọi lại, kết quả sẽ
tự bế tắc ZZ0000ZZ lời gọi này xảy ra sau đó
call_rcu() gọi thời gian gia hạn đầy đủ sau đó.
Trong một số trường hợp, có thể cơ cấu lại mã để
call_rcu() bị trì hoãn cho đến khi khóa được giải phóng. Tuy nhiên,
có những trường hợp điều này có thể khá xấu:
- Nếu một số mục cần được chuyển tới call_rcu() trong
cùng một phần quan trọng thì mã sẽ cần phải tạo một danh sách chúng, sau đó duyệt qua danh sách sau khi khóa được được thả ra.
- Trong một số trường hợp, khóa sẽ được giữ trên một số kernel API,
do đó việc trì hoãn
call_rcu()cho đến khi khóa được giải phóng yêu cầu mục dữ liệu phải được chuyển qua API chung. Sẽ tốt hơn nhiều nếu đảm bảo rằng các cuộc gọi lại được gọi không có khóa nào ngoài việc phải sửa đổi các API đó để cho phép các mục dữ liệu tùy ý được truyền ngược lại qua chúng.
Nếu call_rcu() gọi trực tiếp lệnh gọi lại, các hạn chế về khóa sẽ gặp khó khăn
hoặc thay đổi API sẽ được yêu cầu.
- Câu đố nhanh #2:
Lệnh gọi lại RCU phải tôn trọng hạn chế khóa nào?
ZZ0000ZZ
Điều quan trọng cần lưu ý là không gian người dùng RCU triển khai ZZ0000ZZ
cho phép call_rcu() gọi trực tiếp các cuộc gọi lại, nhưng chỉ khi đầy đủ
thời gian gia hạn đã trôi qua kể từ khi những cuộc gọi lại đó được xếp hàng đợi. Đây là
trường hợp này vì một số môi trường không gian người dùng cực kỳ hạn chế.
Tuy nhiên, những người viết triển khai RCU cho không gian người dùng rất quan tâm
được khuyến khích tránh gọi lại từ call_rcu(), do đó có được
những lợi ích tránh bế tắc đã nêu ở trên.
Bản tóm tắt¶
Việc cho phép call_rcu() gọi ngay lập tức các đối số của nó sẽ phá vỡ RCU,
ngay cả trên hệ thống UP. Vì vậy đừng làm điều đó! Ngay cả trên hệ thống UP, RCU
cơ sở hạ tầng ZZ0000ZZ tôn trọng thời gian gia hạn và ZZ0001ZZ gọi lại lệnh gọi lại
từ một môi trường đã biết trong đó không có khóa nào được giữ.
Lưu ý rằng ZZ0000ZZ an toàn để đồng bộ hóa_rcu() quay trở lại ngay lập tức
Các hệ thống UP, bao gồm các bản dựng PREEMPT SMP chạy trên hệ thống UP.
- Câu đố nhanh #3:
Tại sao không thể
sync_rcu()quay lại ngay trên hệ thống UP đang chạy RCU được ưu tiên trước?
- Trả lời Câu đố nhanh #1:
Tại sao ZZ0000ZZ gọi đồng bộ hó
a_rcu()trong trường hợp này là hợp pháp?- Bởi vì chức năng gọi đang quét một liên kết được bảo vệ RCU
danh sách và do đó nằm trong phần quan trọng bên đọc RCU. Do đó, hàm được gọi đã được gọi trong RCU phần quan trọng phía đọc và không được phép chặn.
- Trả lời Câu đố nhanh #2:
Lệnh gọi lại RCU phải tôn trọng hạn chế khóa nào?
- Bất kỳ khóa nào có được trong lệnh gọi lại RCU đều phải được lấy
ở nơi khác bằng cách sử dụng biến thể _bh của nguyên hàm spinlock. Ví dụ: nếu lệnh gọi lại RCU thu được “mylock” thì việc mua lại bối cảnh quy trình của khóa này phải sử dụng thứ gì đó như
spin_lock_bh()để lấy khóa. Xin lưu ý rằng Ví dụ: bạn cũng có thể sử dụng các biến thể _irq của spinlocksspin_lock_irqsave().- Nếu mã ngữ cảnh quy trình chỉ đơn giản là sử dụng spin_lock(),
sau đó, vì lệnh gọi lại RCU có thể được gọi từ ngữ cảnh softirq, cuộc gọi lại có thể được gọi từ một softirq bị gián đoạn phần quan trọng của bối cảnh-quy trình. Điều này sẽ dẫn đến tự bế tắc.
- Hạn chế này có vẻ vô cớ vì rất ít RCU
cuộc gọi lại có được khóa trực tiếp. Tuy nhiên, rất nhiều RCU các cuộc gọi lại có được các khóa ZZ0000ZZ, chẳng hạn như thông qua nguyên thủy
kfree().- Trả lời Câu đố nhanh #3:
Tại sao không thể
sync_rcu()quay lại ngay trên hệ thống UP chạy RCU ưu tiên?- Bởi vì một số nhiệm vụ khác có thể đã được ưu tiên ở giữa
của phần quan trọng phía đọc RCU. Nếu đồng bộ hó
a_rcu()chỉ cần quay trở lại ngay lập tức, nó sẽ sớm báo hiệu kết thúc thời gian ân hạn, điều này sẽ là một cú sốc khó chịu đối với chủ đề khác đó khi nó bắt đầu chạy lại.