Để tạo một tệp để sử dụng làm chỉ báo khóa, bạn có thể sử dụng lệnh gọi hệ thống mở được xác định trong fcntl.h mà bạn đã gặp trong một chương trước đó với các cờ O_CREAT và O_EXCL được
Trang 1File locking
File locking là một phần rất quan trọng của hệ điều hành đa nhiệm Chương trình thường xuyên cần chia sẻ dữ liệu, thường là thông qua các tệp và điều đó rất quan trọng là các chương trình đó có một số cách thiết lập quyền kiểm soát một tập tin Tập tin sau đó có thể được cập nhật một cách an toàn hoặc chương trình thứ hai có thể dừng lại chính nó từ việc cố gắng đọc một tập tin ở trạng thái thoáng qua trong khi một chương trình khác đang ghi vào nó Linux có một số tính năng mà bạn có thể sử dụng để khóa tệp Phương pháp đơn giản nhất là một kỹ thuật để tạo ra khóa các tập tin theo cách nguyên tử, để không có gì khác có thể xảy ra trong khi khóa đang được tạo Điều này mang lại cho một lập trình một phương pháp tạo các tệp được đảm bảo
là duy nhất và không thể được tạo đồng thời bởi một chương trình khác Phương pháp thứ hai tiên tiến hơn: nó cho phép các chương trình khóa các phần của tệp để truy cập độc quyền Có hai cách khác nhau để đạt được hình thức khóa thứ hai này Chúng tôi
sẽ chỉ nhìn vào một chi tiết, vì cái thứ hai rất giống nhau - nó chỉ có giao diện lập trình hơi khác
Creating Lock Files
Nhiều ứng dụng chỉ cần có khả năng tạo tệp khóa cho tài nguyên Các chương trình khác sau đó có thể kiểm tra các tập tin để xem liệu họ có được phép truy cập tài nguyên Thông thường, các tệp khóa này ở một vị trí đặc biệt với tên liên quan đến tài nguyên được kiểm soát
Ví dụ: khi sử dụng modem, Linux sẽ tạo một tệp khóa, thường sử dụng một thư mục trong / var /
thư mục spool
Hãy nhớ rằng các tập tin khóa chỉ hoạt động như các chỉ số; các chương trình cần hợp tác để sử dụng chúng họ đang
gọi là khóa tư vấn trái ngược với khóa bắt buộc, trong đó hệ thống sẽ thực thi hành vi khóa
Để tạo một tệp để sử dụng làm chỉ báo khóa, bạn có thể sử dụng lệnh gọi hệ thống mở được xác định trong fcntl.h (mà
bạn đã gặp trong một chương trước đó) với các cờ O_CREAT và O_EXCL được đặt Điều này cho phép bạn kiểm tra xem
tập tin đã không tồn tại và sau đó tạo nó trong một hoạt động nguyên tử duy nhất
Trang 2Lần đầu tiên bạn chạy chương trình:
$ /lock1
Open succeeded
nhưng lần sau bạn thử, bạn sẽ nhận được
$ /lock1
Open failed with error 17
Làm thế nào nó hoạt động
Chương trình gọi mở để tạo một tệp có tên /tmp/LCK.test, sử dụng cờ O_CREAT và O_EXCL Các
Lần đầu tiên bạn chạy chương trình, tập tin không tồn tại, vì vậy cuộc gọi mở thành công Các lệnh sau đó của chương trình không thành công vì tệp đã tồn tại Để chương trình thành công trở lại, bạn phải
tự xóa tệp khóa
Trên các hệ thống Linux ít nhất, lỗi 17 đề cập đến EEXIST, một lỗi được sử dụng để chỉ ra rằng một tệp đã tồn tại
Số lỗi được xác định trong tệp tiêu đề errno.h hoặc, thông thường hơn, bởi các tệp được bao gồm bởi nó Trong này
trường hợp, định nghĩa, thực sự trong /usr/include/asm-generic/errno-base.h, đọc
#define EEXIST 17 /* File exists */
Đây là một lỗi thích hợp cho lỗi mở (O_CREAT | O_EXCL)
Trang 3Nếu một chương trình chỉ cần một tài nguyên dành riêng trong một thời gian ngắn thực hiện, thì thường được gọi là quan trọng
phần, nó sẽ tạo tập tin khóa bằng cách sử dụng lệnh gọi hệ thống mở trước khi vào phần quan trọng và sử dụng
cuộc gọi hệ thống hủy liên kết để xóa nó sau đó, khi chương trình thoát khỏi phần quan trọng
Bạn có thể chứng minh làm thế nào các chương trình có thể hợp tác với cơ chế khóa này bằng cách viết một mẫu
lập trình và chạy hai bản sao của nó cùng một lúc Bạn sẽ sử dụng cuộc gọi getpid mà bạn đã thấy trong
Chương 4; nó trả về định danh quy trình, một số duy nhất cho mỗi chương trình hiện đang thực thi
Để chạy chương trình, trước tiên bạn nên sử dụng lệnh này để đảm bảo rằng tệp khóa không tồn tại:
Trang 4$ rm -f /tmp/LCK.test2
Sau đó chạy hai bản sao của chương trình bằng cách sử dụng lệnh này:
$ /lock2 & /lock2
Điều này bắt đầu một bản sao của lock2 trong nền và một bản sao thứ hai chạy ở nền trước Đây là đầu ra:
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
1284 - I have exclusive access
1283 - Lock already present
1283 - I have exclusive access
1284 - Lock already present
Ví dụ trước cho thấy hai yêu cầu của cùng một chương trình đang hợp tác như thế nào Nếu bạn cố gắng
này, bạn sẽ gần như chắc chắn nhìn thấy các định danh quy trình khác nhau trong đầu
ra, nhưng hành vi chương trình sẽ
giống nhau
Làm thế nào nó hoạt động
Với mục đích trình diễn, bạn tạo vòng lặp chương trình 10 lần bằng vòng lặp while Các
sau đó chương trình cố gắng truy cập tài nguyên quan trọng bằng cách tạo một tệp khóa duy nhất, /tmp/LCK.test2 Nếu điều này
thất bại vì tập tin đã tồn tại, sau đó chương trình đợi trong một thời gian ngắn và thử lại Nếu nó thành công,
sau đó nó có thể truy cập vào tài nguyên và, trong phần được đánh dấu phần quan trọng của Hồi giáo, anh ấy thực hiện bất kỳ việc xử lý nào
được yêu cầu với quyền truy cập độc quyền
Trang 5Vì đây chỉ là một cuộc biểu tình, bạn chỉ chờ trong một khoảng thời gian ngắn Khi chương trình kết thúc với
tài nguyên, nó giải phóng khóa bằng cách xóa tệp khóa Sau đó nó có thể thực hiện một số xử lý khác
(chỉ có chức năng ngủ trong trường hợp này) trước khi thử hỏi lại khóa Tệp khóa hoạt động như một nhị phân
semaphore, cung cấp cho mỗi chương trình một câu trả lời có hoặc không cho câu hỏi, tôi có thể sử dụng tài nguyên không?
tìm hiểu thêm về semaphores trong Chương 14
Điều quan trọng là phải nhận ra rằng đây là một sự sắp xếp hợp tác và bạn phải viết các chương trình chính xác cho nó hoạt động Một chương trình không thể tạo tập tin khóa có thể chỉ cần xóa tập tin và thử lại Sau đó nó có thể tạo tệp khóa, nhưng
chương trình khác tạo tệp khóa không có cách nào để biết rằng nó không còn có quyền truy cập độc quyền vào tài nguyên
Locking Regions
Tạo tập tin khóa là tốt để kiểm soát truy cập độc quyền vào các tài nguyên như cổng nối tiếp hoặc không thường xuyên
truy cập các tệp, nhưng nó rất tốt để truy cập vào các tệp được chia sẻ lớn Giả sử bạn
có một tệp lớn được viết
bởi một chương trình nhưng được cập nhật bởi nhiều chương trình khác nhau cùng một lúc Điều này có thể xảy ra nếu một chương trình là
ghi nhật ký một số dữ liệu thu được liên tục trong một thời gian dài và đang được xử
lý bởi một số khác
các chương trình Các chương trình xử lý có thể được chờ đợi để chương trình đăng nhập kết thúc nó chạy liên tục
-vì vậy họ cần một số cách hợp tác để cung cấp quyền truy cập đồng thời vào cùng một tệp
Bạn có thể giải quyết tình huống này bằng cách khóa các vùng của tệp sao cho một phần cụ thể của tệp là
bị khóa, nhưng các chương trình khác có thể truy cập các phần khác của tệp Đây được gọi là phân đoạn tệp hoặc vùng tệp, khóa
Linux có (ít nhất) hai cách để làm điều này: sử dụng lệnh gọi hệ thống fcntl và sử dụng lệnh gọi lockf Nhìn có vẻ tốt
chủ yếu ở giao diện fcntl vì đó là giao diện được sử dụng phổ biến nhất lockf là hợp lý
tương tự, và trên Linux, thông thường chỉ là một giao diện thay thế cho fcntl Tuy nhiên, fcntl và lockf
các cơ chế khóa không hoạt động cùng nhau: Chúng sử dụng các triển khai cơ bản khác nhau, vì vậy bạn nên
Không bao giờ trộn lẫn hai loại cuộc gọi; dính vào cái này hay cái khác
Bạn đã gặp cuộc gọi fcntl trong Chương 3 Định nghĩa của nó là
Trang 6#include <fcntl.h>
int fcntl(int fildes, int command, );
fcntl hoạt động trên các mô tả tệp mở và, tùy thuộc vào tham số lệnh, có thể thực hiện khác nhau nhiệm vụ Ba tùy chọn lệnh quan tâm để khóa tệp như sau:
❑ F_GETLK
❑ F_SETLK
❑ F_SETLKW
Khi bạn sử dụng các đối số thứ ba này phải là một con trỏ tới một đàn struct, vì vậy nguyên mẫu có hiệu quả như sau:
int fcntl(int fildes, int command, struct flock *flock_structure);
Cấu trúc flock (file lock) phụ thuộc vào việc triển khai, nhưng nó sẽ chứa ít nhất các mục sau ,các thành viên:
❑ short l_type;
❑ short l_whence;
❑ off_t l_start;
❑ off_t l_len;
❑ pid_t l_pid;
Thành viên l_type lấy một trong một số giá trị, cũng được xác định trong fcntl.h Chúng được hiển thị trong bảng sau:
Các thành viên l_whence, l_start và l_len xác định một vùng - một tập hợp các byte liền kề - trong một tệp l_whence phải là một trong những CatchK_SET,
SEEK_CUR, SEEK_END (từ unistd.h) Những cái này tương ứng với sự khởi đầu, vị trí hiện tại và kết thúc của một tập tin, tương ứng l_whence định nghĩa phần bù cho
Trang 7l_start, byte đầu tiên trong khu vực, là tương đối Thông thường, đây sẽ là
XEMK_SET, vì vậy l_start được tính từ đầu tập tin Tham số l_len xác định số lượng byte trong vùng Tham số l_pid được sử dụng để báo cáo quá trình giữ khóa; xem mô
tả c
Mỗi byte trong một tệp chỉ có thể có một loại khóa duy nhất trên đó tại một thời điểm
và có thể bị khóa để chia sẻ truy cập, khóa để truy cập độc quyền, hoặc mở khóa Có khá nhiều tổ hợp lệnh và tùy chọn cho lệnh gọi fcntl, vì vậy hãy lần lượt xem xét từng cái.ủa F_GETLK
The F_GETLK Command
Lệnh đầu tiên của anh ta là F_GETLK Nó được khóa thông tin về tập tin fildes (tham
số đầu tiên) đã mở Nó không cố gắng khóa tập tin Quá trình gọi chuyển thông tin về loại khóa nó có thể muốn tạo và fcntl được sử dụng với lệnh F_GETLK trả về bất kỳ thông tin nào có thể ngăn chặn khóa xảy ra Các giá trị được sử dụng trong cấu trúc đàn được mô tả trong bảng sau:
Một quy trình có thể sử dụng lệnh gọi F_GETLK để xác định trạng thái khóa hiện tại trên một vùng của tệp Nó nên thiết lập cấu trúc đàn để chỉ ra loại khóa mà nó có thể yêu cầu và xác định vùng mà nó quan tâm trong Cuộc gọi fcntl trả về giá trị khác -1 nếu nó thành công Nếu tập tin đã có khóa ngăn chặn yêu cầu khóa thành công, nó ghi
đè lên cấu trúc đàn với các thông tin liên quan
Nếu khóa sẽ thành công, cấu trúc đàn không thay đổi Nếu cuộc gọi F_GETLK không thể nhận được thông tin, nó trả về -1 để chỉ sự thất bại Nếu cuộc gọi F_GETLK thành công (nghĩa là, nó sẽ trả về một giá trị khác -1), ứng dụng gọi điện phải kiểm tra nội dung của cấu trúc đàn để xác định xem nó đã được sửa đổi hay chưa Vì giá trị l_pid được đặt cho quá trình khóa (nếu tìm thấy), đây là trường thuận tiện để kiểm tra để xác định xem cấu trúc đàn đã được thay đổi
The F_SETLK Command
Lệnh này cố gắng khóa hoặc mở khóa một phần của tệp được tham chiếu bởi fildes Các giá trị được sử dụng trong
Cấu trúc flock (và khác với cấu trúc đàn được sử dụng bởi F_GETLK) như sau:
Trang 8Như với F_GETLK, khu vực bị khóa được xác định bởi các giá trị của l_start,
l_whence và l_len các lĩnh vực của cấu trúc đàn Nếu khóa thành công, fcntl trả về giá trị khác -1; trên thất bại, -1 là trả lại Hàm luôn trả về ngay lập tức
The F_SETLKW Command
Lệnh F_SETLKW giống như lệnh F_SETLK ở trên, ngoại trừ việc nếu nó có thể có được thì khóa, cuộc gọi sẽ đợi cho đến khi nó có thể Khi cuộc gọi này đã bắt đầu chờ,
nó sẽ chỉ trở lại khi khóa có thể thu được hoặc một tín hiệu xảy ra Chúng tôi bao gồm các tín hiệu trong Chương 11 Tất cả các khóa được giữ bởi một chương trình trên một tệp sẽ tự động bị xóa khi mô tả tệp có liên quan được đóng lại Điều này cũng tự động xảy ra khi chương trình kết thúc
Use of read and write with Locking
Khi bạn sử dụng tính năng khóa trên các vùng của tệp, điều đó rất quan trọng để sử dụng chức năng đọc cấp thấp hơn và viết các cuộc gọi để truy cập dữ liệu trong tệp, chứ không phải là fread và fwrite cấp cao hơn Điều này là cần thiết bởi vì fread và fwrite thực hiện việc đệm dữ liệu đọc hoặc ghi bên trong thư viện, do đó, thực hiện một cuộc gọi fread để đọc 100 byte đầu tiên của một tệp có thể (thực tế gần như chắc chắn sẽ) đọc nhiều hơn 100 byte và đệm dữ liệu bổ sung bên trong thư viện Nếu chương trình sử dụng fread để đọc 100 byte tiếp theo, nó thực sự sẽ đọc dữ liệu đã được lưu trong thư viện và không cho phép mức thấp đọc để kéo thêm dữ liệu từ tập tin Để xem tại sao đây là một vấn đề, hãy xem xét hai chương trình muốn cập nhật cùng một tệp Giả sử tập tin bao gồm 200 byte dữ liệu, tất cả các số không Chương trình đầu tiên bắt đầu trước và có được khóa ghi trên 100 đầu tiên byte của tập tin Sau
đó, nó sử dụng fread để đọc trong 100 byte đó Tuy nhiên, như thể hiện trong một chương trước, Fread sẽ đọc trước tối đa các byte BUFSIZ, vì vậy nó thực sự đọc toàn
bộ tệp vào bộ nhớ, nhưng chỉ chuyển 100 byte đầu tiên trở lại chương trình Chương trình thứ hai sau đó bắt đầu Nó nhận được một khóa ghi trên 100 byte thứ hai của chương trình Đây là thành công vì chương trình đầu tiên chỉ khóa 100 byte đầu tiên Chương trình thứ hai viết twos để byte 100 đến 199, đóng tệp, mở khóa và thoát Chương trình đầu tiên sau đó khóa 100 byte thứ hai của tệp và gọi fread để đọc chúng
Vì dữ liệu đó đã được thư viện đệm vào bộ nhớ, những gì chương trình thực sự nhìn thấy là 100 byte số không, không phải là 100 twos thực sự tồn tại trong tệp trên ổ đĩa
Trang 9cứng Vấn đề này không xảy ra khi bạn sử dụng đọc và viết Mô tả về việc khóa tập tin có vẻ hơi phức tạp, nhưng nó thực sự khó mô tả hơn nó là để sử dụng
Trang 10Làm thế nào nó hoạt động
Chương trình trước tiên tạo một tệp, mở nó cho cả đọc và viết, sau đó điền vào tệp với dữ liệu Sau đó, nó thiết lập hai vùng: vùng đầu tiên từ byte 10 đến 30, cho khóa được chia sẻ (đọc) và vùng thứ hai từ byte 40 đến 50, cho một khóa (ghi) độc quyền Sau đó, nó gọi fcntl để khóa hai vùng và chờ một vài phút trước khi đóng tệp và thoát Hình 7-1 hiển thị kịch bản này với các khóa khi chương trình bắt đầu chờ
Về bản thân, chương trình này không phải là rất hữu ích Bạn cần một chương trình thứ hai để kiểm tra các khóa, lock4.c
Trang 12Để kiểm tra khóa, trước tiên bạn cần chạy chương trình lock3; sau đó chạy chương trình lock4 để kiểm tra khóa tập tin Bạn làm điều này bằng cách thực hiện chương trình lock3 trong nền, với lệnh sau:
$ /lock3 &
$ process 1534 locking file
Dấu nhắc lệnh trả về vì lock3 đang chạy trong nền và sau đó bạn ngay lập tức chạy chương trình lock4 bằng lệnh sau:
$ /lock4
Đầu ra bạn nhận được, với một số thiếu sót cho ngắn gọn, như sau:
Testing F_WRLOCK on region from 0 to 5
F_WRLCK - Lock would succeed
Testing F_RDLOCK on region from 0 to 5
F_RDLCK - Lock would succeed
Trang 13Testing F_WRLOCK on region from 10 to 15
Lock would fail F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLOCK on region from 10 to 15
F_RDLCK - Lock would succeed
Testing F_WRLOCK on region from 15 to 20
Lock would fail F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLOCK on region from 15 to 20
F_RDLCK - Lock would succeed
Testing F_WRLOCK on region from 25 to 30
Lock would fail F_GETLK returned:
l_type 0, l_whence 0, l_start 10, l_len 20, l_pid 1534
Testing F_RDLOCK on region from 25 to 30
F_RDLCK - Lock would succeed
Testing F_WRLOCK on region from 40 to 45
Lock would fail F_GETLK returned:
l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 1534
Testing F_RDLOCK on region from 40 to 45
Lock would fail F_GETLK returned:
l_type 1, l_whence 0, l_start 40, l_len 10, l_pid 1534
Testing F_RDLOCK on region from 95 to 100
F_RDLCK - Lock would succeed
Làm thế nào nó hoạt động Đối với mỗi nhóm năm byte trong tệp, lock4 thiết lập cấu trúc vùng để kiểm tra các khóa trên tệp, mà nó sau đó sử dụng để xác định xem vùng
có thể bị ghi hoặc đọc bị khóa hay không Các thông tin trả về hiển thị các byte vùng, offset từ byte 0, điều đó sẽ khiến yêu cầu khóa không thành công Vì phần l_pid của cấu trúc được trả về chứa định danh quy trình của chương trình hiện có tệp bị khóa, chương trình đặt nó thành -1 (một giá trị không hợp lệ) và sau đó kiểm tra xem nó đã được thay đổi khi fcntl trả lại cuộc gọi Nếu khu vực hiện tại bị khóa, l_pid sẽ không thay đổi Để hiểu đầu ra, bạn cần xem tệp bao gồm fcntl.h (thường / usr / include / fcntl.h) để thấy rằng một l_type của 1 là từ định nghĩa của F_WRLCK là 1 và một l_type của 0 là từ định nghĩa của F_RDLCK là 0 Do đó, l_type của 1 cho bạn biết rằng khóa sẽ không thành công do khóa ghi hiện có và l_type bằng 0 là do khóa đọc hiện có Trên các vùng của tập tin đó lock3 chưa bị khóa, cả khóa chia sẻ và khóa độc quyền sẽ thành công Từ byte 10 đến 30, bạn có thể thấy rằng có thể có khóa chia sẻ,