File I/O
1 file descriptor
// 查看当前session中的fd数量限制
ulimit -n
// 修改当前session中的fd数量限制
ulimit -n your_need
可以在一个终端使用以上命令,另外再打开一个进行查看。
在程序中,可以使用系统函数进行修改
#include <sys/resource.h>
struct rlimit
{
rlim_t rlim_cur;
rlim_t rlim_max;
}
int getrlimit(int resource, struct rlimit* rlim);
int setrlimit(int resource, const struct rlimit* rlim);
ext:
resource可选择的有:
RLIMIT_CORE: 设置core文件的大小,0表示禁止创建core文件。一个程序崩溃的时候,会在指定目录生成一个core文件,主要是用于调试。
RLIMIT_CPU: 设置每秒内的cpu Time,如果超过这个限制,则会发送SIGXCPU信号。
RLIMIT_FSIZE: 设置进程内文件最大长度,如果超过这个长度,则产生SIGXFSZ信号。如果线程阻塞,或者进程抓取或者忽略了这个信号,则会从底部减少文件大小并设置EFBIG错误。
RLIMIT_NOFILE: 设置文件描述符大小,比最大文件描述符大一,如果超过了这个限制,则申请文件描述符失败,并且设置EMFILE错误。
RLIMIT_STACK: 设置进程最大堆栈,单位为bytes。
RLIMIT_AS: 设置进程可用内存,单位为bytes。如果超过这个限制,malloc()和mmap()将会失败,并设置ENOMEM错误。
2 lseek function
off_t lseek(int fildes, off_t offset, int whence) 移动读/写偏移量
whence可选择的有:
SEEK_SET: 文件偏移量设置为 offset
SEEK_CUR: 文件偏移量设置为 当前偏移量 + offset
SEEK_END: 文件偏移量设置为 文件大小 + offset
获取当前偏移量的方法:
offset value = lseek(fd, 0, SEEK_CUR);
当操作的文件为 pipe, FIFO, socket时,返回-1,并设置错误为ESPIPE。
其它错误可以man 3 lseek查阅
程序用例:test2.1.cc
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if(lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("Seek Failed\n");
else
printf("Seek OK\n");
return 0;
}
g++ -o test2.1 test2.1.cc
./test2.1
输出:Seek Failed。原因:参数1 fildes必须是已经打开的文件描述符.
./test2.1 < /etc/passwd
输出:Seek OK
3 File Sharing -- close-on-exec
当使用fork创建子线程后,子线程获得父进程的数据空间,堆和栈的副本,也包含文件描述符,共享共同的打开文件标记,当前的偏移量等。
当在子进程中调用exec执行另一个程序时,替换了当前进程的正文,数据,堆和栈,即原先的子进程中的文件描述符丢失了(并未关闭),无法再关闭这些在子进程中不再使用到的文件描述符。而文件描述符是系统珍贵资源,数量有限。
一种办法是在执行exec之前先把这些文件描述符关闭,但在复杂的系统中,这是一件比较麻烦的事情。
另一种办法是打开文件的时候就指定好,当子进程执行exec的时候,这个文件关闭。即所谓的close-on-exec。
// 通过fcntl实现
int flags = fcntl(fd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(fd, F_SETFD, flags);
// 在创建或打开文件的时候指定
int fd = open("text.log", O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC);
具体查看man 2 open或者man 2 creat
4 Atomic Operations -- O_APPEND
有这样一个例子:
程序A和程序B对同一个文件进行写操作。
程序A先写入1500字节,程序B打开,调用lseek,得到文件末尾为1500, 写入100字节。
程序A执行,此时,程序A的file table entry记录文件末尾为1500,写入100字节。此时,覆盖了程序B写入的内容。
解决方案:
// 每次write的时候,都调用lseek,获取文件末尾偏移量。
// 在创建或者打开的时候设置参数O_APPEND, 设置此标记后,每次调用write,文件偏移量都会更新到文件末尾。
5 Atomic Operations -- pthread, pwrite
对一个文件指定位置进行写入或者读取,有两个步骤:
-1: 调用lseek,设置偏移量
-2: 调用read或者write操作。
两步操作在过程中可能被打断,得到的不是需要的结果。
而pthread/pwrite可以解决这个问题:
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
使用pthread/pwrite并不会改变当前文件偏移量。
当打开文件时指定了参数O_APPEND时,pwrite总是添加到文件末尾,而不会按照参数offset指定位置。
6 dup, dup2, dup3
int dup(int oldfd);
int dup2(int oldfd, int newfd);
int dup3(int oldfd, int newfd, int flags);
这些系统调用将会创建一个文件描述符的副本。
dup将返回一个未使用的最小的文件描述符
dup2将返回一个指定的文件描述符。如果oldfd指定了文件描述标记(close-on-exec),newfd并不会拥有。
需要重新使用fcntl指定。
如果newfd已经存在,则会先被关闭。
如果oldfd和newfd数值相同,且oldfd是有效的文件描述符,则dup2不做任何事情,返回newfd。
如果oldfd和newfd数值相同,且oldfd是无效的文件描述符,则调用失败, 不会调用newfd关闭的步骤。
dup3效果与dup2相同,但可以通过指定flags为O_CLOEXEC,使得newfd具有close-on-exec功能。
如果oldfd和newfd数值相同,则调用失败,并设置错误为INVAL。
7 fcntl function
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
可以改变一个已经打开的文件的属性
其中, cmd 可选择的有:
F_DUPFD: 返回一个大于等于参数3且最小未使用的文件描述符,与fd共享一个文件表项,如果fd有文件描述符标志(目前只有一个,FD_CLOEXEC),则此标志会被清除。可以查看dup2相关。
F_DUPFD_CLOEXEC: (since Linux 2.6.24) 效果等同于F_DUPFD,但会给返回的文件描述符添加close-on-exce标志。
F_GETFD: 返回fd的文件描述符标志。目前仅有一个标志:FD_CLOEXEC
F_SETFD: 将参数3设置为fd的文件描述符标志。
F_GETFL: 返回fd的文件状态标志,即open时候的flags标记。
O_RDONLY, O_WDONLY, O_RDWR, O_EXEC, O_SEARCH,
O_APPEND, O_NONBLOCK, O_SYNC, O_DSYNC, O_RSYNC, O_FSYNC, O_ASYNC
F_SETFL: 设置fd的文件状态标志,可设置的值有(Linux下, 本机为CentOS7.0, Mac和FreeBSD有不同的选项):
O_APPEND, 每次write的时候都将添加到文件的末尾,效果等同于open的时候添加O_APPEND标记。
O_NONBLOCK, 非阻塞IO,如果read没有数据可读,或者write操作阻塞,则返回-1并且设置错误为EAGAIN。
O_ASYNC, 当IO可用时,允许发送SIGIO信号到进程组。
O_DIRECT, 直接进行文件IO,系统尽量不进行缓存。具体可参考以下链接:
O_NOATIME, 读文件不改变最后访问时间。
另外还有F_GETOWN, F_SETOWN, F_GETOWN_EX等等,具体查看fcntl(2)
8 ioctl function
ioctl 是设备驱动程序中对设备的IO通道进行管理的函数。
不同的设备驱动都会定义自己的ioctl指令。
后边讲到套接字的时候再具体详述。
再详细点的讲述可以看这里:
参考: