APUE 学习笔记——高级 IO
如何创建一个守护进程?
-
首先调用
umask
将文件模式创建屏蔽字设置为一个已知值(通常是0)因为继承而来的文件模式创建屏蔽字很可能会被设置为拒绝某些权限。如果守护进程要创建文件,那么它可能需要设置特定的权限。
-
然后调用
fork
,然后使父进程exit
。这样做实现了下面几点:- 如果该守护进程是作为一条简单
shell
命令启动的,那么父进程终止会让shell
认为这条命令已经执行完毕 - 虽然子进程继承了父进程的进程组
ID
,但是获得了一个新的进程ID
,这就保证了子进程不是一个进程组的组长ID
,这就是后面的setsid
调用的先决条件
- 如果该守护进程是作为一条简单
-
然后调用
setsid
创建一个新会话。执行完setsid
之后,调用进程将成为新会话的首进程,而且它也是一个新进程组的组长进程,同时没有控制终端 -
将当前工作目录改为根目录
/
这是因为守护进程要求在系统重启/关闭之前是一直存在的。所以如果守护进程的当前工作目录在一个挂载的文件系统中,则该文件系统就不能被卸载。而从父进程中继承过来的当前工作目录可能就在一个挂在的文件系统中。
当然某些守护进程可能会将当前工作目录更改到某个指定位置(不一定是
/
) -
关闭不再需要的文件描述符。这使得守护进程不再持有从其父进程继承而来的任何文件描述符。
可以先判断最高文件描述符值,然后关闭直到该值的所有描述符
-
某些守护进程打开
/dev/null/
使其具有文件描述符0,1,2
。这样任何试图读标准输入、写标准输出、写标准错误的库例程都不会产生任何效果。因为守护进程并不与任何终端关联,所以其输出无处显示,也无法获得用户的输入。
1、fcntl
用于文件记录锁的时候,cmd
参数的选项有哪些?
-
F_GETLK
:判断由flockptr
所描述的锁能否顺利创建(如果不满足互斥规则,则无法创建)- 如果无法顺利创建,则将现有的锁(它阻止了
flockptr
所描述的锁的创建)的信息重写入flockptr
指向的内存 -
如果可以顺利创建,则将
flockptr
结构的l_type
设置为F_UNLCK
,其他信息保持不变如果
l_type
本身就是F_UNLCK
,则F_GETLK
会失败,errno
报告为Invalid Argument
- 如果无法顺利创建,则将现有的锁(它阻止了
-
F_SETLK
:设置由flockptr
所描述的锁。如果不满足兼容性规则从而无法加锁,则fcntl
会立即失败返回,此时errno
设置为EACCESS
或者EAGAIN
- 如果
flockptr
的l_type
为F_UNLCK
,则为清除由flockptr
所描述的锁
- 如果
-
F_SETLKW
:这个命令是F_SETLK
的阻塞版本。W
表示wait
。- 如果所请求的读锁或者写锁因为另一个进程对当前锁的请求区域的某个部分已经进行了加锁而不能完成,则调用线程会被设置为睡眠。
- 如果请求创建的锁已经可用,或者休眠由信号中断,则该进程被唤醒。
-
通常使用
F_GETLK
命令来测试能否建立一把锁,然后用F_SETLK
或者F_SETLKW
试图建立那把锁。但是注意到:这二者不是一个原子操作!因此不能保证在这两次fcntl
调用之间,可能另有一个进程插入并建立一把相同的锁从而导致本进程建立锁的过程失败。
2、fcntl
文件锁的隐含继承和释放规则?
-
记录锁与进程和文件两者相关联
- 当一个进程终止时,它所建立的所有记录锁全部被释放
-
无论一个文件描述符何时关闭,该进程通过这个描述符所引用的文件上的任何一把锁都将被释放(这些锁都是该进程设置的)
文件描述符是和进程关联的。文件时和进程无关的
fork
产生的子进程并不继承父进程所设置的记录锁。这是因为父进程与子进程是不同的进程,而记录锁是与进程和文件两者相关联子进程想获得记录锁,可以在继承而来的文件描述符上调用
fcntl
函数来设置记录锁。- 在执行
exec
后,新程序可以继承原程序的记录锁。但注意:如果对一个文件描述符设置了执行时关闭标志,则作为exec
的一部分关闭该文件描述符时,将释放相应文件的所有锁。
3、在文件尾端加锁/解锁时要注意什么?
- 当执行
fcntl(F_SETLK,flockptr)
,而flockptr->l_type=F_WRLCK
,flockptr->l_whence=SEEK_END
,flockptr->l_start=0
,flockptr->l_len=0
时,表示从当前文件尾端开始,包括以后所有可能追加写到该文件的任何字节都将加写锁 - 然后执行
write
一个字节时,该字节将被加锁 - 当执行
fcntl(F_SETLK,flockptr)
,而flockptr->l_type=F_UNLCK
,flockptr->l_whence=SEEK_END
,flockptr->l_start=0
,flockptr->l_len=0
时,表示从当前文件尾端开始解锁,但是之前刚写入的一个字节仍然保持在加锁状态!!
如果你想解除的锁包括之前刚写入的一个字节,则应该flockptr->l_len=-1
, 负的长度值表示在指定偏移量之前的字节数。
当对文件的一部分加锁时,内核将指定的偏移量变换成绝对文件偏移量。这是因为当前偏移量和文件尾端可能不断变化,但是这种变化不应该影响已经存在的锁的状态,所以内核必须独立于当前文件偏移量和文件尾端而记住锁的位置。
4、什么是建议性锁和强制性锁?
- 建议性锁:并不能阻止对文件有读写权限的任何其他进程不使用记录锁而访问文件.每个使用文件的进程都要主动检查该文件是否有锁存在,当然都是通过具体锁的API,比如fctl记录锁F_GETTLK来主动检查是否有锁存在。如果有锁存在并被排斥,那么就主动保证不再进行接下来的IO操作。如果每一个进程都主动进行检查,并主动保证,那么就说这些进程都以一致性的方法处理锁,(这里的一致性方法就是之前说的两个主动)。但是这种一致性方法依赖于编写进程程序员的素质。
- 强制性锁:内核会检查每一个
open,read,write
等操作,验证调用进程是否违背了正在访问的文件上的某一把锁。所有记录或文件锁功能内核执行的。上述提到的破坏性IO操作会被内核禁止。当文件被上锁来进行读写操作时,在锁定该文件的进程释放该锁之前,内核会强制阻止任何对该文件的读或写违规访问,每次读或写访问都得检查锁是否存在。也就是强制性锁机制,让锁变得名副其实,真正达到了锁的效果。
5、如何开启强制性锁?
对于一个特定文件,打开其设置组ID
位,同时关闭其组执行位,就开启了对该文件的强制性锁机制。
因为当组执行位关闭时,设置组
ID
位不再有意义(设置组ID
位的目的就是为了那些需要特殊组执行权限)
6、mmap
函数中 prot
、flag
参数的选项有哪些?
-
prot
:指定了映射存储区的保护要求,可以为下列之一:PROT_READ
:映射区可读PROT_WRITE
:映射区可写PROT_EXEC
:映射区可执行PROT_NONE
:映射区不可访问
也可以为
PROT_READ
、PROT_WRITE
、PROT_EXEC
的按位或。 -
flag
:影响映射存储区的多种属性:-
MAP_FIXED
:返回值必须等于addr
。因为这不利于可移植性,所以不鼓励使用此标志。如果未指定此标志,且
addr
非 0,则内核只是将addr
视为在何处设置映射区的一个建议,但是不保证会使用所要求的地址。将addr
设为 0,可以获取最大可移植性 -
MAP_SHARED
:它描述了本进程对于映射区所进行的存储操作的配置。此标志指定存储操作将修改底层的映射文件(即存储操作相当于对底层的映射文件进行write
) -
MAP_PRIVATE
:它描述了本进程对于映射区所进行的存储操作的配置。此标志指定存储操作将不会修改底层的映射文件,而是创建该底层的映射文件的一个私有副本。所有后续的对该映射区的引用都是引用该副本此标志的一个用途是用于调试程序,它将程序文件的正文部分映射到存储区,但运行用户修改其中的指令。任何修改只影响程序文件的副本,而不影响源文件
-
MAP_ANON
: 表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)若 fd 是
/dev/zero
设备的描述符时,也有类似功能
-
mmap
映射内存的限制?
linux
采用的是页式管理机制。对于用 mmap()
映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大
小由 mmap()
的 len
参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从 mmap()
返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:
总结一下就是, 文件大小, mmap的参数 len 都不能决定进程能访问的大小, 而是容纳文件被映射部分的最小页面数决定。
API
int fcntl(int fd,int cmd,.../*struct flock *flockptr */);
struct flock{
short l_type;// 指示锁的类型,如F_RDLCK(共享性读锁),F_WRLCK(独占性写锁),F_UNLCK(解锁)
short l_whence;// 指示锁的相对位置,如 SEEK_SET,SEEK_CUR,SEEK_END
off_t l_start; // 锁的起点相对于相对位置的字节数
off_t l_len; // 锁的长度,0表示锁的长度直到 EOF
pid_t l_pid; // 由 F_GETLK 返回,表示持有锁的进程的 ID
}
void *mmap(void *addr,size_t len,int prot,int flag,int fd,off_t off);
本作品采用《CC 协议》,转载必须注明作者和本文链接