Một spinlock trong Linux là gì?


32

Tôi muốn biết chi tiết về spinlocks của Linux; ai đó có thể giải thích chúng cho tôi?

Câu trả lời:


34

Khóa xoay là một cách để bảo vệ tài nguyên được chia sẻ khỏi bị sửa đổi bởi hai hoặc nhiều quy trình cùng một lúc. Quá trình đầu tiên cố gắng sửa đổi tài nguyên "mua lại" khóa và tiếp tục trên đường đi, thực hiện những gì cần thiết với tài nguyên. Bất kỳ quy trình nào khác sau đó cố gắng để có được khóa đều bị dừng; họ được cho là "quay tại chỗ" chờ khóa được phát hành theo quy trình đầu tiên, do đó, tên spin lock.

Nhân Linux sử dụng khóa spin cho nhiều thứ, chẳng hạn như khi gửi dữ liệu đến một thiết bị ngoại vi cụ thể. Hầu hết các thiết bị ngoại vi phần cứng không được thiết kế để xử lý nhiều cập nhật trạng thái đồng thời. Nếu hai sửa đổi khác nhau phải xảy ra, một phải tuân thủ nghiêm ngặt cái kia, chúng không thể trùng nhau. Khóa xoay cung cấp sự bảo vệ cần thiết, đảm bảo rằng các sửa đổi xảy ra cùng một lúc.

Khóa spin là một vấn đề bởi vì các khối quay của lõi CPU không thể thực hiện bất kỳ công việc nào khác. Mặc dù nhân Linux cung cấp các dịch vụ đa nhiệm cho các chương trình không gian người dùng chạy bên dưới, nhưng cơ sở đa nhiệm đa năng đó không mở rộng sang mã hạt nhân.

Tình trạng này đang thay đổi và đã xảy ra đối với hầu hết sự tồn tại của Linux. Cho đến Linux 2.0, hạt nhân gần như hoàn toàn là một chương trình tác vụ đơn lẻ: bất cứ khi nào CPU chạy mã hạt nhân, chỉ có một lõi CPU được sử dụng, bởi vì có một khóa quay duy nhất bảo vệ tất cả các tài nguyên được chia sẻ, được gọi là Khóa hạt nhân lớn (BKL ). Bắt đầu với Linux 2.2, BKL đang dần bị chia thành nhiều khóa độc lập mà mỗi khóa bảo vệ một lớp tài nguyên tập trung hơn. Ngày nay, với kernel 2.6, BKL vẫn tồn tại, nhưng nó chỉ được sử dụng bởi mã thực sự cũ không thể dễ dàng chuyển sang một số khóa chi tiết hơn. Giờ đây, một hộp đa lõi có thể có mọi CPU chạy mã hạt nhân hữu ích.

Có giới hạn đối với tiện ích phá vỡ BKL vì nhân Linux thiếu đa nhiệm chung. Nếu lõi CPU bị chặn quay trên khóa spin kernel, nó không thể được sửa lại, để đi làm điều gì khác cho đến khi khóa được giải phóng. Nó chỉ ngồi và quay cho đến khi khóa được phát hành.

Khóa xoay có hiệu quả có thể biến hộp 16 lõi của quái vật thành hộp đơn lõi, nếu khối lượng công việc lớn đến mức mọi lõi luôn chờ đợi một khóa xoay. Đây là giới hạn chính đối với khả năng mở rộng của nhân Linux: nhân đôi lõi CPU từ 2 lên 4 có thể sẽ tăng gần gấp đôi tốc độ của một hộp Linux, nhưng nhân đôi từ 16 lên 32 có lẽ sẽ không, với hầu hết khối lượng công việc.


@Warren: Một vài nghi ngờ- Tôi muốn biết thêm về Khóa hạt nhân lớn này và ý nghĩa của nó. Tôi cũng không hiểu đoạn cuối "nhân đôi lõi CPU từ 2 lên 4 có thể sẽ tăng gần gấp đôi tốc độ của một hộp Linux, nhưng nhân đôi nó từ 16 lên 32 có lẽ sẽ không"
Sen

2
Re: ý nghĩa của BKL: Tôi nghĩ rằng tôi đã làm rõ điều đó ở trên. Chỉ với một khóa trong kernel, bất cứ khi nào hai lõi cố gắng làm điều gì đó được bảo vệ bởi BKL, một lõi sẽ bị chặn trong khi lần đầu tiên kết thúc bằng cách sử dụng tài nguyên được bảo vệ của nó. Khóa càng mịn, khả năng điều này sẽ xảy ra càng thấp, do đó việc sử dụng bộ xử lý càng lớn.
Warren Young

2
Re: nhân đôi: Tôi có nghĩa là có luật giảm lợi nhuận khi thêm lõi xử lý vào máy tính. Khi số lượng lõi tăng lên, cơ hội hai hoặc nhiều trong số chúng sẽ cần truy cập vào một tài nguyên được bảo vệ bởi một khóa cụ thể. Tăng độ chi tiết của khóa giúp giảm khả năng xảy ra va chạm như vậy, nhưng cũng có quá nhiều chi phí. Bạn có thể dễ dàng thấy điều này trong các siêu máy tính, thường có hàng ngàn bộ xử lý hiện nay: hầu hết các khối lượng công việc đều không hiệu quả vì chúng không thể tránh được nhiều bộ xử lý do tranh chấp tài nguyên được chia sẻ.
Warren Young

1
Mặc dù đây là một lời giải thích thú vị (+1 cho điều đó), tôi không nghĩ rằng nó có hiệu quả trong việc truyền đạt sự khác biệt giữa spinlocks và các loại khóa khác.
Gilles 'SO- ngừng trở nên xấu xa'

2
Nếu người ta muốn biết sự khác biệt giữa khóa xoay và, giả sử, một semaphore, đó là một câu hỏi khác. Một câu hỏi hay nhưng tiếp tuyến khác là, thiết kế nhân Linux có gì khiến cho các khóa xoay trở thành lựa chọn tốt thay vì một thứ phổ biến hơn trong mã người dùng, như một mutex. Câu trả lời này phân chia rất nhiều như là.
Warren Young

11

Khóa xoay là khi một quá trình liên tục bỏ phiếu để loại bỏ khóa. Nó được coi là xấu vì quá trình này đang tiêu thụ chu kỳ (thường là) không cần thiết. Nó không dành riêng cho Linux, mà là một mẫu lập trình chung. Và trong khi nó thường được coi là một thực tiễn xấu, trên thực tế, đó là giải pháp chính xác; có những trường hợp chi phí sử dụng bộ lập lịch cao hơn (tính theo chu kỳ CPU) so với chi phí của một vài chu kỳ mà spinlock dự kiến ​​sẽ kéo dài.

Ví dụ về spinlock:

#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
  sleep 1
done
#do stuff

Thường xuyên có một cách để tránh khóa quay. Đối với ví dụ cụ thể này, có một công cụ Linux được gọi là inotifywait (nó thường không được cài đặt theo mặc định). Nếu nó được viết bằng C, bạn chỉ cần sử dụng API inotify mà Linux cung cấp.

Ví dụ tương tự, sử dụng inotifywait cho thấy cách thực hiện điều tương tự mà không cần khóa xoay:

#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff

Vai trò của người lập lịch ở đó là gì? Hay nó có vai trò gì?
Sen

1
Trong phương pháp khóa quay, bộ lập lịch sẽ tiếp tục quá trình cứ sau 1 giây để thực hiện nhiệm vụ của mình (chỉ đơn giản là kiểm tra sự tồn tại của tệp). Trong ví dụ inotifywait, bộ lập lịch chỉ tiếp tục lại quá trình khi tiến trình con (inotifywait) thoát. Inotifywait cũng đang ngủ; bộ lập lịch chỉ tiếp tục lại khi sự kiện inotify xảy ra.
Shawn J. Goff

Vậy kịch bản này được xử lý như thế nào trong một hệ thống xử lý lõi đơn?
Sen

@Sen: Điều này được giải thích khá tốt trong Trình điều khiển thiết bị Linux .
Gilles 'SO- ngừng trở nên xấu xa'

1
Kịch bản bash đó là một ví dụ tồi tệ của spinlock. Bạn đang tạm dừng quá trình làm cho nó đi ngủ. Một spinlock không bao giờ ngủ. Trên một máy lõi đơn, nó chỉ tạm dừng bộ lập lịch và tiếp tục (không phải chờ đợi bận rộn)
Martin

7

Khi một luồng cố gắng để có được một khóa, ba điều có thể xảy ra nếu nó thất bại, nó có thể thử và chặn, nó có thể thử và tiếp tục, nó có thể thử đi ngủ để báo cho hệ điều hành đánh thức nó khi có sự kiện xảy ra.

Bây giờ hãy thử và tiếp tục sử dụng ít thời gian hơn sau đó thử và chặn. Chúng ta hãy nói rằng hiện tại một "thử và tiếp tục" sẽ mất i đơn vị thời gian và một "thử và chặn" sẽ mất một trăm.

Bây giờ chúng ta hãy giả sử rằng trung bình một luồng sẽ mất 4 đơn vị thời gian giữ khóa. Thật lãng phí khi chờ đợi 100 đơn vị. Vì vậy, thay vì bạn viết một vòng lặp "thử và tiếp tục". Trong lần thử thứ tư, bạn thường sẽ có được khóa. Đây là một khóa quay. Nó được gọi như vậy bởi vì sợi chỉ tiếp tục quay tại chỗ cho đến khi nó có được khóa.

Một biện pháp an toàn được thêm vào là giới hạn số lần vòng lặp chạy. Vì vậy, trong ví dụ bạn thực hiện chạy vòng lặp for, ví dụ sáu lần, nếu thất bại thì bạn "thử và chặn".

Nếu bạn biết rằng một luồng sẽ luôn giữ khóa trong 200 đơn vị, thì bạn đang lãng phí thời gian của máy tính cho mỗi lần thử và tiếp tục.

Vì vậy, cuối cùng, một khóa quay có thể rất hiệu quả hoặc lãng phí. Thật lãng phí khi thời gian "thông thường" để giữ khóa cao hơn thời gian để "thử và chặn". Sẽ hiệu quả khi thời gian thông thường để giữ khóa nhỏ hơn nhiều so với thời gian để 'thử và chặn ".

Ps: Cuốn sách cần đọc trên chủ đề là "A Thread Primer", nếu bạn vẫn có thể tìm thấy nó.


chủ đề tiếp tục quay tại chỗ cho đến khi nó được khóa . Bạn có thể vui lòng cho tôi biết quay này là gì? Có giống như nó đến với Wait_queue và được Trình lập lịch thực hiện không? Có lẽ tôi đang cố gắng vào cấp thấp, nhưng vẫn không thể giữ được sự nghi ngờ của mình.
Sen

Quay trong số 3-4 hướng dẫn trong vòng lặp, về cơ bản.
Paul Stelian

5

Một khóa là một cách để hai hoặc nhiều nhiệm vụ (quy trình, chủ đề) để đồng bộ hóa. Cụ thể, khi cả hai tác vụ cần truy cập không liên tục vào một tài nguyên chỉ có thể được sử dụng bởi một tác vụ tại một thời điểm, đó là cách để các tác vụ sắp xếp không sử dụng tài nguyên cùng một lúc. Để truy cập tài nguyên, một tác vụ phải thực hiện các bước sau:

take the lock
use the resource
release the lock

Không thể khóa nếu không có nhiệm vụ khác đã thực hiện. (Hãy nghĩ về khóa như một đối tượng mã thông báo vật lý. Đối tượng đang ở trong ngăn kéo hoặc ai đó có trong tay. Chỉ có người đang giữ đối tượng mới có thể truy cập tài nguyên.) Vì vậy, người cầm khóa thực sự có nghĩa là chờ đợi cho đến khi không ai khác có khóa, sau đó lấy nó.

Từ quan điểm cấp cao, có hai cách chính để thực hiện khóa: spinlocks và điều kiện. Với spinlocks , lấy khóa có nghĩa là chỉ quay Spin (tức là không làm gì trong vòng lặp) cho đến khi không có ai khác có khóa. Với các điều kiện, nếu một tác vụ cố gắng khóa nhưng bị chặn vì một tác vụ khác giữ nó, người mới sẽ vào hàng chờ; các hoạt động phát hành báo hiệu cho bất kỳ nhiệm vụ chờ đợi mà khóa hiện có sẵn.

(Những giải thích này không đủ để cho phép bạn thực hiện khóa, vì tôi chưa nói gì về nguyên tử. Nhưng nguyên tử không quan trọng ở đây.)

Spinlocks rõ ràng là lãng phí: nhiệm vụ chờ tiếp tục kiểm tra xem khóa có được thực hiện hay không. Vậy tại sao và khi nào nó được sử dụng? Spinlocks thường rất rẻ để có được trong trường hợp khóa không được giữ. Điều này làm cho nó hấp dẫn khi cơ hội cho khóa được tổ chức là nhỏ. Hơn nữa, spinlocks chỉ khả thi nếu có được khóa sẽ không mất nhiều thời gian. Vì vậy, spinlocks có xu hướng được sử dụng trong các tình huống chúng sẽ được giữ trong một thời gian rất ngắn, do đó, hầu hết các nỗ lực được dự kiến ​​sẽ thành công ngay lần thử đầu tiên và những người cần chờ đợi không phải chờ đợi lâu.

Có một lời giải thích tốt về spinlocks và các cơ chế đồng thời khác của nhân Linux trong Trình điều khiển thiết bị Linux , chương 5.


Điều gì sẽ là một cách tốt để thực hiện các nguyên thủy đồng bộ hóa khác? Lấy một spinlock, kiểm tra xem người khác có thực sự nguyên thủy đang được thực hiện không, sau đó sử dụng trình lập lịch biểu hoặc cấp quyền truy cập? Chúng ta có thể coi khối () được đồng bộ hóa trong Java là một dạng spinlock không và trong các nguyên thủy được triển khai đúng cách chỉ cần sử dụng một spinlock?
Paul Stelian

@PaulStelian Thực sự rất phổ biến khi thực hiện các khóa chậm chậm của Cameron theo cách này. Tôi không biết đủ Java để trả lời phần đó, nhưng tôi nghi ngờ điều đó synchronizedsẽ được thực hiện bởi một spinlock: một synchronizedkhối có thể chạy trong một thời gian rất dài. synchronizedlà một cấu trúc ngôn ngữ để làm cho các khóa dễ sử dụng trong một số trường hợp nhất định, không phải là nguyên thủy để xây dựng các nguyên hàm đồng bộ hóa lớn hơn.
Gilles 'SO- ngừng trở nên xấu xa'

3

Spinlock là một khóa hoạt động bằng cách vô hiệu hóa bộ lập lịch và có thể làm gián đoạn (biến thể irqsave) trên lõi cụ thể mà khóa được mua. Nó khác với một mutex ở chỗ nó vô hiệu hóa lập lịch để chỉ luồng của bạn có thể chạy trong khi spinlock được giữ. Một mutex cho phép các luồng ưu tiên cao hơn khác được lên lịch trong khi nó được giữ nhưng không cho phép chúng thực hiện đồng thời phần được bảo vệ. Vì spinlocks vô hiệu hóa đa nhiệm, bạn không thể lấy một spinlock và sau đó gọi một số mã khác sẽ cố gắng để có được một mutex. Mã của bạn bên trong phần spinlock không bao giờ được ngủ (mã thường ngủ khi nó gặp một mutex bị khóa hoặc semaphore trống).

Một điểm khác biệt với mutex là các chủ đề thường xếp hàng cho một mutex để một mutex bên dưới có một hàng đợi. Trong khi spinlock chỉ đảm bảo rằng không có luồng nào khác sẽ chạy ngay cả khi nó phải. Do đó, bạn không bao giờ được giữ một spinlock khi gọi các chức năng bên ngoài tệp của bạn mà bạn không chắc chắn sẽ không ngủ.

Khi bạn muốn chia sẻ spinlock của mình với một ngắt, bạn phải sử dụng biến thể irqsave. Điều này sẽ không chỉ vô hiệu hóa lịch trình mà còn vô hiệu hóa các ngắt. Nó có ý nghĩa phải không? Spinlock hoạt động bằng cách đảm bảo không có gì khác sẽ chạy. Nếu bạn không muốn ngắt để chạy, bạn vô hiệu hóa nó và tiến hành an toàn vào phần quan trọng.

Trên máy đa lõi, một spinlock sẽ thực sự quay tròn chờ đợi một lõi khác giữ khóa để giải phóng nó. Việc quay này chỉ xảy ra trên các máy đa lõi vì trên các lõi đơn thì điều đó không thể xảy ra (bạn giữ spinlock và tiếp tục hoặc bạn không bao giờ chạy cho đến khi khóa được giải phóng).

Spinlock không lãng phí khi nó có ý nghĩa. Đối với các phần quan trọng rất nhỏ, sẽ rất lãng phí khi phân bổ hàng đợi nhiệm vụ mutex so với việc chỉ tạm dừng bộ lập lịch trong vài micrô giây để hoàn thành công việc quan trọng. Nếu bạn cần ngủ hoặc giữ khóa trên một thao tác io (có thể ngủ) thì hãy sử dụng mutex. Chắc chắn không bao giờ khóa một spinlock và sau đó cố gắng phát hành nó trong một ngắt. Trong khi điều này sẽ hoạt động, nó sẽ giống như crap arduino của while (flagnotset); trong trường hợp như vậy sử dụng một semaphore.

Lấy một spinlock khi bạn cần loại trừ lẫn nhau đơn giản cho các khối giao dịch bộ nhớ. Lấy một mutex khi bạn muốn nhiều luồng dừng lại ngay trước khóa mutex và sau đó là chuỗi ưu tiên cao nhất được chọn để tiếp tục khi mutex trở nên miễn phí và khi bạn khóa và phát hành trong cùng một luồng. Lấy một semaphore khi bạn có ý định đăng nó trong một chủ đề hoặc một ngắt và đưa nó trong một chủ đề khác. Đó là ba cách hơi khác nhau để đảm bảo loại trừ lẫn nhau và chúng được sử dụng cho các mục đích hơi khác nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.