Professional Documents
Culture Documents
Hình 4 minh họa các chuyển đổi trạng thái kiểm soát thời điểm tinh chỉnh
ổ khóa xảy ra và khi các cuộc đua được báo cáo. Khi một biến được cấp
phát lần đầu, nó được đặt ở trạng thái Virgin, cho biết rằng dữ liệu là mới
và chưa được tham chiếu bởi bất kỳ luồng nào. Khi dữ liệu được truy
cập, nó sẽ chuyển sang trạng thái Exclusive, biểu thị rằng nó đã được truy
cập, nhưng chỉ bởi một luồng. Trong trạng thái này, các lần đọc và ghi
tiếp theo bởi cùng một luồng không thay đổi trạng thái của biến và không
cập nhật C (v). Điều này giải quyết vấn đề khởi tạo, vì luồng đầu tiên có
thể khởi tạo biến mà không khiến C (v) được tinh chỉnh. Khi nào và nếu
một luồng khác truy cập vào biến, thì trạng thái sẽ thay đổi. Quyền truy
cập đã đọc thay đổi trạng thái thành Được chia sẻ. Ở trạng thái Chia sẻ, C
(v) được cập nhật, nhưng các cuộc đua dữ liệu không được báo cáo, ngay
cả khi C (v) trở nên trống. Điều này giải quyết vấn đề dữ liệu được chia
sẻ đã đọc, vì nhiều luồng có thể đọc một biến mà không gây ra một cuộc
đua được báo cáo. Truy cập ghi từ một luồng mới sẽ thay đổi trạng thái
từ Độc quyền hoặc Được chia sẻ sang trạng thái Chia sẻ-Sửa đổi, trong
đó C (v) được cập nhật và các cuộc đua được báo cáo, giống như được
mô tả trong phiên bản ban đầu, đơn giản của thuật toán.
Sự hỗ trợ của chúng tôi cho việc khởi chạy khiến việc kiểm tra của
Eraser phụ thuộc nhiều hơn vào công cụ lập lịch so với những gì chúng
tôi muốn. Giả sử rằng một luồng cấp phát và khởi tạo một biến được chia
sẻ mà không có khóa và làm cho biến đó có thể truy cập nhầm vào luồng
thứ hai trước khi nó hoàn tất quá trình khởi tạo. Sau đó, Eraser sẽ phát
hiện ra lỗi nếu bất kỳ truy cập nào của luồng thứ hai xảy ra trước các
hành động khởi tạo cuối cùng của luồng đầu tiên, nhưng nếu không thì
Eraser sẽ bỏ sót lỗi. Chúng tôi không nghĩ rằng đây là một vấn đề, nhưng
chúng tôi không có cách nào để biết chắc chắn.
2.3Read-Write Locks
Nhiều chương trình sử dụng khóa một đầu đọc, nhiều đầu đọc cũng như
khóa đơn giản. Để phù hợp với phong cách này, chúng tôi giới thiệu lần
cải tiến cuối cùng của chúng tôi về kỷ luật khóa: chúng tôi yêu cầu rằng
đối với mỗi biến v, một số khóa m bảo vệ v, nghĩa là m được giữ ở chế
độ ghi cho mỗi lần ghi v và m được giữ ở một số chế độ (đọc hoặc viết)
cho mỗi lần đọc v.
Chúng tôi tiếp tục sử dụng các chuyển đổi trạng thái của Hình 4,
nhưng khi biến chuyển sang trạng thái Chia sẻ-Sửa đổi, việc kiểm tra hơi
khác một chút:
Có nghĩa là, các khóa được giữ hoàn toàn ở chế độ đọc sẽ bị loại bỏ khỏi
tập ứng cử viên khi ghi xảy ra, vì các khóa được giữ bởi người viết không
bảo vệ chống lại cuộc chạy đua dữ liệu giữa người viết và một số chuỗi
trình đọc khác.
3. THỰC HIỆN LỖI
Eraser được triển khai cho hệ điều hành Digital Unix trên bộ xử lý Alpha, sử
dụng hệ thống sửa đổi nhị phân ATOM [Srivastava và Eustace 1994]. Eraser
lấy một chương trình nhị phân chưa sửa đổi làm đầu vào và thêm thiết bị đo
đạc để tạo ra một nhị phân mới giống hệt nhau về mặt chức năng, nhưng bao
gồm các lệnh gọi đến thời gian chạy Eraser để triển khai thuật toán Lockset
Để duy trì C (v), Eraser công cụ mỗi lần tải và lưu trữ trong chương trình.
Để duy trì lock_held(t) cho mỗi luồng t, Eraser công cụ mỗi lệnh gọi để lấy
hoặc giải phóng một khóa, cũng như các sơ khai quản lý việc khởi tạo và
hoàn thiện luồng. Để khởi tạo C (v) cho dữ liệu được cấp phát động, Eraser
công cụ mỗi cuộc gọi đến bộ cấp phát lưu trữ.
Eraser coi mỗi từ 32 bit trong dữ liệu heap hoặc toàn cục là một biến có
thể được chia sẻ, vì trên nền tảng của chúng tôi, từ 32 bit là đơn vị kết hợp
bộ nhớ nhỏ nhất. Eraser không tải và lưu trữ thiết bị có chế độ địa chỉ gián
tiếp ra khỏi con trỏ ngăn xếp, vì chúng được giả định là các tham chiếu ngăn
xếp và các biến được chia sẻ được giả định là ở các vị trí toàn cục hoặc trong
heap. Eraser sẽ duy trì các tập ứng cử viên cho các vị trí ngăn xếp được truy
cập thông qua các thanh ghi không phải là con trỏ ngăn xếp, nhưng đây là
một phần tạo tác của việc triển khai chứ không phải là một kế hoạch có chủ
ý để hỗ trợ các chương trình chia sẻ vị trí ngăn xếp giữa các luồng.
Khi một cuộc đua được báo cáo, Eraser cho biết tệp và số dòng tại đó nó
được phát hiện và danh sách dấu vết của tất cả các khung ngăn xếp đang
hoạt động. Báo cáo cũng bao gồm ID luồng, địa chỉ bộ nhớ, kiểu truy cập bộ
nhớ và các giá trị thanh ghi quan trọng như bộ đếm chương trình và con trỏ
ngăn xếp. Khi được sử dụng cùng với mã nguồn của chương trình, chúng tôi
nhận thấy rằng thông tin này thường đủ để xác định nguồn gốc của cuộc đua.
Nếu nguyên nhân của một cuộc đua vẫn chưa rõ ràng, người dùng có thể chỉ
đạo Eraser ghi lại tất cả các quyền truy cập vào một biến cụ thể dẫn đến thay
đổi bộ khóa ứng viên của nó.
3.1Đại diện cho các bộ khóa ứng viên
Việc triển khai các bộ khóa một cách ngây thơ sẽ lưu trữ một danh sách
các khóa ứng viên cho mỗi vị trí bộ nhớ, có khả năng tiêu tốn nhiều lần
bộ nhớ được cấp phát của chương trình. Chúng ta có thể tránh được
khoản chi phí này bằng cách khai thác một thực tế may mắn là số lượng
các bộ khóa khác nhau được quan sát trong thực tế là khá ít. Trên thực tế,
chúng tôi chưa bao giờ quan sát thấy hơn 10.000 bộ khóa khác nhau xuất
hiện trong bất kỳ quá trình thực thi nào của thuật toán giám sát Lockset.
Do đó, chúng tôi biểu diễn mỗi tập khóa bằng một số nguyên nhỏ, một
chỉ mục ổ khóa vào một bảng mà các mục nhập của nó đại diện cho tập
hợp khóa dưới dạng vectơ được sắp xếp của địa chỉ khóa. Các mục nhập
trong bảng không bao giờ được phân bổ hoặc sửa đổi thỏa thuận, vì vậy
mỗi chỉ mục ổ khóa vẫn có giá trị trong suốt thời gian của chương trình.
Các chỉ mục ổ khóa mới được tạo ra do kết quả của việc mua lại khóa,
phát hành khóa hoặc thông qua ứng dụng của hoạt động giao nhau. Để
đảm bảo rằng mỗi chỉ mục ổ khóa đại diện cho một bộ khóa duy nhất,
chúng tôi duy trì một bảng băm của các vectơ khóa hoàn chỉnh được tìm
kiếm trước khi một chỉ mục ổ khóa mới được tạo. Eraser cũng lưu trữ kết
quả của mỗi giao điểm, do đó, trường hợp nhanh cho giao điểm được
thiết lập chỉ đơn giản là tra cứu bảng. Mỗi vectơ khóa trong bảng được
sắp xếp, do đó khi bộ nhớ cache bị lỗi, trường hợp chậm của hoạt động
giao nhau có thể được thực hiện bằng cách so sánh đơn giản giữa hai
vectơ đã sắp xếp.
Đối với mỗi từ 32 bit trong phân đoạn dữ liệu và heap, có một từ bóng
tương ứng được sử dụng để chứa chỉ mục ổ khóa 30 bit và điều kiện
trạng thái 2 bit. Ở trạng thái Exclusive, 30 bit không được sử dụng để lưu
trữ chỉ mục ổ khóa mà được sử dụng để lưu trữ ID của luồng có quyền
truy cập độc quyền.
Tất cả các quy trình cấp phát bộ nhớ tiêu chuẩn đều được thiết kế để
cấp phát và khởi tạo một từ bóng cho mỗi từ được chương trình cấp phát.
Khi một chuỗi truy cập vào vị trí bộ nhớ, Eraser sẽ tìm từ bóng bằng cách
thêm một vị trí cố định vào địa chỉ của vị trí. Hình 5 minh họa cách bộ
nhớ bóng và biểu diễn chỉ mục ổ khóa được sử dụng để liên kết mỗi biến
được chia sẻ với một tập hợp các khóa ứng viên tương ứng.
3.2Performance
Hiệu suất không phải là mục tiêu chính trong việc triển khai Eraser của
chúng tôi và do đó, có nhiều cơ hội để tối ưu hóa. Các ứng dụng thường
chậm đi từ 10 đến 30 trong khi sử dụng Eraser. Sự giãn nở thời gian này
có thể thay đổi thứ tự các luồng được lên lịch và có thể ảnh hưởng đến
hoạt động của các ứng dụng nhạy cảm với thời gian. Kinh nghiệm của
chúng tôi cho thấy rằng sự khác biệt trong lập lịch chuỗi ít ảnh hưởng
đến kết quả của Eraser. Chúng tôi có ít kinh nghiệm hơn với các ứng
dụng rất nhạy cảm về thời gian và có thể chúng sẽ được hưởng lợi từ kỹ
thuật giám sát hiệu quả hơn.
Chúng tôi ước tính rằng một nửa sự chậm lại trong quá trình triển khai
hiện tại là do chi phí thực hiện lệnh gọi thủ tục ở mỗi lần tải và lệnh lưu
trữ. Chi phí này có thể được loại bỏ bằng cách sử dụng phiên bản ATOM
có thể mã giám sát nội tuyến [Scales et al. Năm 1996]. Ngoài ra, có nhiều
cơ hội để sử dụng phân tích tĩnh để giảm chi phí của mã giám sát; nhưng
chúng tôi đã không khám phá chúng.
Mặc dù điều chỉnh hiệu suất hạn chế của chúng tôi, chúng tôi nhận
thấy rằng Eraser đủ nhanh để gỡ lỗi hầu hết các chương trình và do đó
đáp ứng tiêu chí hiệu suất thiết yếu nhất.
3.3Chú thích chương trình
Đúng như dự đoán, kinh nghiệm của chúng tôi với Eraser cho thấy nó có
thể tạo ra cảnh báo sai. Một phần trong nghiên cứu của chúng tôi là nhằm
tìm ra các chú thích hiệu quả để ngăn chặn các cảnh báo giả mà không vô
tình làm mất các cảnh báo hữu ích. Đây là một chìa khóa để làm cho một
công cụ như Eraser trở nên hữu ích. Nếu các cảnh báo sai bị loại bỏ bằng
các chú thích chính xác và cụ thể, thì khi một chương trình được sửa đổi
và chương trình đã sửa đổi được kiểm tra, chỉ những cảnh báo mới và có
liên quan sẽ được tạo ra.
Theo kinh nghiệm của chúng tôi, các cảnh báo sai chủ yếu được chia
thành ba loại chính:
—Sử dụng lại bộ nhớ: Đã báo cáo sai cảnh báo vì bộ nhớ được sử dụng
lại mà không đặt lại bộ nhớ bóng. Eraser cung cấp tất cả các quy trình
cấp phát bộ nhớ C, C11 và Unix tiêu chuẩn. Tuy nhiên, nhiều chương
trình triển khai danh sách miễn phí hoặc trình cấp phát riêng và Eraser
không có cách nào biết rằng phần bộ nhớ được tái chế riêng được bảo vệ
bởi một bộ khóa mới.
—Khóa riêng tư: Các cảnh báo sai đã được báo cáo vì khóa được thực
hiện mà không truyền thông tin này đến Eraser trong thời gian chạy. Điều
này thường là do triển khai riêng của nhiều khóa đầu đọc / ghi đơn,
không phải là một phần của giao diện pthreads tiêu chuẩn mà Eraser công
cụ.
—Benign Races: Các cuộc đua dữ liệu thực được tìm thấy không ảnh
hưởng đến tính đúng đắn của chương trình. Một số trong số này là cố ý,
và những người khác là vô tình.
Đối với mỗi danh mục này, chúng tôi đã phát triển chú thích chương trình
để cho phép người dùng Eraser loại bỏ báo cáo sai. Đối với các cuộc đua
lành tính, chúng tôi đã thêm
EraserIgnoreOn( )
EraserIgnoreOff( )
thông báo cho bộ dò cuộc đua rằng nó không nên báo cáo bất kỳ cuộc
đua nào trong mã được đánh dấu ngoặc. Để ngăn chặn các cuộc đua sử
dụng lại bộ nhớ bị báo cáo, chúng tôi đã thêm
EraserReuse(address, size)
hướng dẫn Eraser đặt lại bộ nhớ bóng tương ứng với phạm vi bộ nhớ
được chỉ định về trạng thái Virgin. Cuối cùng, sự tồn tại của các triển
khai khóa riêng tư có thể được giao tiếp bằng cách chú thích chúng bằng
EraserReadLock(lock)
EraserReadUnlock(lock)
EraserWriteLock(lock)
EraserWriteUnlock(lock)
Chúng tôi nhận thấy rằng một số ít các chú thích này thường đủ để
loại bỏ tất cả các cảnh báo sai.
3.4Race Detection in an OS Kernel
Chúng tôi đã bắt đầu sửa đổi Eraser để phát hiện races trong hệ điều hành
SPIN [Bershad et al. 1995]. Một số tính năng của SPIN, chẳng hạn như
tạo mã thời gian chạy và liên kết mã muộn, làm phức tạp quá trình đo đạc
và do đó Eraser chưa hoạt động trong môi trường này. Tuy nhiên, trong
khi chúng tôi không có kết quả về các cuộc đua dữ liệu được tìm thấy,
chúng tôi đã có được một số kinh nghiệm hữu ích về việc triển khai một
công cụ như vậy ở cấp nhân, khác với cấp người dùng theo một số cách.
Đầu tiên, SPIN (giống như nhiều hệ điều hành) thường tăng mức ngắt
của bộ xử lý để loại trừ lẫn nhau đối với các cấu trúc dữ liệu được chia sẻ
được truy cập bởi trình điều khiển thiết bị và mã mức ngắt khác. Trong
hầu hết các hệ thống, việc nâng mức ngắt lên n đảm bảo rằng chỉ các ngắt
có mức ưu tiên lớn hơn n mới được phục vụ cho đến khi mức ngắt được
hạ xuống. Nâng cao và sau đó khôi phục mức ngắt có thể được sử dụng
thay vì khóa, như sau:
Tuy nhiên, không giống như khóa, một mức ngắt cụ thể bảo vệ toàn bộ
dữ liệu được bảo vệ bởi mức ngắt thấp hơn. Chúng tôi đã kết hợp sự khác
biệt này vào Eraser bằng cách gán khóa cho từng mức ngắt riêng lẻ. Khi
hạt nhân đặt mức ngắt thành n, Eraser xử lý hoạt động này như thể n
khóa ngắt đầu tiên đã được thực hiện. Chúng tôi mong đợi kỹ thuật này
cho phép chúng tôi phát hiện các chủng tộc giữa mã bằng cách sử dụng
khóa tiêu chuẩn và mã sử dụng mức ngắt.
Một sự khác biệt nữa là hệ điều hành sử dụng nhiều hơn việc đồng bộ
hóa kiểu post / wait. Ví dụ phổ biến nhất là việc sử dụng các semaphores
để đồng bộ hóa việc thực thi giữa một luồng và một trình điều khiển thiết
bị I / O. Khi nhận được dữ liệu, trình điều khiển thiết bị sẽ thực hiện một
số xử lý tối thiểu và sau đó sử dụng thao tác V để báo hiệu một luồng
đang chờ hoạt động P, chẳng hạn, để đánh thức một luồng đang chờ hoàn
thành I / O. Điều này có thể gây ra sự cố cho Eraser nếu dữ liệu được
chia sẻ giữa trình điều khiển thiết bị và luồng. Bởi vì các semaphores
không được “sở hữu” nên Eraser khó có thể suy ra dữ liệu nào chúng
đang được sử dụng để bảo vệ, dẫn đến việc đưa ra các cảnh báo sai. Các
hệ thống tích hợp luồng và xử lý ngắt [Kleiman và Eykholt 1995] có thể
gặp ít rắc rối hơn với vấn đề này.
4. EXPERIENCE
Chúng tôi đã hiệu chỉnh Eraser trên một số chương trình đơn giản có các lỗi
đồng bộ hóa phổ biến (ví dụ: quên khóa, sử dụng khóa sai, v.v.) và các phiên
bản của các chương trình đó đã sửa lỗi. Trong khi lập trình các thử nghiệm
này, chúng tôi đã vô tình giới thiệu một cuộc đua và đáng mừng là Eraser đã
phát hiện ra nó. Những bài kiểm tra đơn giản này cực kỳ hữu ích để tìm lỗi
trong Eraser. Sau khi thuyết phục bản thân rằng công cụ hoạt động, chúng
tôi đã giải quyết một số máy chủ đa luồng lớn được viết bởi các nhà nghiên
cứu giàu kinh nghiệm tại Trung tâm nghiên cứu hệ thống của Digital
Equipment Corporation: máy chủ HTTP và công cụ lập chỉ mục từ
AltaVista, máy chủ bộ đệm Vesta và hệ thống đĩa phân tán Petal. Chúng tôi
cũng áp dụng Eraser cho một số bài tập về nhà do các lập trình viên đại học
tại Đại học Washington viết.
Như được mô tả chi tiết bên dưới, Eraser đã tìm thấy các điều kiện chạy
đua không mong muốn trong ba trong số bốn chương trình máy chủ và trong
nhiều bài tập về nhà ở bậc đại học. Nó cũng tạo ra các cảnh báo sai mà
chúng tôi có thể ngăn chặn bằng các chú thích. Khi chúng tôi tìm thấy điều
kiện cuộc đua hoặc cảnh báo sai, chúng tôi đã sửa đổi chương trình một cách
thích hợp và sau đó điều chỉnh lại Eraser để xác định các vấn đề còn lại.
Mười lần lặp lại của quy trình này thường là đủ để giải quyết tất cả các cuộc
đua được báo cáo của chương trình.
Các lập trình viên của máy chủ mà chúng tôi đã kiểm tra Eraser đã không
bắt đầu với kế hoạch kiểm tra Eraser hoặc thậm chí sử dụng kỷ luật khóa của
Eraser. Thực tế là Eraser hoạt động tốt trên các máy chủ là bằng chứng cho
thấy các lập trình viên có kinh nghiệm có xu hướng tuân theo kỷ luật khóa
đơn giản ngay cả trong môi trường cung cấp nhiều nguyên thủy đồng bộ hóa
phức tạp hơn.
Trong phần còn lại của phần này, chúng tôi báo cáo chi tiết về trải
nghiệm của chúng tôi với từng chương trình.
4.1AltaVista
Chúng tôi đã kiểm tra hai thành phần của dịch vụ lập chỉ mục Web
AltaVista phổ biến: mhttpd và Ni2.
Chương trình mhttpd là một máy chủ HTTP nhẹ được thiết kế để hỗ
trợ tải máy chủ cực cao mà AltaVista đã trải qua. Mỗi yêu cầu tìm kiếm
được xử lý bởi một luồng riêng biệt và dựa vào khóa để đồng bộ hóa
quyền truy cập bằng các yêu cầu đồng thời tới cấu trúc dữ liệu được chia
sẻ. Ngoài ra, mhttpd sử dụng một số luồng bổ sung để quản lý các tác vụ
nền như cấu hình và quản lý bộ đệm tên. Máy chủ bao gồm khoảng 5000
dòng mã nguồn C. Chúng tôi đã thử nghiệm mhttpd bằng cách gọi một
loạt các tập lệnh thử nghiệm từ ba trình duyệt Web riêng biệt. Thử
nghiệm mhttpd đã sử dụng khoảng 100 ổ khóa khác nhau tạo thành
khoảng 250 bộ khóa khác nhau.
Công cụ lập chỉ mục Ni2 được sử dụng để tra cứu thông tin theo các
truy vấn chỉ mục. Cấu trúc dữ liệu chỉ mục được chia sẻ giữa tất cả các
luồng máy chủ và sử dụng khóa một cách rõ ràng để đảm bảo rằng các
bản cập nhật được tuần tự hóa. Các thư viện Ni2 cơ bản chứa khoảng
20.000 dòng mã nguồn C. Chúng tôi đã thử nghiệm Ni2 một cách riêng
biệt bằng cách sử dụng một tiện ích có tên ft gửi một loạt các yêu cầu
ngẫu nhiên bằng cách sử dụng một số chủ đề cụ thể (chúng tôi đã sử
dụng 10). Thử nghiệm ft đã sử dụng khoảng 900 khóa tạo thành khoảng
3600 bộ khóa riêng biệt.
Chúng tôi đã tìm thấy một số lượng lớn các cuộc đua được báo cáo,
hầu hết trong số đó là báo động sai. Nguyên nhân chủ yếu là do tái sử
dụng bộ nhớ, sau đó là các khóa riêng và các cuộc đua lành tính. Các
chủng tộc lành tính được tìm thấy trong Ni2 đặc biệt thú vị, bởi vì chúng
minh họa cho việc sử dụng các chủng tộc một cách có chủ đích để giảm
thiểu chi phí khóa. Ví dụ: hãy xem xét đoạn mã sau:
khóa ip lock giữ. Cuộc đua đã được cố ý lập trình như một sự tối ưu hóa
để tránh bị khóa chi phí trong trường hợp phổ biến là ip fp đã được thiết
lập. Chương trình chính xác ngay cả với cuộc đua, vì trường ip fp không
bao giờ chuyển từ khác 0 sang 0 trong phạm vi nhiều luồng và chương
trình lặp lại kiểm tra bên trong khóa trong trường hợp trường được kiểm
tra bằng 0 (do đó tránh được cuộc đua trong đó hai luồng tìm trường bằng
không và cả hai sau đó khởi tạo nó).
Loại mã này rất phức tạp. Ví dụ: có thể an toàn khi truy cập trường p-
> ip fp trong phần còn lại của quy trình (các dòng được thay thế bằng dấu
chấm lửng trong đoạn mã). Nhưng trên thực tế, đây sẽ là một sai lầm, bởi
vì mô hình nhất quán bộ nhớ của Alpha cho phép bộ xử lý xem các hoạt
động của bộ nhớ không theo thứ tự nếu không có đồng bộ hóa can thiệp.
Mặc dù mã Ni2 là đúng, sau khi sử dụng Eraser, lập trình viên đã quyết
định lập trình lại phần này của nó để đối số về độ đúng của nó đơn giản
hơn.
Chúng tôi cũng tìm thấy một cuộc chạy đua lành tính trong chương
trình khai thác thử nghiệm Ni2, nơi nhiều luồng chạy đua đọc và ghi vào
một biến toàn cục được gọi là truy vấn giết. Biến này được khởi tạo thành
false và được đặt thành true để chỉ ra rằng tất cả các luồng sẽ thoát. Mỗi
luồng định kỳ thăm dò biến và thoát ra khi nó được đặt thành true. Các
mã hoàn thiện khác có các cuộc đua lành tính tương tự. Để ngăn trình dò
cuộc đua báo cáo những cuộc đua như vậy, chúng tôi đã sử dụng chú
thích EraserIgnoreOn / Off (). Tương tự, mhttpd bỏ qua các khóa khi cập
nhật định kỳ dữ liệu và thống kê cấu hình toàn cầu. Đây thực sự là những
lỗi đồng bộ hóa, nhưng ảnh hưởng của chúng tương đối nhỏ, đó có lẽ là
lý do tại sao chúng không được phát hiện trong thời gian dài.
Việc chèn chín chú thích trong thư viện Ni2, năm chú thích trong bộ
khai thác thử nghiệm ft và 10 chú thích trong máy chủ mhttpd đã giảm số
lượng cuộc đua được báo cáo từ hơn một trăm xuống còn không.
4.2Máy chủ bộ nhớ đệm Vesta
Vesta là một hệ thống quản lý cấu hình phần mềm tiên tiến. 4 Các cấu
hình được viết bằng ngôn ngữ chức năng chuyên dụng mô tả các phần
phụ thuộc và các quy tắc được sử dụng để suy ra trạng thái hiện tại của
phần mềm. Kết quả một phần, chẳng hạn như tệp “.o” được tạo bởi trình
biên dịch C, được lưu vào bộ nhớ đệm trong máy chủ bộ đệm Vesta và
được sử dụng bởi trình tạo Vesta để tạo một cấu hình cụ thể. Máy chủ bộ
nhớ cache bao gồm khoảng 30.000 dòng mã C11. Chúng tôi đã kiểm tra
máy chủ bộ nhớ cache bằng cách sử dụng tiện ích TestCache đưa ra một
luồng yêu cầu ngẫu nhiên đồng thời. Máy chủ bộ nhớ đệm đã sử dụng 10
luồng, có 26 khóa riêng biệt và khởi tạo 70 bộ khóa khác nhau.
Trong quá trình thử nghiệm máy chủ bộ nhớ cache, Eraser đã báo cáo
một số cuộc đua, chủ yếu xoay quanh ba cấu trúc dữ liệu. Tập hợp chủng
tộc đầu tiên được phát hiện trong mã duy trì dấu vân tay trong các mục
nhập bộ nhớ cache. Bởi vì tính toán một dấu vân tay có thể tốn kém, máy
chủ bộ nhớ cache duy trì một trường boolean trong mục nhập bộ nhớ
cache ghi lại xem dấu vân tay đó có hợp lệ hay không. Tệp tham chiếu
chỉ được tính nếu giá trị thực của nó là cần thiết và giá trị hiện tại của nó
không hợp lệ. Thật không may, boolean đã được truy cập mà không có
khóa bảo vệ, trong mã như thế này:
Đây là một cuộc chạy đua dữ liệu nghiêm trọng, vì trong trường hợp
không có rào cản bộ nhớ, ngữ nghĩa Alpha không đảm bảo rằng nội dung
của trường validFP nhất quán với trường fp.
Một tập hợp các cuộc đua khác xoay quanh danh sách miễn phí trong
đối tượng CacheS. Đối tượng CacheS duy trì một danh sách miễn phí các
loại mục nhật ký khác nhau. Phản hồi đầu tiên của chúng tôi là sử dụng
chú thích EraserReuse () trong đó các phần tử được phân bổ khỏi danh
sách miễn phí này. Tuy nhiên, điều này không làm cho tất cả các cảnh
báo biến mất; các cuộc gọi để xóa nhật ký vẫn gây ra các cuộc đua. Kiểm
tra cho thấy phần đầu của mỗi khúc gỗ được bảo vệ bằng khóa, nhưng
không phải các mục riêng lẻ. Các quy trình Flush khóa phần đầu của nhật
ký, lưu trữ giá trị của nó trong một biến ngăn xếp, đặt phần đầu thành 0
và nhả khóa. Sau đó, họ truy cập các mục riêng lẻ mà không có bất kỳ ổ
khóa nào, cuối cùng đưa chúng vào danh sách miễn phí. Điều này đúng
vì các luồng khác truy cập các mục nhật ký với khóa đầu nhật ký được
giữ và các luồng không duy trì các con trỏ vào nhật ký. Do đó, Flush làm
cho dữ liệu trở nên riêng tư một cách hiệu quả đối với luồng mà Flush
được gọi. Chúng tôi đã loại bỏ báo cáo về các cuộc đua này bằng cách
chuyển các chú thích EraserReuse () sang ba quy trình Flush.
Cuối cùng, đã có một số cảnh báo sai liên quan đến đối tượng TCP
sock và SRPC được sử dụng để triển khai RPC phía máy chủ. Máy chủ
bộ đệm sử dụng một chuỗi máy chủ chính để chờ các yêu cầu RPC đến.
Khi nhận được yêu cầu, luồng này sẽ chuyển các cấu trúc dữ liệu RPC và
socket hiện tại tới một luồng công nhân chịu trách nhiệm xử lý phần còn
lại của RPC. Vì luồng chính và luồng công nhân sẽ không bao giờ truy
cập đồng thời các cấu trúc dữ liệu nên chúng không cần sử dụng khóa để
tuần tự hóa quyền truy cập. Đối với Eraser, điều này giống như vi phạm
kỷ luật khóa và được gắn cờ là một cuộc đua. Với một số nỗ lực, có thể
sửa đổi Eraser để nhận ra kỷ luật khóa này, nhưng chúng tôi đã có thể đạt
được hiệu quả tương tự với hai chú thích EraserReuse ().
Tổng cộng, 10 chú thích và một lần sửa lỗi là đủ để giảm các báo cáo
cuộc đua từ vài trăm xuống 0.
4.3Peta
Petal là một hệ thống lưu trữ phân tán cung cấp cho khách hàng một đĩa
ảo khổng lồ được thực hiện bởi một cụm máy chủ và đĩa vật lý [Lee và
Thekkath 1996]. Petal thực hiện một thuật toán đồng thuận phân tán cũng
như các cơ chế phát hiện và khôi phục lỗi. Máy chủ Petal có khoảng
25.000 dòng mã C và chúng tôi đã sử dụng 64 luồng công nhân đồng thời
trong các thử nghiệm của mình. Chúng tôi đã thử nghiệm Petal bằng một
tiện ích đưa ra các yêu cầu đọc và ghi ngẫu nhiên.
Chúng tôi đã tìm thấy một số cảnh báo sai do triển khai khóa người
đọc-người viết riêng tư. Chúng dễ dàng bị loại bỏ bằng cách sử dụng chú
thích. Chúng tôi cũng phát hiện ra một cuộc đua thực sự trong GMapCh
CheckServerThread () thông thường. Quy trình này được chạy bởi một
luồng duy nhất và kiểm tra định kỳ để đảm bảo rằng các máy chủ lân cận
đang chạy. Tuy nhiên, khi làm như vậy, nó đọc trường trạng thái gmap->
mà không giữ khóa gmapState (tất cả các luồng khác giữ trước khi viết
gmap-> trạng thái).
Chúng tôi đã tìm thấy hai cuộc đua trong đó các biến toàn cầu chứa số
liệu thống kê đã được sửa đổi mà không cần khóa. Những cuộc đua này
là có chủ đích, dựa trên cơ sở rằng việc khóa là tốn kém và số liệu thống
kê của máy chủ chỉ cần gần đúng.
Cuối cùng, chúng tôi đã tìm thấy một cảnh báo sai mà chúng tôi
không thể chú thích. Hàm GmapCh_Write2 () chia một số luồng và
chuyển từng tham chiếu tới một thành phần của khung ngăn xếp của
GmapCh_Write2. GmapCh_Write2 () triển khai một cấu trúc giống như
phép nối để giữ cho khung ngăn xếp hoạt động cho đến khi các luồng
quay trở lại. Nhưng Eraser không khởi động lại bộ nhớ bóng cho mỗi
khung ngăn xếp mới; do đó, việc sử dụng lại bộ nhớ ngăn xếp cho các
trường hợp khác nhau của khung ngăn xếp dẫn đến báo động giả.
4.4Bài tập đại học
Đối lập với kinh nghiệm của chúng tôi với các chương trình máy chủ đa
luồng trưởng thành, hai đồng nghiệp của chúng tôi tại Đại học
Washington đã sử dụng Eraser để kiểm tra các loại lỗi đồng bộ hóa được
tìm thấy trong các bài tập về nhà do lớp hệ điều hành đại học của họ tạo
ra (giao tiếp cá nhân, SE Choi và EC Lewis, 1997). Chúng tôi báo cáo kết
quả của họ ở đây để chứng minh cách Eraser hoạt động với cơ sở mã ít
phức tạp hơn.
Lớp được yêu cầu hoàn thành bốn bài tập đa luồng tiêu chuẩn. Các
nhiệm vụ này có thể được phân loại đại khái là cấp thấp (xây dựng khóa
từ test-and-set), cấp luồng (xây dựng một gói luồng nhỏ), cấp đồng bộ
hóa (xây dựng semaphores và mutexes) và cấp ứng dụng (kiểu nhà sản
xuất / người tiêu dùng các vấn đề). Mỗi nhiệm vụ được xây dựng dựa
trên việc thực hiện nhiệm vụ trước đó. Các đồng nghiệp của chúng tôi đã
sử dụng Eraser để kiểm tra từng bài tập này cho khoảng 40 nhóm; tổng
cộng khoảng 100 bài tập có thể chạy được đã được nộp (không phải tất cả
các nhóm đều hoàn thành tất cả các bài tập; một số không biên dịch; và
một số nhóm ngay lập tức bị khóa). Trong số các nhiệm vụ “đang hoạt
động” này, 10% có các cuộc đua dữ liệu được tìm thấy bởi Eraser. Những
nguyên nhân này là do quên lấy khóa, lấy khóa trong khi ghi nhưng
không dùng để đọc, sử dụng các khóa khác nhau để bảo vệ cùng một cấu
trúc dữ liệu vào những thời điểm khác nhau và quên yêu cầu lại các khóa
đã được phát hành trong một vòng lặp.
Eraser cũng báo cáo một cảnh báo giả được kích hoạt bởi một hàng
đợi được bảo vệ ngầm các phần tử bằng cách truy cập hàng đợi thông qua
các trường đầu và đuôi bị khóa (giống như đối tượng CacheS của Vesta).
4.5Hiệu quả và Độ nhạy
Vì Eraser sử dụng một phương pháp kiểm tra nên nó không thể chứng
minh rằng một chương trình không có các cuộc đua dữ liệu. Nhưng
chúng tôi tin rằng Eraser hoạt động tốt, so với kiểm tra và gỡ lỗi thủ
công, và kiểm tra của Eraser không nhạy cảm lắm với việc xen kẽ bộ lập
lịch. Để kiểm tra những niềm tin này, chúng tôi đã thực hiện hai thí
nghiệm bổ sung.
Chúng tôi đã tham khảo lịch sử chương trình của Ni2 và giới thiệu lại
hai chủng tộc dữ liệu đã tồn tại trong các phiên bản trước. Lỗi đầu tiên là
quyền truy cập không khóa vào số lượng tham chiếu được sử dụng để thu
thập rác cấu trúc dữ liệu tệp. Cuộc đua khác là do không thực hiện được
một khóa bổ sung cần thiết để bảo vệ cấu trúc dữ liệu của một chương
trình con được gọi ở giữa một thủ tục lớn. Các chủng tộc này đã tồn tại
trong mã nguồn Ni2 trong vài tháng trước khi chúng được tác giả chương
trình tìm thấy và sửa theo cách thủ công. Sử dụng Eraser, một người
trong chúng tôi có thể xác định vị trí của cả hai cuộc đua trong vài phút
mà không được cung cấp bất kỳ thông tin nào về vị trí của các cuộc đua
hoặc cách chúng được gây ra. Phải mất 30 phút để sửa cả hai lỗi và xác
minh sự vắng mặt của báo cáo cuộc đua tiếp theo.
Chúng tôi đã kiểm tra vấn đề độ nhạy bằng cách chạy lại Ni2 và Vesta
thử nghiệm, nhưng chỉ sử dụng hai luồng đồng thời thay vì 10. Nếu
Eraser nhạy cảm với sự khác biệt trong việc đan xen luồng thì chúng tôi
sẽ tìm thấy một tập hợp các báo cáo chủng tộc khác nhau. Trên thực tế,
chúng tôi đã tìm thấy các báo cáo cuộc đua giống nhau (mặc dù đôi khi
theo thứ tự khác nhau) trên nhiều lần chạy bằng cách sử dụng hai chủ đề
hoặc 10.
5. RÚT KINH NGHIỆM BỔ SUNG
Trong phần này, chúng tôi đề cập ngắn gọn đến hai chủ đề khác, mỗi chủ đề
liên quan đến hình thức kiểm tra động để tìm lỗi đồng bộ hóa trong các
chương trình đa luồng mà chúng tôi đã thử nghiệm và tin rằng nó quan trọng
và đầy hứa hẹn, nhưng chúng tôi đã không triển khai trong Eraser.
Chủ đề đầu tiên là bảo vệ bằng nhiều khóa. Một số chương trình bảo vệ
một số biến được chia sẻ bằng nhiều khóa thay vì một khóa duy nhất. Trong
trường hợp này, quy tắc là mọi luồng ghi biến phải giữ tất cả các khóa bảo
vệ và mọi luồng đọc biến phải giữ ít nhất một khóa bảo vệ. Chính sách này
chỉ cho phép một cặp truy cập đồng thời nếu cả hai quyền truy cập đều được
đọc và do đó ngăn chặn các cuộc chạy đua dữ liệu.
Sử dụng nhiều khóa bảo vệ theo một số cách tương tự như sử dụng khóa
đầu đọc và khóa ghi, nhưng nó không nhằm mục đích tăng tính đồng thời
cũng như tránh bế tắc trong một chương trình có chứa cuộc gọi lên.
Sử dụng phiên bản Eraser trước đó đã phát hiện điều kiện chủng tộc trong
các chương trình Modula-3 đa luồng, chúng tôi nhận thấy rằng thuật toán
Lockset đã báo cáo cảnh báo sai cho các chương trình Trestle [Manasse và
Nelson 1991] bảo vệ các vị trí được chia sẻ bằng nhiều khóa, vì mỗi trong số
hai trình đọc có thể truy cập vị trí trong khi giữ hai ổ khóa khác nhau. Như
một thử nghiệm, chúng tôi đã xử lý vấn đề bằng cách sửa đổi thuật toán
Lockset để tinh chỉnh bộ ứng viên chỉ để ghi, trong khi kiểm tra nó cho cả
lần đọc và ghi, như sau:
Điều này đã ngăn chặn các cảnh báo sai, nhưng việc sửa đổi này có thể
gây ra các âm tính giả. Ví dụ: nếu một luồng t1 đọc v trong khi giữ khóa m1
và một luồng t2 ghi v trong khi giữ khóa m2, thì việc vi phạm kỷ luật khóa
sẽ chỉ được báo cáo nếu lần ghi trước lần đọc. Nói chung, phiên bản sửa đổi
sẽ hoạt động tốt chỉ khi trường hợp thử nghiệm gây ra đủ số lần đọc biến
được chia sẻ để thực hiện theo các lần ghi tương ứng.
Về mặt lý thuyết, có thể xử lý nhiều khóa bảo vệ mà không có bất kỳ
nguy cơ âm tính giả nào, nhưng cấu trúc dữ liệu được yêu cầu (bộ khóa thay
vì chỉ bộ khóa) dường như có chi phí phức tạp vượt quá khả năng thu được.
Vì chúng tôi không thoải mái với âm tính giả và vì kỹ thuật khóa nhiều bảo
vệ không phổ biến, nên phiên bản Eraser hiện tại bỏ qua kỹ thuật này, tạo ra
cảnh báo sai cho các chương trình sử dụng nó.
Chủ đề thứ hai là bế tắc. Nếu cuộc đua dữ liệu là Scylla, thì bế tắc là
Charybdis
Một kỷ luật đơn giản để tránh bế tắc là chọn thứ tự từng phần trong số tất
cả các khóa và lập trình từng luồng sao cho bất cứ khi nào nó giữ nhiều hơn
một khóa, nó sẽ thu thập chúng theo thứ tự tăng dần. Kỷ luật này tương tự
như kỷ luật khóa để tránh chạy đua dữ liệu: nó phù hợp để kiểm tra bằng
giám sát động và dễ dàng tạo ra một trường hợp thử nghiệm cho thấy sự vi
phạm kỷ luật hơn là tạo ra một trường hợp thử nghiệm thực sự gây ra bế tắc.
Đối với một thử nghiệm độc lập, chúng tôi đã chọn một ứng dụng Trestle
lớn được biết là có đồng bộ hóa phức tạp (formedit, trình chỉnh sửa giao diện
người dùng chế độ xem kép), ghi lại tất cả các lần nhận khóa và kiểm tra
xem liệu đơn đặt hàng có tồn tại trên các ổ khóa hay không được tôn trọng
bởi mọi chủ đề. Một vài giây sau khi khởi động formedit, màn hình thử
nghiệm của chúng tôi đã phát hiện ra một chu kỳ khóa, cho thấy rằng không
tồn tại thứ tự từng phần. Việc kiểm tra chu kỳ chặt chẽ cho thấy một sự bế
tắc tiềm ẩn trong formedit. Chúng tôi coi đây là một kết quả đầy hứa hẹn và
phỏng đoán rằng kiểm tra bế tắc dọc theo những dòng này sẽ là một bổ sung
hữu ích cho Eraser. Tuy nhiên, cần phải làm việc nhiều hơn để lập danh mục
các biến thể âm thanh và hữu ích trên kỷ luật thứ tự từng phần và phát triển
các chú thích để ngăn chặn các cảnh báo sai.
6. CONCLUSION
Các nhà thiết kế phần cứng đã học cách thiết kế để có thể kiểm tra được. Các
lập trình viên sử dụng các luồng cũng phải học như vậy. Nó là không đủ để
viết một chương trình chính xác; Tính đúng đắn phải được chứng minh, lý
tưởng nhất là bằng cách kiểm tra tĩnh, thực tế bằng sự kết hợp của kiểm tra
tĩnh từng phần, sau đó là kiểm tra động có kỷ luật.
Bài viết này đã mô tả những ưu điểm của việc thực thi một kỷ luật khóa
đơn giản thay vì kiểm tra các chủng tộc trong các chương trình song song
nói chung sử dụng nhiều nguyên thủy đồng bộ hóa khác nhau và đã chứng
minh rằng với kỹ thuật này, việc kiểm tra động các chương trình đa luồng
sản xuất cho các chủng tộc dữ liệu là rất thực tế.
Các lập trình viên trong lĩnh vực hệ điều hành dường như xem các công
cụ phát hiện chủng tộc động là bí truyền và không thực tế. Thay vào đó, kinh
nghiệm của chúng tôi khiến chúng tôi tin rằng chúng là một cách thực tế và
hiệu quả để tránh các cuộc chạy đua dữ liệu và việc phát hiện cuộc đua động
phải là một quy trình tiêu chuẩn trong bất kỳ nỗ lực kiểm tra có kỷ luật nào
đối với một chương trình đa luồng. Khi việc sử dụng đa luồng mở rộng, sự
không đáng tin cậy do các cuộc đua dữ liệu gây ra cũng sẽ tăng lên, trừ khi
các phương pháp tốt hơn được sử dụng để loại bỏ chúng. Chúng tôi tin rằng
phương pháp Lockset được triển khai trong Eraser là đầy hứa hẹn.
ACKNOWLEDGMENTS
Chúng tôi xin cảm ơn những cá nhân sau đây đã đóng góp cho dự án này.
Sung-Eun Choi và E. Christoper Lewis chịu trách nhiệm về tất cả các thí
nghiệm ở bậc đại học. Alan Heydon, Dave Detlefs, Chandu Thekkath và
Edward Lee đã đưa ra lời khuyên chuyên môn về Vesta và Petal. Puneet
Kumar đã làm việc trên một phiên bản trước đó của Eraser. Cynthia
Hibbard, Brian Bershad, Michael Ernst, Paulo Guedes, Wilson Hsieh, Terri
Watson, và những người đánh giá SOSP và TOCS đã cung cấp phản hồi hữu
ích về các bản thảo trước đó của bài viết này.
Question
Eraser chỉ đảm bảo rằng dữ liệu được bảo vệ bởi một bộ khóa nhất quán.
Đưa ra một bản phác thảo trực quan về một loại điều kiện race mà nó sẽ bỏ
lỡ, một ví dụ và giải thích cách Eraser có thể được mở rộng để xử lý chúng.
https://blog.acolyer.org/2015/01/28/eraser-a-dynamic-data-race-detector-for-
multi-threaded-programs/
http://www.cs.cmu.edu/afs/cs/academic/class/15712-f15/www/lectures/06-
eraser.pdf
https://dl.acm.org/doi/10.1145/265924.265927
http://pages.cs.wisc.edu/~swift/classes/cs736-
fa12/blog/2012/10/eraser_a_dynamic_data_race_det.html
https://slidetodoc.com/eraser-a-dynamic-data-race-detector-for-
multithreaded-2/