fcntl和flock两个系统调用的区别【转】

 
1 fcntl和flock两个系统调用的区别
 
总的来说,flock函数只能锁定整个文件,无法锁定文件的某一区域。
而fcntl可以利用struct flock结构体,来实现文件里部分区域锁定的操作。

附:
fcntl(文件描述词操作)
相关函数 open,flock

表头文件 #include<unistd.h>
#include<fcntl.h>

定义函数 int fcntl(int fd , int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock * lock);

函数说明 fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令。
有以下几种情况:
F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述词,并且复制参数fd的文件描述词。执行成功则返回新复制的文件描述词。请参考 dup2()。F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会 关闭。
F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
F_GETFL 取得文件描述词状态旗标,此旗标为open()的参数flags。
F_SETFL 设置文件描述词状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。参数lock指针为flock 结构指针,定义如下
struct flock
{
short int l_type; /* 锁定的状态*/
short int l_whence;/*决定l_start位置*/
off_t l_start; /*锁定区域的开头位置*/
off_t l_len; /*锁定区域的大小*/
pid_t l_pid; /*锁定动作的进程*/
};
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置。
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置。

返回值 成功则返回0,若有错误则返回-1,错误原因存于errno.



flock(锁定文件或解除锁定)
相关函数 open,fcntl

表头文件 #include<sys/file.h>

定义函数 int flock(int fd,int operation);

函数说明 flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。

参数 operation有下列四种情况:
LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
LOCK_UN 解除文件锁定状态。
LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。
单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

返回值 返回0表示成功,若有错误则返回-1,错误代码存于errno。
   
  作者: 202.106.46.*  2009-11-4 14:13  
 
2 附:File locking
 
File locking

File locking is a mechanism that restricts access to a computer file by only allowing one user or process access at any specific time. The purpose of locking is to prevent the classic interceding update scenario (see race condition).

The interceding update problem may be illustrated as in the following example:

Process A reads a customer record from a file containing account information, including the customer's account balance and phone number.
Process B now reads the same record from the same file so it has its own copy.
Process A changes the account balance in its copy of the customer record and writes the record back to the file.
Process B—which still has the original stale value for the account balance in its copy of the customer record—updates the customer's phone number and writes the customer record back to the file.
Process B has now written its stale account balance value to the file, causing the changes made by process A to be lost.
File locking prevents this problem by enforcing the serialization of update processes to any given file. Most operating systems support the concept of record locking which means that individual records within any given file may be locked, so increasing the number of concurrent update processes.

One use of file locking is in database maintenance where it can serialize access to the entire physical file underlying a database. While this prevents any other process from accessing the file it can actually be more efficient than individually locking a large number of regions in the file by removing the overhead of achieving and releasing each lock.

Poor use of file locks, like any computer lock, can result in poor performance or deadlock.

Contents [hide]
1 In Microsoft Windows
2 In UNIX
2.1 Problems
2.2 Linux
3 Lock files
4 Unlocker software
5 References


In Microsoft Windows
Shared file access in Windows is managed by three distinct mechanisms:

Using share access controls that allow applications to specify whole-file access sharing for read, write or delete;
Using byte range locks to arbitrate read and write access to regions within a single file; and
By Windows file systems disallowing executing files from being opened for write or delete access.
The semantics of share access controls in Windows are inherited from the original MS-DOS system (where sharing was introduced in MS-DOS 3.3) Thus, an application must explicitly allow sharing - otherwise an application has exclusive read, write and delete access to the file (other types of access, such as those to retrieve the attributes of a file are allowed.)

For a file with shared access, applications may then use byte range locking to control access to specific regions of the file. Such byte range locks specify a region of the file (offset and length) and the type of lock (shared or exclusive). Note that the region of the file being locked is not required to have data within the file and applications sometimes exploit this ability to implement their functionality.

For applications that use the file read/write APIs in Windows, byte range locks are enforced (also referred to as mandatory locks) by the file systems that execute within Windows. For applications that use the file mapping APIs in Windows, byte range locks are not enforced (also referred to as advisory locks.) Byte range locking may also have other side-effects on the Windows system. For example, the Windows file sharing mechanism will typically disable client side caching of a file for all clients when byte range locks are used on any client to control access of the file. The client will observe slower access to the file because all read and write operations must be sent to the file server where the file is stored.

Improper error handling in an application program can lead to a situation where a file is locked (either using share access or with byte range file locking) and cannot be accessed by other applications. In this case the user may be able to restore access to the file by terminating the malfunctioning program manually. This is typically done through the Task Manager utility.

File sharing is determined by the sharing mode parameter in the CreateFile function used to open files. Files can be opened to allow sharing the file for read, write or delete access. Subsequent attempts to open the file must be compatible with all previously granted sharing access to the file. When the file is closed, the sharing access restrictions are adjusted to remove the restrictions imposed by that specific file open.

Byte range locking type is determined by the dwFlags parameter in the LockFileEx function used to lock a region of a file. The Windows API function LockFile can also be used and acquires an exclusive lock on the region of the file.

Any file that is executing on the computer system as a program (e.g., an EXE, COM, DLL, CPL or other binary program file format) is normally prevented by the file system from being opened for write or delete access, reporting a sharing violation, despite the fact that the progra
   
  作者: 202.106.46.*  2009-11-4 15:43  
 
3 附:File locking
 
m is not opened by any application. However, some access is still allowed. For example, a running application file can be renamed or copied (read) even when executing.

Files are accessed by applications in Windows by using file handles. These file handles can be explored with the Process Explorer utility. This utility can also be used to force-close handles without needing to terminate the application holding them.

Microsoft Windows XP and Server 2003 editions have introduced volume snapshot capability to NTFS, allowing open files to be accessed by backup software despite any exclusive locks. However, unless software is rewritten to specifically support this feature, the snapshot will be crash consistent only, while properly supported applications can assist the operating system in creating "transactionally consistent" snapshots. Other windows commercial software for accessing locked files include File Access Manager, and Open File Manager. It operates by installing its own drivers to access the files in kernel mode.

In UNIX
Open files and programs are not automatically locked in UNIX. There are different kinds of file locking mechanisms available in different flavours of UNIX and many operating systems support more than one kind for compatibility. The two most common mechanisms are fcntl(2) and flock(2). Although some types of locks can be configured to be mandatory, file locks under UNIX are by default advisory. This means that cooperating processes may use locks to coordinate access to a file between themselves, but programs are also free to ignore locks and access the file in any way they choose to.

Two kinds of locks are offered: shared locks and exclusive locks. In the case of fcntl, different kinds of locks may be applied to different sections (byte ranges) of a file, or else to the whole file. Shared locks can be acquired by an unlimited number of processes at the same time, but an exclusive lock can only be acquired by one process, and cannot coexist with a shared lock. To acquire a shared lock, a process must wait until there are no processes holding any exclusive locks. To acquire an exclusive lock, a process must wait until there are no processes holding either kind of lock.

Shared locks are sometimes called "read locks" and exclusive locks are sometimes called "write locks". However, because locks on UNIX are advisory, this isn't enforced. Thus it is possible for a database to have a concept of "shared writes" vs. "exclusive writes"; for example, changing a field in place may be permitted under shared access, whereas garbage-collecting and rewriting the database may require exclusive access.

File locks are based on inode instead of file name, since UNIX allows multiple names to refer to the same file. This combination of inode usage and non-mandatory locking leads to great flexibility in accessing files from multiple or many processes. On the other hand, the cooperative locking approach can lead to problems when a process writes to a file without obeying file locks set by other processes. For this reason, some UNIX and UNIX-like operating systems support mandatory locking as well.

Problems
Both flock and fcntl have quirks which occasionally puzzle programmers from other operating systems.

Mandatory locks have no effect on the unlink function. As a result, certain programs may, effectively, circumvent mandatory locking. The authors of Advanced Programming in the UNIX Environment (Second Edition) observed that the ed editor did so (page 456).

Whether flock locks work on network filesystems, such as NFS, is implementation-dependent. On BSD systems flock calls are successful no-ops. On Linux prior to 2.6.12 flock calls on NFS files would only act locally. Kernel 2.6.12 and above implement flock calls on NFS files using POSIX byte range locks. These locks will be visible to other NFS clients that implement fcntl()/POSIX locks.[1]

Lock upgrades and downgrades release the old lock before applying the new lock. If an application downgrades an exclusive lock to a shared lock while another application is blocked waiting for an exclusive lock, the latter application will get the exclusive lock and the first application will be locked out.

All fcntl locks associated with a file for a given process are removed when any file descriptor for that file is closed by that process, even if a lock was never requested for that file descriptor. Also, fcntl locks are not inherited by a child process. The fcntl close semantics are particularly troublesome for applications which call subroutine libraries that may access files.

Linux
Linux 2.4 and later added notification of external changes to files with dnotify mechanism (through the F_NOTIFY parameter in fcntl). This mechanism is replaced by the superior inotify, which was introduced in Linux 2.6.13. Linux also supports mandatory locking through the special "mount(8) -o mand" parameter for filesystem mounting, but this is rarely used.

Lock files
A system similar to the use of file locking is often used by shell scripts and other programs
   
  作者: 202.106.46.*  2009-11-4 15:43  
 
4 附:File locking
 
: creation of lock files, which are files whose contents are irrelevant (although often one will find the Process identifier of the holder of the lock in the file) and whose only purpose is to signal by their presence that some resource is locked. A lock file is often the best approach if the resource to be controlled is not a regular file at all, so using methods for locking files does not apply.

When using lock files, care must be taken to ensure that operations are atomic. In order to obtain a lock, the process must verify that the lock file does not exist and then create it, whilst preventing another process from creating it in the meantime. Various methods are used to achieve this, such as taking advantage of a system call for this purpose (such system calls are usually unavailable to shell scripts) or by creating the lock file under a temporary name and then attempting to move it into place.

Certain Mozilla products (such as Firefox, Thunderbird, Sunbird) use this type of file resource lock mechanism (using a temporary file named "parent.lock".)

Unlocker software
An unlocker is a utility used to determine what file a process is using if a user tried to delete it, and displays a list of processes as well as choices on what to do with the process (kill task, unlock, etc.) along with a list of file function options (delete, rename, move).
   
  作者: 202.106.46.*  2009-11-4 15:43  
 
5 附:Linux 2.6 中的文件锁
 

级别: 初级

黄 晓晨 (huangxc@cn.ibm.com), 软件工程师, IBM
冯 锐 (fengrui@cn.ibm.com), 软件工程师, IBM


2008 年 7 月 03 日

本文的目的是想帮助读者理清 Linux 2.6中文件锁的概念以及 Linux 2.6 都提供了何种数据结构以及关键的系统调用来实现文件锁,从而可以帮助读者更好地使用文件锁来解决多个进程读取同一个文件的互斥问题。本文主要描述了 Linux 中各类文件锁的概念,使用场景,内核中描述文件锁的数据结构以及与文件锁密切相关的系统调用等内容。
在多任务操作系统环境中,如果一个进程尝试对正在被其他进程读取的文件进行写操作,可能会导致正在进行读操作的进程读取到一些被破坏或者不完整的数据;如 果两个进程并发对同一个文件进行写操作,可能会导致该文件遭到破坏。因此,为了避免发生这种问题,必须要采用某种机制来解决多个进程并发访问同一个文件时 所面临的同步问题,由此而产生了文件加锁方面的技术。

早期的 UNIX 系统只支持对整个文件进行加锁,因此无法运行数据库之类的程序,因为此类程序需要实现记录级的加锁。在 System V Release 3 中,通过 fcntl 提供了记录级的加锁,此后发展成为 POSIX 标准的一部分。本文将基于 2.6.23 版本的内核来探讨 Linux 中文件锁的相关技术。

Linux 中的文件锁

Linux 支持的文件锁技术主要包括劝告锁(advisory lock)和强制锁(mandatory lock)这两种。此外,Linux 中还引入了两种强制锁的变种形式:共享模式强制锁(share-mode mandatory lock)和租借锁(lease)。

在 Linux 中,不论进程是在使用劝告锁还是强制锁,它都可以同时使用共享锁和排他锁(又称为读锁和写锁)。多个共享锁之间不会相互干扰,多个进程在同一时刻可以对同 一个文件加共享锁。但是,如果一个进程对该文件加了排他锁,那么其他进程则无权再对该文件加共享锁或者排他锁,直到该排他锁被释放。所以,对于同一个文件 来说,它可以同时拥有很多读者,但是在某一特定时刻,它只能拥有一个写者,它们之间的兼容关系如表 1 所示。


表 1. 锁间的兼容关系
是否满足请求
当前加上的锁 共享锁 排他锁
无 是 是
共享锁 是 否
排他锁 否 否


劝告锁

劝告锁是一种协同工作的锁。对于这一种锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调。也就是说,如果有进程不 遵守“游戏规则”,不检查目标文件是否已经由别的进程加了锁就往其中写入数据,那么内核是不会加以阻拦的。因此,劝告锁并不能阻止进程对文件的访问,而只 能依靠各个进程在访问文件之前检查该文件是否已经被其他进程加锁来实现并发控制。进程需要事先对锁的状态做一个约定,并根据锁的当前状态和相互关系来确定 其他进程是否能对文件执行指定的操作。从这点上来说,劝告锁的工作方式与使用信号量保护临界区的方式非常类似。

劝告锁可以对文件的任意一个部分进行加锁,也可以对整个文件进行加锁,甚至可以对文件将来增大的部分也进行加锁。由于进程可以选择对文件的某个部分进行加锁,所以一个进程可以获得关于某个文件不同部分的多个锁。

强制锁

与劝告锁不同,强制锁是一种内核强制采用的文件锁,它是从 System V Release 3 开始引入的。每当有系统调用 open()、read() 以及write() 发生的时候,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。也就是说,如果有进程不遵守游戏规则,硬要往加了锁的文件中写入内 容,内核就会加以阻拦:

如果一个文件已经被加上了读锁或者共享锁,那么其他进程再对这个文件进行写操作就会被内核阻止;

如果一个文件已经被加上了写锁或者排他锁,那么其他进程再对这个文件进行读取或者写操作就会被内核阻止。

如果其他进程试图访问一个已经加有强制锁的文件,进程行为取决于所执行的操作模式和文件锁的类型,归纳如表 2 所示:


表 2. 进行对已加强制锁的文件进行操作时的行为
当前锁类型 阻塞读 阻塞写 非阻塞读 非阻塞写
读锁 正常读取数据 阻塞 正常读取数据 EAGAIN
写锁 阻塞 阻塞 EAGAIN EAGAIN


需要注意的是,如果要访问的文件的锁类型与要执行的操作存在冲突,那么采用阻塞读/写操作的进程会被阻塞,而采用非阻塞读/写操作的进程则不会阻塞,而是立即返回 EAGAIN。

另外,unlink() 系统调用并不会受到强制锁的影响,原因在于一个文件可能存在多个硬链接,此时删除文件时并不会修改文件本身的内容,而是只会改变其父目录中 dentry 的内容。

然而,在有些应用中并不适合使用强制锁,所以索引节点结构中的 i_flags 字段中定义了一个标志位MS_MANDLOCK用于有选择地允许或者不允许对一个文件使用强制锁。在 super_block 结构中,也可以将 s_flags 这个标志为设置为1或者0,用以表示整个设备上的文件是否允许使用强制锁。

要想对一个文件采用强制锁,必须按照以下步骤执行:

使用 -o mand 选项来挂载文件系统。这样在执行 mount() 系统调用时,会传入 MS_MANDLOCK 标记,从而将 super_block 结构中的 s_flags 设置为 1,用来表示在这个文件系统上可以采用强制锁。例如:

# mount -o mand /dev/sdb7 /mnt
# mount | grep mnt
/dev/sdb7 on /mnt type ext3 (rw,mand)




1.修改要加强制锁的文件的权限:设置 SGID 位,并清除组可执行位。这种组合通常来说是毫无意义的,系统用来表示该文件被加了强制锁。例如:

# touch /mnt/testfile
# ls -l /mnt/testfile
-rw-r--r-- 1 root root 0 Jun 22 14:43 /mnt/testfile
# chmod g+s /mnt/testfile
# chmod g-x /mnt/testfile
# ls -l /mnt/testfile
-rw-r-Sr-- 1 root root 0 Jun 22 14:43 /mnt/testfile




2.使用 fcntl() 系统调用对该文件进行加锁或解锁操作。

共享模式锁

Linux 中还引入了两种特殊的文件锁:共享模式强制锁和租借锁。这两种文件锁可以被看成是强制锁的两种变种形式。共享模式强制锁可以用于某些私有网络文件系统,如 果某个文件被加上了共享模式强制锁,那么其他进程打开该文件的时候不能与该文件的共享模式强制锁所设置的访问模式相冲突。但是由于可移植性不好,因此并不 建议使用这种锁。

租借锁

采用强制锁之后,如果一个进程对某个文件拥有写锁,只要它不释放这个锁,就会导致访问该文件的其他进程全部被阻塞或不断失败重试;即使该进程只
   
  作者: 202.106.46.*  2009-11-4 16:12  
 
6 附:Linux 2.6 中的文件锁
 
拥有读锁,也会造成后续更新该文件的进程的阻塞。为了解决这个问题,Linux 中采用了一种新型的租借锁。

当进程尝试打开一个被租借锁保护的文件时,该进程会被阻塞,同时,在一定时间内拥有该文件租借锁的进程会收到一个信号。收到信号之后,拥有该文件租借锁的 进程会首先更新文件,从而保证了文件内容的一致性,接着,该进程释放这个租借锁。如果拥有租借锁的进程在一定的时间间隔内没有完成工作,内核就会自动删除 这个租借锁或者将该锁进行降级,从而允许被阻塞的进程继续工作。

系统默认的这段间隔时间是 45 秒钟,定义如下:

137 int lease_break_time = 45;




这个参数可以通过修改 /proc/sys/fs/lease-break-time 进行调节(当然,/proc/sys/fs/leases-enable 必须为 1 才行)。






回页首




Linux 内核中关于文件锁的实现

在 Linux 内核中,所有类型的文件锁都是由数据结构 file_lock 来描述的,file_lock 结构是在 文件中定义的,内容如下所示:


清单 1. file_lock 结构
811 struct file_lock {
812 struct file_lock *fl_next; /* singly linked list for this inode */
813 struct list_head fl_link; /* doubly linked list of all locks */
814 struct list_head fl_block; /* circular list of blocked processes */
815 fl_owner_t fl_owner;
816 unsigned int fl_pid;
817 wait_queue_head_t fl_wait;
818 struct file *fl_file;
819 unsigned char fl_flags;
820 unsigned char fl_type;
821 loff_t fl_start;
822 loff_t fl_end;
823
824 struct fasync_struct * fl_fasync; /* for lease break notifications */
825 unsigned long fl_break_time; /* for nonblocking lease breaks */
826
827 struct file_lock_operations *fl_ops; /* Callbacks for filesystems */
828 struct lock_manager_operations *fl_lmops; /* Callbacks for lockmanagers */
829 union {
830 struct nfs_lock_info nfs_fl;
831 struct nfs4_lock_info nfs4_fl;
832 struct {
833 struct list_head link; /* link in AFS vnode's pending_locks list */
834 int state; /* state of grant or error if -ve */
835 } afs;
836 } fl_u;
837 };




表 3 简单描述了 file_lock 结构中的各个字段所表示的含义。


表 3. file_lock 数据结构的字段
类型 字段 字段描述
struct file_lock* fl_next 与索引节点相关的锁列表中下一个元素
struct list_head fl_link 指向活跃列表或者被阻塞列表
struct list_head fl_block 指向锁等待列表
struct files_struct * fl_owner 锁拥有者的 files_struct
unsigned int fl_pid 进程拥有者的 pid
wait_queue_head_t fl_wait 被阻塞进程的等待队列
struct file * fl_file 指向文件对象
unsigned char fl_flags 锁标识
unsigned char fl_type 锁类型
loff_t fl_start 被锁区域的开始位移
loff_t fl_end 被锁区域的结束位移
struct fasync_struct * fl_fasync 用于租借暂停通知
unsigned long fl_break_time 租借的剩余时间
struct file_lock_operations * fl_ops 指向文件锁操作
struct lock_manager_operations * fl_mops 指向锁管理操作
union fl_u 文件系统特定信息


一个 file_lock 结构就是一把“锁”,结构中的 fl_file 就指向目标文件的 file 结构,而 fl_start 和 fl_end 则确定了该文件要加锁的一个区域。当进程发出系统调用来请求对某个文件加排他锁时,如果这个文件上已经加上了共享锁,那么排他锁请求不能被立即满足,这个 进程必须先要被阻塞。这样,这个进程就被放进了等待队列,file_lock 结构中的 fl_wait 字段就指向这个等待队列。指向磁盘上相同文件的所有 file_lock 结构会被链接成一个单链表 file_lock_list,索引节点结构中的 i_flock 字段会指向该单链表结构的首元素,fl_next 用于指向该链表中的下一个元素;当前系统中所有被请求,但是未被允许的锁被串成一个链表:blocked_list。fl_link 字段指向这两个列表其中一个。对于被阻塞列表(blocked_list)上的每一个锁结构来说,fl_next 字段指向与该锁产生冲突的当前正在使用的锁。所有在等待同一个锁的那些锁会被链接起来,这就需要用到字段 fl_block,新来的等待者会被加入到等待列表的尾部。 此外,fl_type 表示锁的性质,如读、写。fl_flags 是一些标志位,在 linux 2.6中,这些标志位的定义如下所示:


清单 2. 标志位的定义
773 #define FL_POSIX 1
774 #define FL_FLOCK 2
775 #define FL_ACCESS 8 /* not trying to lock, just looking */
776 #define FL_EXISTS 16 /* when unlocking, test for existence */
777 #define FL_LEASE 32 /* lease held on this file */
778 #define FL_CLOSE 64 /* unlock on close */
779 #define FL_SLEEP 128 /* A blocking lock */




FL_POSIX 锁是通过系统调用 fcntl() 创建的;而 FL_FLOCK 锁是通过系统调用 flock()创建的(详细内容请参见后文中的介绍)。FL_FLOCK 锁永远都和一个文件对象相关联,打开这个文件的进程拥有该 FL_FLOCK 锁。当一个锁被请求或者允许的时候,内核就会把这个进程在同一个文件上的锁都替换掉。FL_POSIX 锁则一直与一个进程以及一个索引节点相关联。当进程死亡或者文件描述符被关闭的时候,这个锁会被自动释放。

对于强制锁来说,在 Linux 中,内核提供了 inline 函数 locks_verify_locked() 用于检测目标文件或者目标文件所在的设备是否允许使用强制锁,并且检查该设备是否已经加上了锁,相关函数如下所示:


清单 3. 与强制锁相关的函数
166 #define __IS_FLG(inode,flg) ((inode)->i_sb->s_flags & (flg))
173 #define IS_MANDLOCK(inode) __IS_FLG(inode, MS_MANDLOC
   
  作者: 202.106.46.*  2009-11-4 16:12  
 
7 附:Linux 2.6 中的文件锁
 
K)

1047 /**
1048 * locks_mandatory_locked - Check for an active lock
1049 * @inode: the file to check
1050 *
1051 * Searches the inode's list of locks to find any POSIX locks which conflict.
1052 * This function is called from locks_verify_locked() only.
1053 */
1054 int locks_mandatory_locked(struct inode *inode)
1055 {
1056 fl_owner_t owner = current->files;
1057 struct file_lock *fl;
1058
1059 /*
1060 * Search the lock list for this inode for any POSIX locks.
1061 */
1062 lock_kernel();
1063 for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) {
1064 if (!IS_POSIX(fl))
1065 continue;
1066 if (fl->fl_owner != owner)
1067 break;
1068 }
1069 unlock_kernel();
1070 return fl ? -EAGAIN : 0;
1071 }

1368 /*
1369 * Candidates for mandatory locking have the setgid bit set
1370 * but no group execute bit - an otherwise meaningless combination.
1371 */
1372 #define MANDATORY_LOCK(inode) \
1373 (IS_MANDLOCK(inode) && ((inode)->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID)
1374
1375 static inline int locks_verify_locked(struct inode *inode)
1376 {
1377 if (MANDATORY_LOCK(inode))
1378 return locks_mandatory_locked(inode);
1379 return 0;
1380 }




这里,函数 locks_verify_locked() 利用宏 MANDATORY_LOCK 来检测目标文件是否允许加锁,条件包括:该文件所在的设备的 super_block 结构中的 s_flags 必须被置为 1,该文件的 SGID 被置为 1 而且同组可执行位被清 0。如果允许,则调用函数locks_mandatory_locked(),该函数从索引节点的锁列表中查找是否存在有与其相冲突的锁,即是否已经加上 了锁。






回页首




Linux 中关于文件锁的系统调用

这里介绍在 Linux 中与文件锁关系密切的两个系统调用:flock() 和 fcntl()。劝告锁既可以通过系统调用 flock() 来实现,也可以通过系统调用 fcntl() 来实现。flock() 系统调用是从 BSD 中衍生出来的,在传统的类 UNIX 操作系统中,系统调用flock() 只适用于劝告锁。但是,Linux 2.6内核利用系统调用 flock() 实现了我们前面提到的特殊的强制锁:共享模式强制锁。另外,flock() 只能实现对整个文件进行加锁,而不能实现记录级的加锁。系统调用fcntl() 符合 POSIX 标准的文件锁实现,它也是非常强大的文件锁,fcntl() 可以实现对纪录进行加锁。

flock()

flock() 的函数原型如下所示:

int flock(int fd, int operation);




其中,参数 fd 表示文件描述符;参数 operation 指定要进行的锁操作,该参数的取值有如下几种:LOCK_SH, LOCK_EX, LOCK_UN 和 LOCK_MANDphost2008-07-03T00:00:00

man page 里面没有提到,其各自的意思如下所示:

LOCK_SH:表示要创建一个共享锁,在任意时间内,一个文件的共享锁可以被多个进程拥有
LOCK_EX:表示创建一个排他锁,在任意时间内,一个文件的排他锁只能被一个进程拥有
LOCK_UN:表示删除该进程创建的锁
LOCK_MAND:它主要是用于共享模式强制锁,它可以与 LOCK_READ 或者 LOCK_WRITE 联合起来使用,从而表示是否允许并发的读操作或者并发的写操作(尽管在 flock() 的手册页中没有介绍 LOCK_MAND,但是阅读内核源代码就会发现,这在内核中已经实现了)
通常情况下,如果加锁请求不能被立即满足,那么系统调用 flock() 会阻塞当前进程。比如,进程想要请求一个排他锁,但此时,已经由其他进程获取了这个锁,那么该进程将会被阻塞。如果想要在没有获得这个排他锁的情况下不阻 塞该进程,可以将 LOCK_NB 和 LOCK_SH 或者 LOCK_EX 联合使用,那么系统就不会阻塞该进程。flock() 所加的锁会对整个文件起作用。

fcntl()

fcntl() 函数的功能很多,可以改变已打开的文件的性质,本文中只是介绍其与获取/设置文件锁有关的功能。fcntl() 的函数原型如下所示:

int fcntl (int fd, int cmd, struct flock *lock);




其中,参数 fd 表示文件描述符;参数 cmd 指定要进行的锁操作,由于 fcntl() 函数功能比较多,这里先介绍与文件锁相关的三个取值 F_GETLK、F_SETLK 以及 F_SETLKW。这三个值均与 flock 结构有关。flock 结构如下所示:


清单 4. flock 结构


struct flock {
...
short l_type; /* Type of lock: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock
(F_GETLK only) */
...
};




在 flock 结构中,l_type 用来指明创建的是共享锁还是排他锁,其取值有三种:F_RDLCK(共享锁)、F_WRLCK(排他锁)和F_UNLCK(删除之前建立的 锁);l_pid 指明了该锁的拥有者;l_whence、l_start 和l_end 这些字段指明了进程需要对文件的哪个区域进行加锁,这个区域是一个连续的字节集合。因此,进程可以对同一个文件的不同部分加不同的锁。l_whence 必须是 SEEK_SET、SEEK_CUR 或 SEEK_END 这几个值中的一个,它们分别对应着文件头、当前位置和文件尾。l_whence 定义了相对于 l_start 的偏移量,l_start 是从文件开始计算的。

可以执行的操作包括:

F_GETLK:进程可以通过它来获取通过 fd 打开的那个文件的加锁信息。执行该操作时,lock 指向的结构中就保存了希望对文件加的锁(或者说要查询的锁)。如果确实存在这样一把锁,它阻止 lock 指向的 flock 结构所给出的锁描述符,则把现存的锁的信息写到 lock 指向的 flock 结构中,并将该锁拥有者的 PID 写入 l_pid 字段中
   
  作者: 202.106.46.*  2009-11-4 16:12  
 
8 附:Linux 2.6 中的文件锁
 
,然后返回;否则,就将 lock 指向的 flock 结构中的 l_type 设置为 F_UNLCK,并保持 flock 结构中其他信息不变返回,而不会对该文件真正加锁。
F_SETLK:进程用它来对文件的某个区域进行加锁(l_type的值为 F_RDLCK 或 F_WRLCK)或者删除锁(l_type 的值为F_UNLCK),如果有其他锁阻止该锁被建立,那么 fcntl() 就出错返回
F_SETLKW:与 F_SETLK 类似,唯一不同的是,如果有其他锁阻止该锁被建立,则调用进程进入睡眠状态,等待该锁释放。一旦这个调用开始了等待,就只有在能够进行加锁或者收到信号时才会返回
需要注意的是,F_GETLK 用于测试是否可以加锁,在 F_GETLK 测试可以加锁之后,F_SETLK 和 F_SETLKW 就会企图建立一把锁,但是这两者之间并不是一个原子操作,也就是说,在 F_SETLK 或者 F_SETLKW 还没有成功加锁之前,另外一个进程就有可能已经插进来加上了一把锁。而且,F_SETLKW 有可能导致程序长时间睡眠。还有,程序对某个文件拥有的各种锁会在相应的文件描述符被关闭时自动清除,程序运行结束后,其所加的各种锁也会自动清除。

fcntl() 既可以用于劝告锁,也可以用于强制锁,在默认情况下,它用于劝告锁。如果它用于强制锁,当进程对某个文件进行了读或写这样的系统调用时,系统则会检查该文 件的锁的 O_NONBLOCK 标识,该标识是文件状态标识的一种,如果设置文件状态标识的时候设置了 O_NONBLOCK,则该进程会出错返回;否则,该进程被阻塞。cmd 参数的值 F_SETFL 可以用于设置文件状态标识。

此外,系统调用 fcntl() 还可以用于租借锁,此时采用的函数原型如下:

int fcntl(int fd, int cmd, long arg);




与租借锁相关的 cmd 参数的取值有两种:F_SETLEASE 和 F_GETLEASE。其含义如下所示:

F_SETLEASE:根据下面所描述的 arg 参数指定的值来建立或者删除租约:
F_RDLCK:设置读租约。当文件被另一个进程以写的方式打开时,拥有该租约的当前进程会收到通知
F_WRLCK:设置写租约。当文件被另一个进程以读或者写的方式打开时,拥有该租约的当前进程会收到通知
F_UNLCK:删除以前建立的租约
F_GETLEASE:表明调用进程拥有文件上哪种类型的锁,这需要通过返回值来确定,返回值有三种:F_RDLCK、F_WRLCK和F_UNLCK,分别表明调用进程对文件拥有读租借、写租借或者根本没有租借
某个进程可能会对文件执行其他一些系统调用(比如 OPEN() 或者 TRUNCATE()),如果这些系统调用与该文件上由 F_SETLEASE 所设置的租借锁相冲突,内核就会阻塞这个系统调用;同时,内核会给拥有这个租借锁的进程发信号,告知此事。拥有此租借锁的进程会对该信号进行反馈,它可能 会删除这个租借锁,也可能会减短这个租借锁的租约,从而可以使得该文件可以被其他进程所访问。如果拥有租借锁的进程不能在给定时间内完成上述操作,那么系 统会强制帮它完成。通过 F_SETLEASE 命令将 arg 参数指定为 F_UNLCK 就可以删除这个租借锁。不管对该租借锁减短租约或者干脆删除的操作是进程自愿的还是内核强迫的,只要被阻塞的系统调用还没有被发出该调用的进程解除阻塞, 那么系统就会允许这个系统调用执行。即使被阻塞的系统调用因为某些原因被解除阻塞,但是上面对租借锁减短租约或者删除这个过程还是会执行的。

需要注意的是,租借锁也只能对整个文件生效,而无法实现记录级的加锁。






回页首




文件锁的使用样例

为了使读者更深入理解本文中介绍的内容,下面我们给出了一个例子来详细介绍文件锁的具体用法。这个例子可以用来检测所使用的文件是否支持强制锁,其源代码如下所示:


清单 5. 锁的使用方法具体示例

# cat -n mandlock.c
1 #include <errno.h>
2 #include <stdio.h>
3 #include <fcntl.h>
4 #include <sys/wait.h>
5 #include <sys/stat.h>
6
7 int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
8 {
9 struct flock lock;
10
11 lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLCK */
12 lock.l_start = offset; /* byte offset, relative to l_whence */
13 lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
14 lock.l_len = len; /* #bytes (0 means to EOF) */
15
16 return( fcntl(fd, cmd, &lock) );
17 }
18
19 #define read_lock(fd, offset, whence, len) \
20 lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len)
21 #define write_lock(fd, offset, whence, len) \
22 lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len)
23
24 #define err_sys(x) { perror(x); exit(1); }
25
26 int main(int argc, char *argv[])
27 {
28 int fd, val;
29 pid_t pid;
30 char buf[5];
31 struct stat statbuf;
32 if (argc != 2) {
33 fprintf(stderr, "usage: %s filename\n", argv[0]);
34 exit(1);
35 }
36 if ((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC )) < 0)
37 err_sys("open error");
38 if (write(fd, "hello world", 11) != 11)
39 err_sys("write error");
40
41 /* turn on set-group-ID and turn off group-execute */
42 if (fstat(fd, &statbuf) < 0)
43 err_sys("fstat error");
44 if (fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
45 err_sys("fchmod error");
46
47 sleep(2);
48
49 if ((pid = fork()) < 0) {
50 err_sys("fork error");
51 } else if (pid > 0) { /* parent */
52 /* write lock entire file */
53 if (write_lock(fd, 0, SEEK_SET, 0) < 0)
54 err_sys("write_lock error");
55
56 sleep(20); /* wait for chil
   
  作者: 202.106.46.*  2009-11-4 16:12  
 
9 附:Linux 2.6 中的文件锁
 
d to set lock and read data */
57
58 if (waitpid(pid, NULL, 0) < 0)
59 err_sys("waitpid error");
60
61 } else { /* child */
62 sleep(10); /* wait for parent to set lock */
63
64 if ( (val = fcntl(fd, F_GETFL, 0)) < 0)
65 err_sys("fcntl F_GETFL error");
66
67 val |= O_NONBLOCK; /* turn on O_NONBLOCK flag */
68
69 if (fcntl(fd, F_SETFL, val) < 0)
70 err_sys("fcntl F_SETFL error");
71
72 /* first let's see what error we get if region is locked */
73 if (read_lock(fd, 0, SEEK_SET, 0) != -1) /* no wait */
74 err_sys("child: read_lock succeeded");
75
76 printf("read_lock of already-locked region returns %d: %s\n", errno, strerror(errno));
77
78 /* now try to read the mandatory locked file */
79 if (lseek(fd, 0, SEEK_SET) == -1)
80 err_sys("lseek error");
81 if (read(fd, buf, 5) < 0)
82 printf("read failed (mandatory locking works)\n");
83 else
84 printf("read OK (no mandatory locking), buf = %5.5s\n", buf);
85 }
86 exit(0);
87 }
88




样例代码中所采用的技术在前文中大都已经介绍过了,其基本思想是在首先在父进程中对文件加上写锁;然后在子进程中将文件描述符设置为非阻塞模式(第 69 行),然后对文件加读锁,并尝试读取该文件中的内容。如果系统支持强制锁,则子进程中的 read() 系统调用(代码中的第 81 行)会立即返回 EAGAIN;否则,等父进程完成写文件操作之后,子进程中的 read() 系统调用就会返回父进程刚刚写入的前 5 个字节的数据。代码中的几个 sleep() 是为了协调父进程与子进程之间的同步而使用的。

该程序在测试系统的执行结果如下所示:

# mount | grep mnt
/dev/sdb7 on /mnt type ext3 (rw,mand)
/dev/sdb6 on /tmp/mnt type ext3 (rw)
# ./mandlock /mnt/testfile
read_lock of already-locked region returns 11: Resource temporarily unavailable
read failed (mandatory locking works)
# ./mandlock /tmp/mnt/testfile
read_lock of already-locked region returns 11: Resource temporarily unavailable
read OK (no mandatory locking), buf = hello




我们可以看到,/dev/sdb7 使用 –o mand 选项挂载到了 /mnt 目录中,而 /dev/sdb6 则么有使用这个选项挂载到了 /tmp/mnt 目录中。由于在程序中我们完成了对测试文件 SGID 和同组可执行位的设置(第 44 行),因此 /mnt/testfile 可以支持强制锁,而 /tmp/mnt/testfile 则不能。这也正是为什么前者的 read() 系统调用会失败返回而后者则可以成功读取到 hello 的原因。






回页首




总结

Linux 的文件锁在以共享索引节点共享文件的情况下设计的,文件锁的实现可以使得不同用户同时读写同一文件的并发问题得以解决。本文描述了 Linux 中各类文件锁的概念,使用场景,内核中描述文件锁的数据结构以及与文件锁密切相关的系统调用等内容,至于与文件锁相关的索引节点数据结构,以及在对文件进 行加锁时遇到的死锁问题等其他知识,这里没有做详尽介绍,感兴趣的读者可以自行参考内核源代码。

本文的目的是想帮助读者理清 Linux 2.6中文件锁的概念以及 Linux 2.6 都提供了何种数据结构以及关键的系统调用来实现文件锁,从而可以帮助读者更好地使用文件锁来解决多个进程读取同一个文件的互斥问题。
   
  作者: 202.106.46.*  2009-11-4 16:12  
 
10 UNIX Programming FAQ对flock和fcntl的解释
 
flock()是从BSD中衍生出来的,但目前在大多数UNIX系统上都能找到,在单个主机上flock()简单有效,但它不能在NFS上工作。Perl中也有一个有点让人迷惑的 flock()函数,但却是在perl内部实现的。

fcntl()是唯一的符合POSIX标准的文件锁实现,所以也是唯一可移植的。它也同时是最强大的文件锁——也是最难用的。在NFS文件系统 上,fcntl()请求会被递交给叫rpc.lockd的守护进程,然后由它负责和主机端的lockd对话,和flock() 不同,fcntl()可以实现记录层上的封锁。
   
  作者: 202.106.46.*  2009-11-5 16:36  
 
11 NFS 启动的 RPC daemons之“rpc.lockd”
 


我们现在知道 NFS 伺服器在启动的时候就得要向 RPC 注册,所以 NFS 伺服器也被称为 RPC server 之一。 那麼 NFS 伺服器主要的任务是进行档案系统的分享,档案系统的分享则与权限有关。 所以 NFS 伺服器启动时至少需要两个 daemons ,一个管理 client 端是否能够登入的问题, 一个管理 client 端能够取得的权限。如果你还想要管理 quota 的话,那麼 NFS 还得要再载入其他的 RPC 程式就是了。我们以较单纯的 NFS 主机来说:

rpc.nfsd:
这个 daemon 主要的功能就是在管理 Client 是否能够登入主机的权限啦,其中还包含这个登入者的 ID 的判别喔!
rpc.mountd
这个 daemon 主要的功能,则是在管理 NFS 的档案系统哩!当 Client 端顺利的通过 rpc.nfsd 而登入主机之后,在他可以使用 NFS 伺服器提供的档案之前,还会经过档案使用权限 (就是那个 -rwxrwxrwx 与 owner, group 那几个权限啦) 的认证程序!他会去读 NFS 的设定档 /etc/exports 来比对 Client 的权限,当通过这一关之后 Client 就可以取得使用 NFS 档案的权限啦! (注:这个也是我们用来管理 NFS 分享之目录的使用权限与安全设定的地方哩!)
rpc.lockd (非必要)
这个玩意儿可以用在管理档案的锁定 (lock) 用途。为何档案需要『锁定』呢? 因为既然分享的 NFS 档案可以让用户端使用,那麼当多个用户端同时尝试写入某个档案时, 就可能对於该档案造成一些问题啦!这个 rpc.lockd 则可以用来克服这个问题。 但 rpc.lockd 必须要同时在用户端与伺服器端都开启才行喔!此外, rpc.lockd 也常与 rpc.statd 同时启用。
rpc.statd (非必要)
可以用来检查档案的一致性,与 rpc.lockd 有关!若发生因为用户端同时使用同一档案造成档案可能有所损毁时, rpc.statd 可以用来检测并尝试回复该档案。与 rpc.lockd 同样的,这个功能必须要在伺服器端与用户端都启动才会生效。



posted on 2012-02-15 13:26  Richard.FreeBSD  阅读(704)  评论(0编辑  收藏  举报

导航