linux编程学习 01 linux IO 系统调用
一、文件I/O系统调用简介
1、标准库函数
遵守ISO标准,基于流的I/O,对文件指针(FILE结构体)进行操作
2、系统调用
兼容POSIX标准,基于文件描述符的I/O,对文件描述符进行操作,包括:
open(): 打开文件
create():创建文件
close(): 关闭文件
read(): 读取文件
write(): 写入文件
lseek(): 文件定位
这些不带缓存的函数都是内核提供的系统调用。它们不是ANSI C的组成部分,但是POSIX的组成部分。
二、文件描述符
1、对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
2、在POSIX应用程序中,整数0、1、2被替换成符号常数 STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在头文件 中。
3、文件描述符的范围是0-OPEN_MAX,早期的unix版本是19(允许每个进程打开20个文件),现在很多系统设置成23,linux为1024
标准文件指针
- stdin 0
- stdout 1
- stderr 2
fdopen()
FILE *fdopen(int fd,const char* mode);
文件描述符 -----> 文件指针 (fd -----> FILE*)
fileno
int fileno(FILE *stream)
文件指针 -----> 文件描述符 (FILE* -----> fd)
三,函数介绍
一、open函数
1、 头文件(后面都是这个格式)
#include <sys/types.h>. // 提供mode_t类型
#include <sys/stat.h> // 提供open()函数的符号
#include <fcntl.h> // 提供open()函数
#include <unistd.h> // 提供close()函数
#include <errno.h> // 提供strerror()函数和errno
#include <stdio.h> // 提供输出函数
#include <string.h> // 提供strerror函数
2、函数原型
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
返回: 若成功为文件描述符,若出错为-1
功能: 打开或创建一个文件
参数
pathname:要打开或者创建的文件路径
flags:用来说明此函数的多个选择项
用下列一个或者多个常数进行按位或运算构成flags参数,这些常数定义在/usr/include/bits/fcntl.h
中,在include fcntl.h
时,会包含这个文件
常量名 | 解释 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写 |
O_CREAT | 如果指定的文件不存在,则按照mode参数指定的文件权限来创建文件 |
O_EXCL | 如果同时指定了O_CREAT,而文件已经存在,则出错。可测试一个文件是否存在。但在网络文件系统进行操作时却没有保证 |
O_DIRECTORY | 如果参数pathname不是一个目录,则open出错 |
O_TRUNC | 如果文件存在,而且为只读或只写成功打开,则将其长度截短为0 |
O_APPEND | 以追加模式打开文件,每次写时加到文件尾端,但在网络文件系统进行操作时没有保证 |
O_NONBLOCK | 如果pathname指定的是是一个FIFO,一个块特殊文件或者一个字符特殊文件,则此选择项为此文件的本次打开操作和后续I/O操作设置非阻塞方式 |
mode:新建文件的访问权限,对于open函数而言,仅当创建新文件时才使用第三个参数。
二、creat函数
int creat(const char *pathname, mode_t mode);
返回: 若成功为只写打开的文件描述符,若出错为-1
功能: 创建一个新文件
等同于open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode) ;
以只写方式打开所创建的文件。
三、close函数
int close(int fd);
返回: 若成功为0,若出错为-1
功能: 关闭一个打开的文件
参数
fd:已打开文件的文件描述符
当一个进程终止时,他所有打开的文件都由内核自动关闭
四、read函数
ssize_t read(int fd, void *buf, size_t count);
返回: 读到的字节数,若已到文件尾为0,若出错为-1
功能: 从打开文件中读数据
参数:
fd:读取文件的文件描述符
buf:存放读取数据的缓存
count:要求读取一次数据的字节数
有多种情况可使实际读到的字节小于要求读取的字节数
- 读普通文件时,在读到要求字节数之前已经到达文件尾端
- 当从终端设备读取时,通常一次只能读取一行
- 当从网络读时,网络中的缓冲机构可能造成返回值小于要求要读的字节数
- 某些面向记录的设备,如磁带,一次最多返回一个记录
- 进程由于信号造成中断
读操作从当前位移量处开始,在成功返回之前,该位移量增加实际读到的字节数==
五、write函数
ssize_t write(int fd, const void *buf, size_t count);
返回: 若成功为已写的字节数,若出错为-1
功能: 向打开的文件中写数据
参数:
fd:写入文件的文件描述符
buf:存放待写数据的缓存
count:要求写入一次数据的字节数
其返回值通常与参数count的值相同,否则表示出错
write出错的原因:磁盘已满,或者超过了对一个给定进程的文件见长度限制
对于普通文件,写操作从文件当前位移量处开始。如果在打开该文件时,指定了O_APPEND选择项,则在每次写操作前,将文件位移量设置在文件的当前结尾处。在一次写成功之后,该文件位移量增加实际写的字节数
示例
int cp(char* source,char* destination)
{
int s = open(source,O_RDONLY);
int d = open(destination,O_CREAT | O_WRONLY | O_TRUNC,0777);
if(s == -1 || d == -1)
{
fprintf(stderr,"open file error %s \n",strerror(errno));
return 0;
}
// 每次读取的大小可以根据设备的block_size来决定,可以通过 tune2fs -l /dev/sda1 查看
int buffer_length = 888,read_flag = 0;
char buffer[buffer_length];
while((read_flag = read(s,buffer,buffer_length)) != 0)
{
// 只写入读取到的数据
if(read_flag > write(d,buffer,read_flag))
{
fprintf(stderr,"write file error %s \n",strerror(errno));
return 0;
}
memset(buffer,'\0',buffer_length);
}
close(s);
close(d);
return 1;
}
lseek函数
off_t lseek(int fd,off_t offset, int whence);
- 头文件:
#include <sys/types.h>
#include <unistd.h>
- 返回:若成功则返回新的文件位移量(绝对偏移量);
- 功能:定位一个以打开的文件
参数
- fd:已打开文件的文件描述符
- offset:位移量
whence:定位的位置
- SEEK_SET:将该文件的位移量设置为距文件开始处offset个字节
- SEEK_CUR:将该文件的位移量设置为其当前值加offset,offset可为正或负
- SEEK_END: offset,offset可为正或负
- lseek也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或
FIFO
,则lseek返回-1,并将errno设置为EPIPE。 - 每个打开文件都有一个与其相关联的"当前文件偏移量"。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读,写操作都从当前文件偏移量处开始,并将偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定
O_APPEND
选择项,否则该位移量被设置为0. - 示例
// 创建黑洞文件
int file_hole(char* file_path)
{
int f = open(file_path,O_CREAT | O_WRONLY | O_TRUNC,0777);
if(f == -1)
{
perror("open or create file:");
return 0;
}
char buffer[] = "abcdef";
if(sizeof(buffer) != write(f,buffer,sizeof(buffer)))
{
perror("write file:");
return 0;
}
// 移出文件末尾,再进行写入
lseek(f,8,SEEK_END);
if(sizeof(buffer) != write(f,buffer,sizeof(buffer)))
{
perror("write file:");
return 0;
}
close(f);
return 1;
}
// cat函数
void cat(int argc,char* argv[])
{
int in = STDIN_FILENO;
int out = STDOUT_FILENO;
if(argc == 1)
{
cp_by_file_no(in,out);
}
else
{
for(int i = 1;i < argc;i++)
{
in = open(argv[i],O_RDONLY);
if(in == -1)
{
perror("open file error:");
}
cp_by_file_no(in,out);
}
}
}
文件I/O的内核数据结构
一个打开的文件在内核中使用三种数据结构(结构体)表示
文件描述符表(使用open等函数返回的文件描述符其实就是文件描述表(结构体数组)的下表)
- 文件描述符标志
- 文件表项指针
文件表项
文件状态标志
- 读,写,追加,同步和非阻塞等状态标志
- 当前文件偏移量
- i节点表项指针
- 饮用计数器
i节点
- 文件类型和对该文件的操作函数指针
- 当前文件长度
- 文件所有者
- 文件所在的设备,文件访问权限
- 指向文件数据在磁盘块上所在位置的指针等
dup与dup2函数
int dup(int oldfd);
int dup2(int oldfd,int newfd);
- 头文件:
#include <unistd.h>
- 返回:成功返回新文件描述符,出错返回-1
- 功能:文件描述符的复制
参数
- oldfd:原先的文件描述符
- newer:新的文件描述符
- 由dup返回的新文件描述符一定时当前可用文件描述符中的最小数值
- 用dup2则可以用newfd参数指定新描述符的数值。如newfd已经打开,则先将其关闭。如若oldfd等于newfd,则dup2返回newfd,而不关闭它(就是把文件描述符表中下标为newfd的结构体中文件表项指针存放的地址切换成文件描述符表中下标为oldfd的结构体中文件表项指针存放的地址,就是指针间的赋值)
- 在进程间通信时可用来改变进程的标准输入和标准输出设备
- 示例
void cat(int argc,char* argv[])
{
int flag_arr[4] = {-1,-1,-1,-1},i = 1,stdin_temp = -1,stdout_temp = -1,file_temp = -1,argc_temp = argc;
if(argc > 2)
{
for(;i < (argc_temp - 1);i++)
{
// 修改输入重定向
if(strcmp("+",argv[i]) == 0)
{
flag_arr[0] = i++;
flag_arr[1] = i;
// 备份下标准输入,函数执行完毕后恢复
stdin_temp = dup(STDIN_FILENO);
file_temp = open(argv[i],O_RDONLY);
if(file_temp == -1)
{
perror("open input file error");
}
dup2(file_temp,STDIN_FILENO);
argc -= 2;
}
// 修改输出重定向
if(strcmp("-",argv[i]) == 0)
{
flag_arr[2] = i++;
flag_arr[3] = i;
// 备份下标准输入,函数执行完毕后恢复
stdout_temp = dup(STDOUT_FILENO);
file_temp = open(argv[i],O_CREAT | O_WRONLY | O_TRUNC,0777);
if(file_temp == -1)
{
perror("create or open output file error");
}
dup2(file_temp,STDOUT_FILENO);
argc -= 2;
}
}
}
// 同时输入了输入和输出
if(flag_arr[0] != -1 && flag_arr[2] != -1)
{
argc = flag_arr[1] + 1;
flag_arr[1] = -1;
}
int in = STDIN_FILENO;
int out = STDOUT_FILENO;
if(argc == 1)
{
cp_by_file_no(in,out);
}
else
{
for(i = 1;i < argc;i++)
{
if(in_array(i,flag_arr,4) == 1)
{
continue;
}
in = open(argv[i],O_RDONLY);
if(in == -1)
{
perror("open file error:");
}
cp_by_file_no(in,out);
}
}
// 恢复标准输入输出
if(flag_arr[0] != -1)
{
dup2(stdin_temp,STDIN_FILENO);
}
if(flag_arr[2] != -1)
{
dup2(stdout_temp,STDOUT_FILENO);
}
}
fcntl函数
int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);
- 头文件:
#include <unistd.h>
#include <fcntl.h>
- 返回:若成功则依赖于cmd,若出错为-1
- 功能:可以改变已经打开文件的性质
cmd的常见取值
F_DUPFD
- 复制文件描述符,新的文件描述符作为函数值返回
F_GETFD/F_SETFD
- 获取/设置文件描述符,通过第三个参数设置
F_GETFL/F_SETFL
- 获取/设置文件描述符标志,通过第三个参数设置
- 可以更改的几个标志是:O_APPEND,O_NONBLOCK,SYNC,O_ASYNC(O_RDONLY,O_WRONLY,O_RDWR不适用)
常见使用
- 复制一个现存的描述符,新文件描述符作为函数值返回(cmd=F_DUPFD)
- 获得/设置文件描述符标志(cmd=F_GETFD或F_SETFD)
- 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)
获得/设置文件锁(cmd=F_SETLK,cmd=F_GETLK、F_SETLKW)
- 文件锁必须用到第三个参数,第三个参数为struck flock结构体指针
- 示例
int set_fl(int fd,int flag)
{
flag |= fcntl(fd,F_GETFL);
if(fcntl(fd,F_SETFL,flag) < 0)
{
return 0;
}
return 1;
}
int clear_fl(int fd)
{
int flag = fcntl(fd,F_GETFL);
flag &= ~flag;
if(fcntl(fd,F_SETFL,flag) < 0)
{
return 0;
}
return 1;
}
I/O处理方式
I/O阻塞模型
- 若所调用的I/O函数没有完成相关功能就会使进程挂起,直到相关数据到达才会返回。如:终端,网络设备的访问
非阻塞模型
- 当请求的I/O操作不能完成时,则不让进程休眠,而且返回一个错误。如open,read,write访问
I/O多路转接模型
- 如果请求的I/O操作阻塞,且她不是真正阻塞I/O,而且让其中的一个函数等待,在这期间,I/O还能进行其他操作。如:select函数。
信号驱动I/O模型
- 在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。
异步I/O模型
- 在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通知内核。有内核进行后续处理,这种用法现在较少。
char str[888] = {'\0'};
// 设置非阻塞
set_fl(STDIN_FILENO,O_NONBLOCK);
// 下面的代码不会等待
read(STDIN_FILENO,str,888);
printf("%s\n",str);
文件和目录
struct stat结构体
struct stat{
mode_t st_mode; /* file type & permission */
ino_t st_ino; /* i-node number */
dev_t st_dev; /* device number(file system) */
dev_t st_rdev; /* device number for special files */
nlink_t st_nlink; /* number of links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
off_t st_size; /* size in byte */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last file status change */
blksize_t st_blksize; /* best I/O block size */
blkcnt_t st_blocks; /* number of disk blocks allocated */
};
文件属性操作函数
int stat(const char* pathname,struct stat* buf);
int fstat(int fd,struct stat* buf);
int lstat(const char* pathname,struct stat* buf);
- 头文件:
#include <sys/types.h>
#include <sys/stat.h>
- 返回:成功返回0,出错返回-1
- 功能:返回一个pathname路径或fd指定的文件的属性信息,存储在结构体buf中
参数
- pathname:文件路径名字
- buf:struct stat结构体指针
- lstat类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息
linux中七种文件类型和七种宏
文件类型 | 宏 |
---|---|
普通文件(regular file) | S_ISREG() |
目录文件(directory file) | S_ISDIR() |
块特殊文件(block special file) | S_ISBLK() |
字符特殊文件(character special file) | S_ISCHR() |
管道文件 FIFO(named pipe) | S_ISFIFO() |
套接字(socket) | S_ISSOCK() |
符号链接(symbolic link) | S_ISLNK() |
- 示例
void file_type(char* path)
{
struct stat s;
if(lstat(path,&s) == -1)
{
perror("incorrect file");
return;
}
char* str = "unknow type";
if(S_ISREG(s.st_mode))
{
str = "regular file";
}
else if(S_ISDIR(s.st_mode))
{
str = "directory";
}
else if(S_ISBLK(s.st_mode))
{
str = "block special file";
}
else if(S_ISCHR(s.st_mode))
{
str = "character special file";
}
else if(S_ISFIFO(s.st_mode))
{
str = "FIFO";
}
else if(S_ISSOCK(s.st_mode))
{
str = "socket file";
}
else if(S_ISLNK(s.st_mode))
{
str = "symbolic link";
}
printf("%s : %s\n",path,str);
}
void print_file_stat(char* path)
{
struct stat* s = malloc(sizeof(struct stat));
if(stat(path,s) == -1)
{
perror("incorrect file : ");
}
printf("mode \t %d\nino \t %d\ndev \t %d\nrdev \t %d\nnlink \t %d\nuid \t %d\ngid \t %d\nsize \t %d\natime \t %d\nmtime \t %d\nctime \t %d\nblksize \t %d\nblocks \t %d\n",
(int)s->st_mode,(int)s->st_ino,(int)s->st_dev,(int)s->st_rdev,(int)s->st_nlink,(int)s->st_uid,(int)s->st_gid,(int)s->st_size,(int)s->st_atime,(int)s->st_mtime,(int)s->st_ctime,(int)s->st_blksize,(int)s->st_blocks);
free(s);
}
文件权限
9种文件访问权限位
用户权限
- S_IRUSR,S_IWUSR,S_IXUSR
组权限
- S_IRGRP,S_IWGRP,S_IXGRP
其他权限
- S_IROTH,S_IWOTH,S_IXOTH
- 文件权限通过按位或方式构造
- 示例:
// 创建文件是给权限
int f1 = open("test.txt",O_CREAT | O_RDONLY,S_IWUSR | S_IRUSR | S_IWGRP);
int f2 = open("test.txt",O_CREAT | O_RDONLY,0620); // 与上面效果一致
// 输出文件权限
void file_permission(char* path)
{
struct stat s;
if(lstat(path,&s) == -1)
{
perror("incorret file");
return;
}
printf("%c%c%c%c%c%c%c%c%c\n"
,(S_IRUSR & s.st_mode) ? 'r' : '-'
,(S_IWUSR & s.st_mode) ? 'w' : '-'
,(S_IXUSR & s.st_mode) ? 'x' : '-'
,(S_IRGRP & s.st_mode) ? 'r' : '-'
,(S_IWGRP & s.st_mode) ? 'w' : '-'
,(S_IXGRP & s.st_mode) ? 'x' : '-'
,(S_IROTH & s.st_mode) ? 'r' : '-'
,(S_IWOTH & s.st_mode) ? 'w' : '-'
,(S_IXOTH & s.st_mode) ? 'x' : '-'
);
}
access函数
int access(const char* pathname, int mode);
- 返回:成功执行时返回0,若出错为-1
- 功能:检查是否可以对指定文件进行某种操作
- 头文件
#include <unistd.h>
参数
- pathname:文件路径
mode:文件访问权限
- R_OK:判断文件是否有读权限
- W_OK:判断文件是否有写权限
- X_OK:判断文件是否有可执行权限
- F_OK:判断文件是否存在
- 示例
void file_access(char* path)
{
if(access(path,F_OK) == -1)
{
printf("%s does not exist\n",path);
return;
}
if(access(path,R_OK) == -1)
{
printf("%d cannot read file:%s\n",getpid(),path);
}
if(access(path,W_OK) == -1)
{
printf("%d cannot write file:%s\n",getpid(),path);
}
if(access(path,X_OK) == -1)
{
printf("%d cannot excute file:%s\n",getpid(),path);
}
}
linux文件系统的结构
文件操作相关的基本元素是:目录结构,索引节点和文件的数据本身
- 目录结构(目录项)
- 索引节点(i节点)
- 文件的数据
文件系统(硬盘)的三个区域
- 超级块:存放文件系统本身的结构信息
- i-节点表:存放i节点信息列表
- 数据区:存放文件内容
软链接与硬链接的区别
硬链接:会创建新目录项并指向原文件的i节点,并且文件的链接数会加1
软链接:会创建新目录项,新i节点,新数据块,新数据块存放的是原文件的路径
link函数
int link(const char* existing path,const char* newpath);
- 返回:成功返回0,出错返回-1
- 功能:创建一个指向现存文件链接(硬链接)
创建条件
- 针对文件创建链接
- 必须是同一分区
- 只有超级用户才能对目录建立链接
- 示例
if(-1 == link(argv[1],argv[2]))
{
printf("fail to create %s\n",argv[2]);
return 1;
}
unlink函数
int link(const char* existing path,const char* newpath);
- 返回:成功返回0,出错返回-1
- 功能:删除pathname指定的硬链接,并将由pathname所引用的文件链接计数减1
文件删除条件
- 链接计数为0
- 无其他进程打开该文件
- 示例
if(argc < 2)
{
printf("incorrect parameter\n");
return 1;
}
if(access(argv[1],F_OK) == -1)
{
printf("%s does not exist\n",argv[1]);
return 1;
}
if(-1 == unlink(argv[1]))
{
printf("fail to delete %s\n",argv[1]);
return 1;
}
remove函数
int remove(const char* pathname);
- 返回:成功返回0,出错返回-1
- 功能:解除对一个文件或目录的链接
- 头文件
#include <unistd.h>
- 对于文件,remove的功能与unlink相同
- 对于目录,remove的功能与rmdir相同
rename函数
int rename(const char* oldname,const char* newname);
- 返回:成功返回0,出错返回-1
- 功能:解除对一个文件或目录的链接
- 头文件
#include <unistd.h>
symlink函数
int symlink(const char* actualpath,const char* sympath);
- 返回:成功返回0,出错返回-1
- 功能:创建一个符号链接(软链接)
- 头文件
#include <unistd.h>
- 创建符号链接并不要求actualpath存在
- 可以跨文件系统建立符号链接
- 示例
if(argc < 3)
{
printf("incorrect parameter\n");
return 1;
}
if(access(argv[1],F_OK) == -1)
{
printf("%s does not exist\n",argv[1]);
return 1;
}
if(access(argv[2],F_OK) == 0)
{
printf("%s has alreay existed\n",argv[2]);
return 1;
}
if(-1 == symlink(argv[1],argv[2]))
{
printf("fail to create %s\n",argv[2]);
return 1;
}
readlink函数
int readlink(const char * path ,char * buf,size_t bufsiz);
- 返回:成功返回0,出错返回-1
- 功能:打开该链接本身,并读该链接中的名字
- 头文件
#include <unistd.h>
- 示例
if(argc < 2)
{
printf("incorrect parameter\n");
return 1;
}
if(access(argv[1],F_OK) == -1)
{
printf("%s does not exist\n",argv[1]);
return 1;
}
const int buffer_size = 1024;
char buffer[1024] = {'\0'};
if(-1 == readlink(argv[1],buffer,buffer_size))
{
printf("fail to delete %s\n",argv[1]);
return 1;
}
write(STDOUT_FILENO,buffer,buffer_size);
write(STDOUT_FILENO,"\n",1);