分类: LINUX
2007-12-22 16:53:23
记录锁
1.记录锁的功能
当一个进程正在读或修改文件的某个部分时,它可以阻止其他进程修改同一文件区。我们不应该从字面上去理解记录锁,实际上它应该叫“区域锁”,因为它锁定的只是文件的一个(也可能是整个文件)。这个区域用来存放多用户的共享区。
2.记录锁的分类
记录锁分为共享读锁和独占写锁,前者也叫做共享锁后者也叫做排他锁。
3.加锁规则
如果一个进程对共享区加了共享读锁,其他进程只能加共享读锁。如果一个进程加了独占写锁,其他进程就不能加任何锁。
4.死锁
如果两个相互等待对方持有并且不释放(已被锁定)的资源是时,则这两个进程就处于死锁状态。如果一个进程已经控制了文件的一个加锁区域,然后它又试图对另一个进程控制的区域加锁,则它就会睡眠,在这种情况下,有发生死锁的可能性。
5.锁的隐含继承和释放
(1)锁与进程和文件两方面有关系,它和前者关系是:当一个进程结束后,他对文件加的锁也就消失了。它和后者的关系是:当进程close文件描述符,切断文件和进程的联系进程所创建的锁也会消失。
(2)由fork产生的子进程不继承父进程所设置的锁。这意味着,若一个进程得到一把锁,然后调用fork,那么对于父进程创建的锁而言,子进程被视为另一个进程,不会拥有该锁。
(3)在执行exec后,新进程可以继承原执行的锁。因为执行exec前后还是一个进程。我们只是改变进程执行的程序,并没有创建新的进程。
6.要注意的问题
记录锁只是提供竞争进入某段代码区的功能,不会导致对文件操作失败。也就是说,我们对文件进行加锁后,我们还是可以对文件进行操作。
1.
名称:: |
fcntl |
功能: |
对文加解锁。 |
头文件: |
#include |
函数原形: |
int fcntl(int filedes,int cmd,…/*struct flock *flockptr */); |
参数: |
filedes 文件描述符 cmd 测试锁或加锁 flockptr 指向flock结构的指针 |
返回值: |
若成功返回0,若失败返回错误编号。 |
对于记录锁,cmd是F_GETLK,F_SETLKW或F_SETLKW.。
F_GETLK判断由flockptr所描述的锁是否会被另外一把锁排斥。如果存在一把锁,他阻止创建由flockptr所描述的锁,则把该现存锁的信息写到flockptr指向的结构中。如果不存在这种情况除了将l_type设置为F_UNLCK之外,flockptr所描述的其他信息都不变。
F_SETLK和F_SETLKW企图建立一把锁。F_SETLK和F_SETLKW的区别是F_SETLKW是F_SETLK的阻塞版本。如果存在其他锁,调用的进程就被阻塞直道捕捉到信号。
第三个参数是一个指向flock结构的指针:
struct flock{
short l_type;
off_t l_start;
shout l_whence;
off_t l_len;
pid_t l_pid;
};
flock结构说明:
所希望的锁类型:F_RDLCK(共享读锁)、F_WRLCK(独占性写锁)、F_UNLCK(解锁一个区域),这是由 l_type决定的。
要加锁或解锁区域的起始字节偏移量,这是由l_statt和l_whence两者决定。
区域的字节长度,由l_len表示。
具有能阻塞当前进程的锁,其持有的ID存放在l_pid中。
如若l_len为0,则表示锁的区域从其起点(由l_start和l_whence决定)开始直至最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于的范围。
如果想锁住整个文件,通常的方法是将l_start说明为0,l_whence说明为SEEK_SET,1_len说明为0。
还要注意的是,对文件加共享读锁时文件应以只读的方式打开,对文件加独占写锁时文件应以只读的方式打开。
I/O多路转接
如果我们想从多个文件描述符读或写数据,如果我们用以前学过的函数(read,write等)去处理可能会阻塞在一个文件描述符上,不能处理其他的文件描述符。那是因为我们以前学的I/O处理函数,都是阻塞的I/O处理函数,它们的特点是,如果缓冲区里有数据它们就会把数据写到文件中,如果缓存区没有数据他们就会等待(阻塞)直到有数据可读。这就造成了他们无法对多个文件描述符进行操作。而对多个文件描述符进行操作在网络通信方面却是执关重要的。
一种比较好的解决方案就是I/O多路转接技术。它现构造一张有关文件描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才返回。在返回时,它告诉进程那些描述符已经准备好可以进行I/O。poll,selsct,pselect这三个函数使我们能够执行I/O多路转接,下面就分别介绍它们。
2.
名称:: |
select |
功能: |
指行I/O多路转接 |
头文件: |
#include |
函数原形: |
int select(int maxfdpl,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *testrict exceptfds,struct timeval *testrict tvptr); |
参数: |
maxfdpl 最大描述符加1 readfds 读描述符集 writefds 写描述符集 excepfds 异常描述符集 tvptr 愿意等待的时间 |
返回值: |
准备就绪的文件描述符数,若超时则返回0,若出错则返回-1 |
select函数使我们可以执行I/O多路转接。传向select的参数告诉内核:我们所关系的描述符。对于每个描述符我们所关心的状态。以及我们愿意等待的时间。从select返回时,内核告诉我们:以准备好的描述符的数量。对于读、写或异常这三个状态中的每一个,那些描述符已经准备好。
这个函数比较复杂,我们一个一个参数的看。
第一个参数maxfdp1的意思是“最大描述符加
中间的三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读(readfds)、可写(writefd)或处于异常条件(wxcepfds)的各个描述符。每个描述符集存放在一个fd_set数据类型中。这种结构相当于一个描述符的数组,它为每个可能的描述符设置1位。
fd0 fd1 fd2 fd3 fdn
0 |
0 |
0 |
0 |
… |
readfdsà
fd0 fd1 fd2 fd3 fdn
0 |
0 |
0 |
0 |
… |
writefdsà
fd0 fd1 fd2 fd3 fdn
0 |
0 |
0 |
0 |
… |
excepfdsà
可用下面4个函数对描述符集进行操作。
select的中间三个参数中的任意一个或全部都可以是空指针,这表示对相应状态不关系。如果所有三个指针都是空指针,则select提供了较sleep更精确的计时器。其等待时间可以小于1秒。
tuptr指定最后等待的时间,它的结构是:
struct timeval{
long tv_sec; 秒
long tv_usec; 微秒
};
有三种情况:
(1) tvptr==NULL:永远等待。如果捕捉到一个信号则中断此无限等待。当所指定的描述符中的一个已经准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR.
(2) tvptr->tv_sec==0&&tvptr_usec==0 完全不等待。测试所有的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。
(3)tvptr->tv_sec!=0||tvptr_usec!=0 等待指定的秒数或微秒数。当指定的描述符之一已准备好,或当指定的时间值已超过时立即返回。如果在超时还没有一个描述符准备好,则返回值是0。
3.
名称:: |
FD_ISSET/FD_CLR/FD_SET/FD_ZERO |
功能: |
描述符集处理函数 |
头文件: |
#include |
函数原形: |
int FD_ISSET(int fd,fd_set *fdset); void FD_CLR(int fd,fd_set *fdset); void FD_SET(int fd,fd_set *fdset); void FD_ZERO(fd_set *fdset); |
参数: |
fdset 描述符集 fd 描述符 |
返回值: |
若fd在描述符集中则返回非0值,否则返回0(FD_ISSET) |
调用FD_ZERO将一个指定的fd_set变量的所有位设置为0。调用FD_SET设置一个fd_set变量的指定位。调用FD_CLR将一指定位清除。最后调用FD_ISSET测试一指定位是否设置。声明了一个描述符集后,必须用FD_ZERO清除其所有位,然后在其中设置我们关心的各个位。
4.
名称:: pselect 功能: 指行I/O多路转接 头文件: #include 函数原形: int
pselect(int masfdp1,fd_set *restrict readfds,fd_set *restrict
writefds,fd_set excepfds,const struct timespec *restrict tsptr,const
sigset_t *restrict sigmask); 参数: maxfdpl 最大描述符加1 readfds 读描述符集 writefds 写描述符集 excepfds 异常描述符集 tsptr 愿意等待时间 sigmask 信号屏蔽集 返回值: 若fd在描述符集中则返回非0值,否则返回0(FD_ISSET)
pselect是select的一个变体,除以下几点外,pselect与select相同:
(1) select的超时值用timeval结构指定,但pselect使用timespec结构指定。timespec以秒和纳秒表示超时值。
(2) pselect的超时值被定义为const,这保证了调用pselect不会改变此值,。
(3) 对于paselect可以使用一可选的信号屏蔽字。若sigmask为空,那么在于信号有关的方面,pselect和select相同。否则,sigmask指向一信号屏蔽字,在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时恢复以前的信号屏蔽字。
5.
名称:: |
poll |
功能: |
指行I/O多路转接 |
头文件: |
#include |
函数原形: |
int poll(struct pollfd fdarray[],nfds_t nfds,int timeout); |
参数: |
fdarray 存放描述符集的数组 nfds fdarray数组元素个数 timeout 超时等待时间 |
返回值: |
准备就绪的描述符数,若超时则返回0,若出错则返回-1 |
poll类似于select,但是其接口则有所不同。poll不时为每个状态(可读性,可写性和异常状态)构造一个描述符集,而是构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及其所关心的状态。 struct pollfd{ int fd; 文件描述符 shout events; shout revents; }; fdarray数组中的元素由nfds说明。 应将每个数组元素的events成员设置为下表的值。通过这些告诉内核我们对该描述符关系的时什么。返回时,内核设置revents成员,以说明对于该描述符已经发生了什么事件。
标志名 说明 POLLIN POLLRDNORM POLLRDBAND POLLPRI 不阻塞地可读除高优先级外的数据(等效于POLLRDNORM|POLLRDBAD) 不阻塞地可读普通数据(优先级波段为0) 不阻塞地可读非0优先级波段数据 不阻塞地可读高优先级数据 POLLOUT POLLWRNORM POLLWRBAND 不阻塞地可写普通数据 与POLLOUT相同 不阻塞地可写非0优先级波段数据 POLLERR POLLHUP POLLNVAL 已出错 已挂断 描述符不引用一打开文件
表头四行测试可读性,接着三行测试可写性,最后三行则是测试异常状态。最后三行是由内核在返回时设置的。即使在events字段中没有指定这三个值,如果相应条件发生,则在revents中也它们。
当一个描述符被挂断后,就不能再写向该描述符。但是仍可能从该描述符读取数据。
poll的最后一个参数说明我们愿意等待多少时间。如同sellect一样,有三种不同情形:
(1) timeout==-1永远等待,当所指定的描述符中的一个已准备好,或捕捉到一个信号时则返回。如果捕捉到一个信号,则poll返回-1,error设置为EINTR.
(2)timeout==0 不等待。测试所有的描述符并立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。
(3)timeout>0 等待timeout毫秒。当指定的描述符之一已准备好,或当指定的时间值已超过时立即返回。如果在超时还没有一个描述符准备好,则返回值是0。
三、读写多个缓冲区
6.
名称:: |
readv/writev |
功能: |
散布读/聚集写 |
头文件: |
#include |
函数原形: |
ssize_t readv(int filedes,const struct iovec *iov,int iovcnt); ssize_t writev(int filedes,const struct iovec *iov,int iovcnt); |
参数: |
filedes 文件描述符 iov 指向iovec结构数组的一个指针。 iovcnt 数组元素的个数 |
返回值: |
若成功则返回已读、写的字节数,若出错则返回-1 |
readv和writev函数用于在一次函数调用中读、写多个非连续缓冲区。有时也将这两个函数成为散布读和聚集写。 这两个函数的第二个参数是指向iovec结构数组的一个指针: struct iovec{ void *iov_base; size_t iov_len; }; writev以顺序iov[0]至iov[iovcnt-1]从缓冲区中聚集输出数据。writev返回输出的字节总数,通常,它应等于所有缓冲区长度之和。 readv则将读入的数据按上述同样顺序散布读到缓冲区中。readv总是先填满一个缓冲区,然后再填写下一个。readv返回读到的总字节数。如果遇到文件结尾,已无数据可读,则返回0。
四、存储映射I/O
存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应字节自动地写入文件。这样就可以在不使用read和write的情况下执行I/O。
6.
名称:: |
mmap |
功能: |
把I/O文件映射到一个存储区域中 |
头文件: |
#include |
函数原形: |
void *mmap(void *addr,size_t len,int prot,int flag,int filedes,off_t off); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 prot 对映射存储区的保护要求 flag flag标志位 filedes 要被映射文件的描述符 off 要映射字节在文件中的起始偏移量 |
返回值: |
若成功则返回映射区的起始地址,若出错则返回MAP_FAILED |
addr参数用于指定映射存储区的起始地址。通常将其设置为0,这表示由系统选择该映射区的起始地址。
filedes指要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数。
off是要映射字节在文件中的起始偏移量。通常将其设置为0。
prot参数说明对映射存储区的保护要求。可将prot参数指定为PROT_NONE,或者是PROT_READ(映射区可读),PROT_WRITE(映射区可写),PROT_EXEC(映射区可执行)任意组合的按位或,也可以是PROT_NONE(映射区不可访问)。对指定映射存储区的保护要求不能超过文件open模式访问权限。
flag参数影响映射区的多种属性:
MAP_FIXED 返回值必须等于addr.因为这不利于可移植性,所以不鼓励使用此标志。
MAP_SHARED 这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件。
MAP_PRIVATE 本标志导致对映射区建立一个该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。
要注意的是必须指定MAP_FIXED或MAP_PRIVATE标志其中的一个,指定前者是对存储映射文件本身的一个操作,而后者是对其副本进行操作。
7.
名称:: |
memcpy |
功能: |
复制映射存储区 |
头文件: |
#include |
函数原形: |
void *memcpy(void *dest,const void *src,size_t n); |
参数: |
dest 待复制的映射存储区 src 复制后的映射存储区 n 待复制的映射存储区的大小 |
返回值: |
返回dest的首地址 |
memcpy拷贝n个字节从dest到src。
8.
名称:: |
munmap |
功能: |
解除存储映射 |
头文件: |
#include |
函数原形: |
int munmap(caddr_t addr,size_t len); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 |
返回值: |
若成功则返回0,若出错则返回-1 |
进程终止时,或调用了munmap之后,存储映射区就被自动解除映射。关闭文件描述符filedes并不解除映射区。
9.
名称:: |
mprotect |
功能: |
改变映射存储区的权限 |
头文件: |
#include |
函数原形: |
int mprotect(void *addr,size_t len,int prot); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 prot 对映射存储区的保护要求 |
返回值: |
若成功则返回0,若出错则返回-1 |
mprotect可以更改一个现存映射存储区的权限。
10.
名称:: |
msync |
功能: |
同步文件到存储器 |
头文件: |
#include |
函数原形: |
int msync(void *addr,size_t len,int flags); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 prot flags |
返回值: |
若成功则返回0,若出错则返回-1 |
如果在共享映射区中的页已被修改,那么我们可以调用msync将该页冲洗到映射的文件中。flags参数使我们对如何冲洗存储区有某种程度的控制。我们可以指定MS_ASYNC标志以简被写页的调度。如果我们希望在返回之前等待写操作的完成,则可指定MS_SYNC标志。一定要制定MSASYNC或MS_SYNC中的一个。MS_INVALIDATE是一个可选的标志,使用它会通知操作系统丢弃与底层存储器没有同步的任何页。