一、记录锁 record locking
 功能:当一个进程正在读或修改文件的某个部分时,它可以阻止其它进程修改同一文件区。

字节范围锁 byte-range locking

二、历史
 flock函数,可以锁整个文件,不能锁文件中的一部分。
 fcntl函数,增加了记录锁的功能。
 lockf函数,在fcntl基础上构造了lockf函数,提供一个简化的接口。可以锁文件中任意字节数的区域

三、fcntl 记录锁
 函数原型:

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

/*
cmd = F_GETLK,测试能否建立一把锁
cmd = F_SETLK,设置锁
cmd = F_SETLKW, 阻塞设置一把锁 */
//POSIX只定义fock结构中必须有以下的数据成员,具体实现可以增加
struct flock {
short l_type; /* 锁的类型: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* 加锁的起始位置:SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* 加锁的起始偏移,相对于l_whence */
off_t l_len; /* 上锁的字节数*/
pid_t l_pid; /* 已经占用锁的PID(只对F_GETLK 命令有效) */
/*...*/
};

(1)所希望的锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)、F_UNLCK(解锁一个区域)。

(2)要加锁或解锁区域的起始字节偏移量由l_start和l_whence两者决定。

(3)注意:该区域可以在当前文件尾端开始或越过其尾端处开始,但是不能在文件起始位置之前开始。

(4)如若l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直至最大可能偏移量为止。

(5)为了锁住整个文件,我们设置l_start和l_whence,使锁的起点在文件起始处,并说明长度(l_len)为0。

上面提到了两种类型的锁:共享读锁(F_RDLCK)和独占写锁(F_WRLCK),基本规则是:

多个进程在一个给定的字节上可以有一把共享的读锁,但是在一个给定字节上只能有一个进程独用的一把写锁。进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能再该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁。

不同进程锁请求的读写锁规则:

-------------------                   加读锁         加写锁

无锁                                      允许             允许
  一个或多个读锁                    允许             拒绝
  一个写锁                              拒绝             拒绝

注意:上面这个规则适用于不同进程提出的锁请求,并不适用于单个进程提出的多个锁请求单个进程提出多个锁请求的时候,以最后一次锁作为标准,即新锁替换旧锁对于同一文件如果一直有不同的进程连续的对其添加读锁,则其它欲对其添加阻塞写锁的进程有可能延长等待时间(这样的情况,对于添加写锁的进程会出现饿死情况)

在读锁时,该描述符必须是读打开;加写锁时,该描述符必须是写打开。

F_GETLK: 判断由flockptr所描述的锁是否会被另外一把锁所排斥(阻塞)。如果存在一把锁,它阻止创建由flockptr所描述的锁,则把该现存锁的信息写到flockptr指向的结构中。如果不存在这种情况,则除了将l_type设置为F_UNLCK之外,flockptr所指向结构中的其他信息保持不变。

F_SETLK : 获取(l_type为F_RDLCK或F_WRLCK)或释放由flockptr指向flock结构所描述的锁,如果无法获取锁时,该函数会立即返回一个EACCESS或EAGAIN错误,而不会阻塞。

F_SETLKW:  F_SETLKW和F_SETLK的区别是,无法设置锁的时候,调用线程会阻塞到该锁能够授权位置。

这里需要注意的是,用F_GETLK测试能否建立一把锁,然后接着用F_SETLK或F_SETLKW企图建立一把锁,由于这两者不是一个原子操作,所以不能保证两次fcntl之间不会有另外一个进程插入并建立一把相关的锁,从而使一开始的测试情况无效。所以一般不希望上锁时阻塞,会直接通过调用F_SETLK,并对返回结果进行测试,以判断是否成功建立所要求的锁。

下面进行测试:

第一个程序:在同一进程中测试能否在加写锁后,继续加读写锁。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)
{
struct flock lock;
lock_init(&lock, type, whence, start, len); if (fcntl(fd, F_GETLK, &lock) == -1)
{
return -1;
} if(lock.l_type == F_UNLCK)
return 0;
return lock.l_pid;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
writew_lock(fd); printf(" F_WRLCK return %d\n",lock_test(fd, F_WRLCK, SEEK_SET, 0, 0));
printf(" F_RDLCK return %d\n", lock_test(fd, F_RDLCK, SEEK_SET, 0, 0)); unlock(fd); return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
F_WRLCK return 0
F_RDLCK return 0

结果表明:表明同一进程可以对已加锁的同一文件区间,仍然能获得加锁权限;

第二个程序是在在父进程中加写锁后,然后再子进程中测试能否继续加读写锁。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)
{
struct flock lock;
lock_init(&lock, type, whence, start, len); if (fcntl(fd, F_GETLK, &lock) == -1)
{
return -1;
} if(lock.l_type == F_UNLCK)
return 0;
return lock.l_pid;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
writew_lock(fd); if (fork() == 0)
{
printf("child F_WRLCK return %d\n",lock_test(fd, F_WRLCK, SEEK_SET, 0, 0));
printf("child F_RDLCK return %d\n",lock_test(fd, F_RDLCK, SEEK_SET, 0, 0));
printf("child pid = %d and ppid = %d\n",getpid(),getppid()); exit(0);
} sleep(3);
unlock(fd); return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
child F_WRLCK return 7483
child F_RDLCK return 7483
child pid = 7484 and ppid = 7483

结果表明:不同进程不能对已加写锁的同一文件区间,获得加锁权限;

还有就是:加锁时,该进程必须对该文件有相应的文件访问权限,即加读锁,该文件必须是读打开,加写锁时,该文件必须是写打开。


四、记录锁的粒度

这里要提到两个概念:记录上锁和文件上锁

记录上锁:对于UNIX系统而言,“记录”这一词是一种误用,因为UNIX系统内核根本没有使用文件记录这种概念,更适合的术语应该是字节范围锁,因为它锁住的只是文件的一个区域。用粒度来表示被锁住文件的字节数目。对于记录上锁,粒度最大是整个文件。

文件上锁:是记录上锁的一种特殊情况,即记录上锁的粒度是整个文件的大小。

之所以有文件上锁的概念是因为有些UNIX系统支持对整个文件上锁,但没有给文件内的字节范围上锁的能力。

五、记录锁的隐含继承与释放

关于记录锁的继承和释放有三条规则,如下:

(1)锁与进程和文件两方面有关,体现在:

  • 当一个进程终止时,它所建立的记录锁将全部释放;
  • 当关闭一个文件描述符时,则进程通过该文件描述符引用的该文件上的任何一把锁都将被释放。

对于第一个方面,可以建立如下测试代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} //process 1
int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd);
printf("process 1 get write lock...\n"); sleep(10); printf("process 1 exit...\n");
return 0;
}

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} //process 2
int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); writew_lock(fd);
printf("process 2 get write lock...\n");
unlock(fd); return 0;
}

先启动进程1,然后立即启动进程2,执行结果如下:

process 1 get write lock...
process 1 exit...
process 2 get write lock...

结果表明:当一个进程终止时,它所建立的记录锁将全部释放。

对于第二个方面,可以进行如下测试:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int readw_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); if (fork() == 0)
{
int fd_1 = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE); readw_lock(fd_1);
printf("child get read lock...\n"); sleep(3); close(fd_1);
printf("close the file descriptor...\n"); pause();
} sleep(1); writew_lock(fd);
printf("parent get write lock...\n");
unlock(fd); return 0;
}

程序的执行结果如下:

huangcheng@ubuntu:~$ ./a.out
child get read lock...
close the file descriptor...
parent get write lock...

运行结果说明:当关闭文件描述符时,与该文件描述符有关的锁都被释放,同样通过dup拷贝得到的文件描述符也会导致这种情况。

(2)由fork产生的子进程不继承父进程所设置的锁。即对于父进程建立的锁而言,子进程被视为另一个进程。记录锁本身就是用来同步不同进程对同一文件区进行操作,如果子进程继承了父进程的锁,那么父子进程就可以同时对同一文件区进行操作,这有违记录锁的规则,所以存在这么一条规则。

下面是测试代码(上面已经用过该代码进行测试):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} pid_t lock_test(int fd, short type, short whence, off_t start, off_t len)
{
struct flock lock;
lock_init(&lock, type, whence, start, len); if (fcntl(fd, F_GETLK, &lock) == -1)
{
return -1;
} if(lock.l_type == F_UNLCK)
return 0;
return lock.l_pid;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
writew_lock(fd); if (fork() == 0)
{
printf("child F_WRLCK return %d\n",lock_test(fd, F_WRLCK, SEEK_SET, 0, 0));
printf("child F_RDLCK return %d\n",lock_test(fd, F_RDLCK, SEEK_SET, 0, 0));
printf("child pid = %d and ppid = %d\n",getpid(),getppid()); exit(0);
} sleep(3);
unlock(fd); return 0;
}

我们知道在前面已经说过,同一个进程可以重复对同一个文件区间加锁,后加的锁将覆盖前面加的锁。那么再假设如果子进程继承了父进程的锁,那么子进程可以对该锁进行覆盖,那么在子进程内对该锁是否能获得权限的测试应该是可以,但测试结果为:

huangcheng@ubuntu:~$ ./a.out
child F_WRLCK return 7483
child F_RDLCK return 7483
child pid = 7484 and ppid = 7483

表明已经进程7483已经占用该锁,所以假设不成立,子进程不会继承父进程的锁;

(3)执行exec后,新程序可以继承原执行程序的锁。但是,如果一个文件描述符设置了close-on-exec标志,在执行exec时,会关闭该文件描述符,所以对应的锁也就被释放了,也就无所谓继承了。

六、记录锁的读和写的优先级

具体进行以下2个方面测试:

  1. 进程拥有读锁,然后优先处理后面的读锁,再处理写锁,导致写锁出现饿死;
  2. 进程拥有写入锁,那么等待的写入锁和等待的读出锁的优先级(FIFO);

测试1:父进程获得对文件的读锁,然后子进程1请求加写锁,随即进入睡眠,然后子进程2请求读锁,看进程2是否能够获得读锁。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int readw_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
readw_lock(fd); //child 1
if (fork() == 0)
{
printf("child 1 try to get write lock...\n");
writew_lock(fd);
printf("child 1 get write lock...\n"); unlock(fd);
printf("child 1 release write lock...\n"); exit(0);
} //child 2
if (fork() == 0)
{
sleep(3); printf("child 2 try to get read lock...\n");
readw_lock(fd);
printf("child 2 get read lock...\n"); unlock(fd);
printf("child 2 release read lock...\n");
exit(0);
} sleep(10);
unlock(fd); return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
child 1 try to get write lock...
child 2 try to get read lock...
child 2 get read lock...
child 2 release read lock...
child 1 get write lock...
child 1 release write lock...

运行结果说明:可知在有写入进程等待的情况下,对于读出进程的请求,系统会一直给予的。那么这也就可能导致写入进程饿死的局面。

测试2:父进程获得写入锁,然后子进程1和子进程2分别请求获得写入锁和读写锁,看两者的响应顺序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int readw_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
writew_lock(fd); //child 1
if (fork() == 0)
{
sleep(3); printf("child 1 try to get write lock...\n");
writew_lock(fd);
printf("child 1 get write lock...\n"); unlock(fd);
printf("child 1 release write lock...\n"); exit(0);
} //child 2
if (fork() == 0)
{
printf("child 2 try to get read lock...\n");
readw_lock(fd);
printf("child 2 get read lock...\n"); unlock(fd);
printf("child 2 release read lock...\n"); exit(0);
} sleep(10);
unlock(fd); return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
child 2 try to get read lock...
child 1 try to get write lock...
child 2 get read lock...
child 2 release read lock...
child 1 get write lock...
child 1 release write lock...

将上面代码该成child2 sleep 3s,child1不sleep:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> #define FILE_PATH "/home/huangcheng/data.txt"
#define FILE_MODE 0777 void lock_init(struct flock *lock, short type, short whence, off_t start, off_t len)
{
if (lock == NULL)
return; lock->l_type = type;
lock->l_whence = whence;
lock->l_start = start;
lock->l_len = len;
} int readw_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_RDLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int writew_lock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_WRLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int unlock(int fd)
{
if (fd < 0)
{
return -1;
} struct flock lock;
lock_init(&lock, F_UNLCK, SEEK_SET, 0, 0); if (fcntl(fd, F_SETLKW, &lock) != 0)
{
return -1;
} return 0;
} int main()
{
int fd = open(FILE_PATH, O_RDWR | O_CREAT, FILE_MODE);
writew_lock(fd); //child 1
if (fork() == 0)
{
printf("child 1 try to get write lock...\n");
writew_lock(fd);
printf("child 1 get write lock...\n"); unlock(fd);
printf("child 1 release write lock...\n"); exit(0);
} //child 2
if (fork() == 0)
{
sleep(3);
printf("child 2 try to get read lock...\n");
readw_lock(fd);
printf("child 2 get read lock...\n"); unlock(fd);
printf("child 2 release read lock...\n"); exit(0);
} sleep(10);
unlock(fd); return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
child 1 try to get write lock...
child 2 try to get read lock...
child 1 get write lock...
child 1 release write lock...
child 2 get read lock...
child 2 release read lock...

由上可知在ubuntu 10.04下,等待的写入锁进程和读出锁进程的优先级由FIFO的请求顺序进程响应。

七、死锁检测程序

//《APUE》程序14-2:加锁和解锁一个文件区域
//《APUE》程序14-4:死锁检测实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h> #define read_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len)) #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) sig_atomic_t sigflag; /* set nonzero by sig handler */
sigset_t newmask, oldmask, zeromask; //输出错误信息并退出
void error_quit(const char *str)
{
fprintf(stderr, "%s\n", str);
exit(1);
} static void sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
} void TELL_WAIT(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
error_quit("signal(SIGUSR1) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
error_quit("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2); /*
* Block SIGUSR1 and SIGUSR2, and save current signal mask.
*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
error_quit("SIG_BLOCK error");
} void TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
} void WAIT_PARENT(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for parent */
sigflag = 0; /*
* Reset signal mask to original value.
*/
int temp = sigprocmask(SIG_SETMASK, &oldmask, NULL);
if (temp < 0)
error_quit("SIG_SETMASK error");
} void TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /* tell child we're done */
} void WAIT_CHILD(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for child */
sigflag = 0; /*
* Reset signal mask to original value.
*/
int temp = sigprocmask(SIG_SETMASK, &oldmask, NULL);
if (temp < 0)
error_quit("SIG_SETMASK error");
} //加锁或解锁某个文件区域
int lock_reg(int fd, int cmd, int type, off_t offset,
int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return fcntl(fd, cmd, &lock);
} //锁住文件中的一个字节
void lockabyte(const char *name, int fd, off_t offset)
{
//在我的系统上(Ubuntu10.04),发生死锁时writew_lock并不会返回-1
if( writew_lock(fd, offset, SEEK_SET, 1) < 0 )
error_quit("writew_lock error");
printf("%s: got the lock, byte %ld\n", name, offset);
} int main(void)
{
int fd;
pid_t pid; fd = creat("templock", FILE_MODE);
if( fd < 0 )
error_quit("create error");
if( write(fd, "ab", 2) != 2 )
error_quit("write error"); TELL_WAIT();
pid = fork();
if( pid < 0 )
error_quit("fork error");
else if( pid == 0 )
{
lockabyte("child", fd, 0);
TELL_PARENT( getpid() );
WAIT_PARENT();
lockabyte("child", fd, 1);
}
else
{
lockabyte("parent", fd, 1);
TELL_CHILD(pid);
WAIT_CHILD();
lockabyte("parent", fd, 0);
}
return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
parent: got the lock, byte 1
child: got the lock, byte 0
^C
huangcheng@ubuntu:~$

注解:

1:在该程序中,子进程锁住字节0,父进程锁住字节1,然后,它们又都试图锁住对方已经加锁的字节,这样就造成了死锁。

2:《APUE》上说:检测到死锁时,内核必须选择一个进程出错返回。但在我的系统(ubuntu 10.04)中,父子进程都被卡住,只有当你强制中断时(Ctrl+C)时,程序才会结束。

UNIX环境高级编程——记录上锁(fcntl函数)以及死锁检测的更多相关文章

  1. Unix环境高级编程学习笔记——fcntl

    写这篇文正主要是为了介绍下fcntl,并将我自己在学习过程中的一些理解写下来,不一定那么官方,也有错误,希望指正,共同进步- fcntl: 一个修改一打开文件的性质的函数.基本的格式是 int fcn ...

  2. UNIX环境高级编程——sigqueue、sigsuspend函数

    一.sigqueue函数 功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用. int sigqueue(pid_t pid, int sig, ...

  3. <UNIX环境高级编程>文件共享及fork函数

    UNIX系统支持在不同进程间共享打开文件.内核使用3种数据结构表示打开文件,它们之间的关系决定了文件共享方面一个进程对另一个进程可能产生的影响. 内核维持了3个表,即进程表,文件表和v节点表.具体如下 ...

  4. 《UNIX环境高级编程》笔记--read函数,write函数,lseek函数

    1.read函数 调用read函数从文件去读数据,函数定义如下: #include <unistd.h> ssize_t read(int filedes, void* buff, siz ...

  5. 《UNIX环境高级编程》笔记--sigaction函数

    sigaction函数的功能是检查或修改指定信号相关联的处理动作,此函数取代UNIX早期版本使用的signal函数. #include<signal.h> int sigaction(in ...

  6. Unix环境高级编程——守护进程记录总结(从基础到实现)

    一.概念及其特征 守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行.守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程 ...

  7. UNIX环境高级编程——TCP/IP网络编程 常用网络信息检索函数

    UNIX环境高级编程——TCP/IP网络编程   常用网络信息检索函数 gethostname()   getppername()   getsockname()   gethostbyname() ...

  8. unix环境高级编程基础知识之第二篇(3)

    看了unix环境高级编程第三章,把代码也都自己敲了一遍,另主要讲解了一些IO函数,read/write/fseek/fcntl:这里主要是c函数,比较容易,看多了就熟悉了.对fcntl函数讲解比较到位 ...

  9. (三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. 手写JAVA虚拟机(三)——搜索class文件并读出内容

    查看手写JAVA虚拟机系列可以进我的博客园主页查看. 前面我们介绍了准备工作以及命令行的编写.既然我们的任务实现命令行中的java命令,同时我们知道java命令是将class文件(字节码)转换成机器码 ...

  2. spark学习笔记01

    spark学习笔记01 1.课程目标 1.熟悉spark相关概念 2.搭建一个spark集群 3.编写简单spark应用程序 2.spark概述 spark是什么 是基于内存的分布式计算引擎,计算速度 ...

  3. Bootstrap 遮罩层实现方式

    直接上代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <me ...

  4. 从头开始搭建一个VSCode+NetCore的项目

    看这个前,先要对VS开发C#有所了解 获取作案工具 NetCore SDK https://www.microsoft.com/net/learn/get-started/windows 安装 建立工 ...

  5. JavaScript 代码规范

    所有的 JavaScript 项目适用同一种规范. JavaScript 代码规范 代码规范通常包括以下几个方面: 变量和函数的命名规则 空格,缩进,注释的使用规则. 其他常用规范-- 规范的代码可以 ...

  6. AC的故事大结局山寨版(下)(最大流)

    福建工程学院第十二届ACM程序设计大赛真题 AC的故事大结局山寨版(下) TimeLimit:2000MS  MemoryLimit:128MB 64-bit integer IO format:%l ...

  7. Openstack: aborted: Block Device Mapping is Invalid

    Issue: When you create an instance, you may encounter following exception: aborted: Block Device Map ...

  8. 这是最好的时光 这是最坏的时光 v0.1.1.1

    这是最好的时光 这是最坏的时光 v0.1.1.1 1.2 学校的生活二三事之大学 话说上一回,扯了一下我青涩的少年往事,大家反响不一,有叫好的,有吐槽的,有字字码过的,也有一目十行的.我的心情也是随着 ...

  9. Mac小技巧:快速查看指定应用程序的所有窗口

    我们知道在Mac中快速在系统所有程序中切换得快捷键为: cmd + tab 不过有时我们需要快速查看某一个程序的所有窗口,那又该如何呢? 以下方法在MacOS 10.12中测试成功! Mac默认该功能 ...

  10. Swift中方法(method)所谓的lazy绑定简介

    我们知道在ruby之类的动态语言中对象方法可以先从类中预先抽取,然后再应用到某个具体对象上.这称为无绑定的method对象,也可以叫做lazy绑定. 下面举个例子: irb(main):004:0&g ...