系统调用:
由上向下一次调用 | ||
---|---|---|
库函数(标准C库) | man 3 | printf(3) |
系统调用函数 | man 2 | write(2) |
内核(kernel) | Linux write( ) |
文件描述符:
已打开文件的标志,非负整型数,默认情况最多打开1024个文件(0~1023).
系统默认打开3个文件,占用三个描述符:0 标准输入,1 标准输出, 2标准错误输出.
基本IO操作函数:
如何得到和使用文件描述符:
open(2) 打开文件
头文件:
#include
#include
#include
函数声明:
int open(const char *pathname, int flags, /*mode_t mode*/);
open(2)参数含义:
文件路径;打开方式;当打开方式为O_CREAT时需要第三个参数,第三个参数为创建文件的权限
文件权限一般写 0666 ,创建好的文件实际权限为 mode & ~umask
终端输入umask查看值(不同用户不一样)
最常用的六种打开方式
打开方式 | |||
---|---|---|---|
O_RDONLY | O_WRONLY | O_RDWR | 必须三选一 |
只读 | 只写 | 读写 | |
O_CREAT | O_TRUNC | O_APPEND | 可选 |
创建文件 | 将文件截断为0 | 在文件后追加 |
当需要多个打开方式时,用“ | ”连接.
函数返回值:
//返回新的文件描述符;
//如果发生错误,则返回-1,在这种情况下,会设置errno值
errno是一个全局变量,定义在errno.h中 大多数系统调用出错是会设置errno的值,
根据不同的值得到相应的错误原因 perror()直接将相应错误原因打印输出
关于更多 errno 值请查看man 3 errno
使用 open(2) 函数打开文件后一定要使用 close(2) 函数关闭文件!
read(2) 读取文件
头文件:
include
函数声明:
ssize_t read(int fd, void *buf, size_t count);
read(2)参数含义:
文件描述符;缓冲区( 就是读到哪 );缓冲区大小(不能大于定义好的缓冲区大小)
一般定义一个字符数组作为缓冲区,传递字符数组首地址
函数返回值:
//返回读取的字节个数;返回 0 表示文件读完;
//如果发生错误,则返回-1,在这种情况下,会设置errno值
关于 errno 值请查看man 3 errno
write(2) 写入文件
头文件:
include
函数声明:
ssize_t write(int fd, const void *buf, size_t count);
write(2)参数含义:
文件描述符;缓冲区( 就是写到哪 );缓冲区大小(不能大于定义好的缓冲区大小)
一般定义一个字符数组作为缓冲区,传递字符数组首地址
函数返回值:
//成功时,返回写入的字节数
//如果发生错误,则返回-1,在这种情况下,会设置errno值
关于 errno 值请查看man 3 errno
close(2) 关闭文件
头文件:
include
函数声明:
int close(int fd);
close(2)参数含义:
文件描述符
函数返回值:
//成功时,返回0
//如果发生错误,则返回-1,在这种情况下,会设置errno值
关于 errno 值请查看man 3 errno
open(2)、read(2)、write(2)、close(2) 用法示例
#include
#include
#include
#include
#include
#define BUFSIZE 10
int main(int argc, char *argv[])
{
int fd;
char buf[BUFSIZE] = {};
int cnt;
if (argc < 2)
return 1;
// fd 文件描述符>= 0 mode & ~umask
// fd = open(argv[1], O_RDWR | O_CREAT, 0666);
fd = open(argv[1], O_RDONLY); //通过open()获取文件描述符
if (fd == -1)
{
perror("open()"); //提示出错
return 1;
}
// 打开成功
printf("fd:%d\n", fd); //输出一下fd看为多少
//
while (1)
{
cnt = read(fd, buf, BUFSIZE); //获取读取的字节个数,并将读取的内容放入缓冲区
if (cnt == -1)
{
perror("read()"); //如果读取的字节个数为-1,提示错误
goto ERROR;
}
if (cnt == 0) //如果为读取的字节个数为 0 表示文件读完
break;
//将缓冲区的内容写入文件描述符为1的文件,即标准输出;(说白的就是打印到终端上)
write(1, buf, cnt);
}
// close
close(fd); //关闭文件
return 0;
ERROR:
close(fd);
return 1;
}
练习:
1.将文件 1 的内容拷贝到文件 2
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 10
int main(int argc, char **argv)
{
int rfd, wfd;
char buf[BUFSIZE] = {};
int cnt;
if (argc < 3)
{
write(2, "传参不够\n", strlen("传参不够\n")); //写入到标准错误输出
return 1;
}
//errno是一个全局变量,定义在errno.h中 大多数系统调用出错是会设置errno的值,
//根据不同的值得到相应的错误原因 perror()直接将相应错误原因打印输出
//strerror(errno)返回错误字符串
printf("%s\n", strerror(errno));
rfd = open(argv[1], O_RDONLY);
if (rfd == -1)
{
// printf("open():%s\n", strerror(errno));
perror("open()"); // 输出到标准错误输出
return 1;
}
wfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (wfd == -1)
{
// printf("open():%s\n", strerror(errno));
perror("open()");
goto ERROR1;
}
while (1)
{
cnt = read(rfd, buf, BUFSIZE);
if (-1 == cnt)
{
perror("read()");
goto ERROR2;
}
if (0 == cnt)
{
// rfd结束标志
break;
}
write(wfd, buf, cnt);
}
close(rfd);
close(wfd);
return 0;
ERROR2:
close(wfd);
ERROR1:
close(rfd);
return 1;
}
lseek(2) 改变文件偏移量
头文件:
#include
#include
函数声明:
off_t lseek(int fd, off_t offset, int whence);
lseek(2)参数含义:
//文件描述符; 偏移量,传整形,可以是负的; 从哪偏移,可选项如下:
int whence可选项:
SEEK_SET
The offset is set to offset bytes. 将文件偏移量指向文件开头,即从头开始偏移SEEK_CUR
The offset is set to its current location plus offset bytes. 从当前文件指针开始偏移SEEK_END
The offset is set to the size of the file plus offset bytes. 从文件末尾开始偏移
函数返回值:
//成功完成后,lseek()将返回从文件开头以字节为单位的结果偏移位置。
//出错时,返回值(off_t)-1并设置errno以指示错误。
关于 errno 值请查看man errno
空洞文件
lseek()函数允许将文件偏移量设置为超出文件末尾(但这不会改变文件的大小)。 如果稍后写入数据,则后续读取间隙中的数据(“空洞”)将设置为空字节('\ 0'),直到数据实际写入。
即将文件指针设置到文件末尾(SEEK_END),偏移量设置正整数,偏移后写入数据,中间的缝隙会写入(‘\ 0’),并且有实际大小,请看下方示例
用法示例
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
char buf[10] = {};
int cnt;
if (argc < 2)
return 1;
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
perror("open()");
return 1;
}
read(fd, buf, 2);
puts(buf);
cnt = lseek(fd, 5, SEEK_CUR); // current+5
printf("cnt:%d\n", cnt);
read(fd, buf, 2);
puts(buf);
lseek(fd, 5, SEEK_SET); // pos = 5
read(fd, buf, 2);
puts(buf);
lseek(fd, -5, SEEK_END); // pos = end-5
read(fd, buf, 2);
puts(buf);
// 空洞文件
if (lseek(fd, 100, SEEK_END) == (off_t)-1)
{
perror("lseek()");
return 1;
}
write(fd, "q", 1);
close(fd);
return 0;
}
练习
1.判读一个文件有多少行,以及字节个数
#include
#include
#include
#include
#include
#include
#define BUFSIZE 100
int main(int argc, char *argv[])
{
int fd;
int lines = 0; //行号
char buf[BUFSIZE] = {};
char *p = NULL;
int cnt;
if (argc < 2)
return 1;
fd = open(argv[1], O_RDONLY);
if (fd == -1)
{
perror("open()");
return 1;
}
while (1)
{
memset(buf, '\0', BUFSIZE);
cnt = read(fd, buf, BUFSIZE-1);
if (cnt == -1)
{
perror("read()");
goto ERROR;
}
if (cnt == 0)
break;
// 统计buf字符数组中有多少个'\n'
p = buf;
while (1)
{
p = strchr(p, '\n');
if (p == NULL)
break;
lines ++;
p ++;
}
}
printf("共%d行\n", lines);
//将文件指针指向文件末尾,偏移量为0,返回值即为文件字节个数
printf("此文件共%ld个字节\n", lseek(fd, 0, SEEK_END));
close(fd);
return 0;
ERROR:
close(fd);
return 1;
}
dup(2) / dup2(2) 复制文件描述符 实现文件重定向
头文件:
#include
函数声明:
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup(2)
复制旧的文件描述符的文件表项给当前可用的最小文件描述符dup2(2)
复制旧的文件描述符的文件表项给新的文件描述符对于被复制的文件表项,拥有两个文件描述符,可以一起使用,拥有同一个文件指针
如果新的文件描述符通过 lseek(2) 修改文件偏移量,对于旧的描述符,偏移量也会改变。
dup(2)参数含义:
//旧的文件描述符
dup2(2)参数含义:
//旧的文件描述符;新的文件描述符
函数返回值:
//成功时,将返回新的描述符。
//出错时,返回-1,并设置errno。
用法示例
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
if (argc < 2)
return 1;
fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd == -1)
{
perror("open()");
return 1;
}
#if 0
close(1);
dup(fd); // 当前可用最小的文件描述符作为fd的复制
#endif
// 原子操作
// dup2(fd, 1);
printf("%d\n", fcntl(fd, F_DUPFD, 1));
printf("hello world\n");
fflush(NULL); // 刷新标准io的缓存区
// write(1, "hello world\n", 12);
close(fd);
return 0;
}
fcntl(2)
复制文件描述符,改变文件的状态,类似dup的功能