一:
记录上锁用于又亲缘关系或者无亲缘关系的进程之间共享某个文件的读与写,被锁住的文件可以通过其描述副访问,这种锁在内核中维护,属主是由属主的进程ID标识的。(
用于不同进程见上锁)
1:需要给某个打印作业赋一个序列号的进程经历三个步骤:
(1)读序列号文件
(2)使用其中的序列
(3)给序列号加1并写回文件。
我们所需要的是:一个进程能够设置某个锁,宣称没有其他进程能够访问相应的文件,知道第一个进程完成访问为止。my_lock(),my_unlock分别用于刚开始时候给序列号文件上锁以及完成序列号+1更新后给文件解说。
这三步操作应该作为一个
原子操作。
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #define MAXLINE 40
- void my_lock(int fd)
- {
- return;
- }
- void my_unlock(int fd)
- {
- return;
- }
- int main(int argc,char *argv)
- {
- int fd;
- long i,seqno;
- pid_t pid;
- ssize_t n;
- char line[MAXLINE+1];
- pid=fork();
- fd=open(SEQFILE,O_RDWR,3);
- for(i=0;i<20;i++)
- {
- my_lock(fd);
- lseek(fd,0L,SEEK_SET);
- n=read(fd,line,MAXLINE);
- line[n]='\0';
- n=sscanf(line,"%ld\n",&seqno);
- printf("%s:pid=%ld,seq#=%ld\n",arg[0],(long)pid,seqno);
- seqno++;
- snprintf(line,sizeof(line),"%ld\n",seqno);
- lseek(fd,0L,SEEK_SET);
- write(fd,line,strlen(line));
- my_unlock(fd);
- }
- exit(0);
- }
此程序在运行过程中由于三步走不是一个原子操作 所以出现了第一个Pid执行完加到20,下一个仍然会从1加到20,而不是加21到40.所以
造成程序运行正确与否的是三个不走是否是原子执行。2:对比记录上锁和文件上锁:
首先,unix内核没有文件内的记录这一概念,任何关于记录的解释都是由读写文件的应用来进行的。其实“记录上锁”确切的是“字节范围上锁”。
其次,有个重要的概念“粒度”:用于标记能被锁住的对象的大小。对于POSIX记录上锁来说,粒度就是单个字节。粒度越小,允许同时使用的用户数就越多。
eg:有五个进程几乎同时访问一个给定的文件,其中三个是读出这,两个是写入者。访问该文件不同记录,而且这五个请求中的每个话费的时间几乎相同。是1s。
如果上锁是文件级别的,那么所有三个读出这可能同时访问他们个子的记录,但是那两个写入者必须等到所有读出者读完,然后其中一个写入这可以修改自己的记录,另一个写入这随后也这么做。总的时间大约3s
如果上锁是记录级别的,那么所有五个进程都能同时处理,因为他们各自访问不同的记录,总时间1s
3:POSIX fcntl记录上锁
记录上锁的POSIX接口是fcntl函数,用于记录上锁的cmd函数有三个值。这三个命令要求第三个参数arg只想某个flock结构。
- #include<fcntl.h>
- int fcntl(int fd,int cmd,..);
- struct flock
- {
- short l_type;
- short l_whence;
- off_t l_start;
- off_t l_len;
- pid_t pid;
- };
三个命令:
F_SETLK:获取或者释放由arg指向的flock结构所描述的锁。不等待,直接返回错误
F_SETLKW:无法将锁请求的锁授予调用进程,调用线程将等待阻塞诶到该锁可以授予为止。等待
F_GETLK:检查arg指向的锁确定是否又某个已经存在的锁会妨碍新锁授予调用进程,如果没有arg指向的l_type成员被置为F_UNLCK
应该清楚发出F_GETLK命令后紧接着发出F_SETTLK命令不是一个原子操作。不能保证fcntl函数成功返回。
l_whence的三个取值:
SEEK_SET:l_start相对与文件的开头解释
SEEK_CUR:l_start相对与文件当前字节
SEEK_END:l_start相对与文件的末尾解释
锁住文件的两个方式:
(1)制定l_whence成员为SEEK_SET,l_start的成员为0,l_len成员为0
(2)使用lseek把读写指针放到文件头,然后制定l_whence为SEEK_SET,l_start为0,l_len为0.- void my_lock(int fd)
- {
- struct flock lock;
- lock.l_type=F_WRLCK;
- lock.l_whence=SEEK_SET;
- lock.l_start=0;
- lock.l_len=0;
- fcntl(fd,F_SETLK,&lock));
- }
- void my_unlock(int fd)
- {
- struct flock lock;
- lock.l_type=F_UNLCK;
- lock.l_whence=SEEK_SET;
- lock.l_len=0;
- lock.l_start=0;
- fcntl(fd,F_SETLK,&lock);
- }
4:劝告性上锁
POSIX记录上锁称为劝告性上锁,其含义是内核维护着已由各个进程上锁的所有文件的正确信息,但是他不能防止一个进程写已由另一个进程读锁定的某个文件。劝告性锁对于写作进程足够。
5:强制性上锁
使用强制性上锁后,内核检查每个read,write请求,以验证其操作不会干扰由某个进程持有的某个锁。
对于某个特定文件施行强制性上锁,应满足:
(1)组成员执行位必须打开
(2)SGID位必须打开
QUESTION
读出者和写入者的优先级别:
eg:父进程取得一个文件的读出锁,然后fork两个进程,第一个子进程首先尝试获取一个写入锁,第二个进程尝试获取一个读出锁。
- ******************
- int lock_reg(int fd,int cmd,int type,off_t offset,int whence,int len)
- {
- struct flock lock;
- lock.l_type=type;
- lock.l_offset=offset;
- lock.l_whence=whence;
- lock.l_len=len;
- return(fcntl(fd,cmd,&lock));
- }
- int main(int argc,char *argv[])
- {
- int fd;
- fd=open("test1.data",O_RDWR|O_CREAT,3);
- lock_reg(fd,F_SETCLK,F_RDLCK,0,SEEK_SET,0);
- if(fork()==0)
- {
- sleep(1);
- printf("the first child tries to obtains the writelock\n");
- lock_reg(fd,F_SETCLKW,F_WRLCK,0,SEEK_SET,0);
- printf("the first child obtains the writelock\n");
- sleep(2);
- lock_reg(fd,F_SETCLK,F_UNLCK,0,SEEK_SET,0);
- printf("the first child has released the writelock\n");
- exit(0);
- if(fork()==0)
- {
- sleep(3);
- printf("the second child want the read lock\n");
- lock_reg(fd,F_SETCLK,F_RDLCK,0,SEEK_SET,0);
- printf("the second obtains the read lock\n");
- sleep(4);
- lock_reg(fd,F_SETCLK,F_UNLCK,0,SEEK_SET,0);
- exit(0);
- }
- exit(0);
- }
- }
上述列子告诉我们:只要连续不断的发出读出锁的请求,写入这就可能因为获得不了写入锁而一直等待。
QUESTION
等待着的写入这是否比等待的读出者优先?
我们来看一个例子:读出锁请求是在第一秒发生,写入锁请求第三秒发生:
- int lock_reg(int fd,int cmd,int type,off_t offset,int whence,int len)
- {
- struct flock lock;
- lock.l_type=type;
- lock.l_offset=offset;
- lock.l_whence=whence;
- lock.l_len=len;
- return(fcntl(fd,cmd,&lock));
- }
- int main(int argc,char *argv[])
- {
- int fd;
- fd=open("test1.data",O_RDWR|O_CREAT,3);
- lock_reg(fd,F_SETCLK,F_RDLCK,0,SEEK_SET,0);
- if(fork()==0)
- {
- sleep(3);
- printf("the first child tries to obtains the writelock\n");
- lock_reg(fd,F_SETCLKW,F_WRLCK,0,SEEK_SET,0);
- printf("the first child obtains the writelock\n");
- sleep(4);
- lock_reg(fd,F_SETCLK,F_UNLCK,0,SEEK_SET,0);
- printf("the first child has released the writelock\n");
- exit(0);
- if(fork()==0)
- {
- sleep(1);
- printf("the second child want the read lock\n");
- lock_reg(fd,F_SETCLK,F_RDLCK,0,SEEK_SET,0);
- printf("the second obtains the read lock\n");
- sleep(2);
- lock_reg(fd,F_SETCLK,F_UNLCK,0,SEEK_SET,0);
- exit(0);
- }
- exit(0);
- }
- }
这个程序与上一个没有什么区别,只不过让等待读锁的等待的时间少。
我们从这个列子可以知道:不管上锁请求类型,都是FIFO顺序处理上锁请求。
6:守护进程的唯一副本
记录上锁的常见用途是确保某个程序在任何时刻只有一个副本在运行。
7:文件作锁用
O_CREAT|O_EXCL保证文件已经存在的时候返回一个错误。而且考虑到其他进程存在,检查该文件是否存在和创建必须是原子的。因此我们将这个文件作为锁使用。
POSIX.1保证任何时候只有一个进程能够创建这样的文件。
- #include<stdio.h>
- #define LOCKFILE "/tmp/seqno.lock"
- void my_lock(int fd)
- {
- int tempfd;
- tempfd=open(LOCKFILE,O_RDWR|O_CREAT|O_EXCL,3);
- while(tempfd<0)
- {
- if(errno!=EEXIST)
- perror("open error for lock file");
- }
- close(tempfd);
- }
- void my_unlock(int fd)
- {
- unlink(LOCKFILE);
- }
8:NFS上锁
NFS是网络文件系统,unix系统通常以两个额外的守护进程支持NFS记录上锁,lockd和statd
阅读(2661) | 评论(0) | 转发(0) |