文件IO
根据牛客Linux高并发服务器的PDF
文章目录
标准C库IO函数
- 标准C库函数相较于Linux IO函数,具有跨平台的优势,可以通过在不同的平台调用不同的API来实现相同的操作
- 标准C库IO函数与Linux系统IO函数是调用与被调用的关系
- 标准C库IO函数比Linux系统IO函数效率高(缓冲区的功劳)
- 一般网络通信用Linux系统IO函数,对磁盘读写的时候用标准C库的IO函数
通过标准C库IO函数fopen来打开文件,会返回一个FILE类型的指针,FILE结构体包括文件读写指针(用来操控文件数据),文件描述符(用来定位文件)和IO缓冲区(用来提升执行效率)
标准C库IO函数的核心在于缓冲区,如果直接用Linux系统内核的read和write函数,每次读写都要重新访问一次磁盘,访问磁盘需要花费很多时间,IO的缓冲区很大程度减少了对磁盘的访问次数,提高了read和write函数的使用效率。
虚拟地址空间
虚拟地址空间是不存在的,但是可以用来解释一些程序加载时的内存问题,以及程序当中堆和栈的一些概念
虚拟地址空间里面的数据会被CPU中的MMU映射到真实物理内存中
文件描述符
程序占用磁盘空间不占用内存空间,但是进程是占用。
程序运行起来的时候操作系统会为其分配资源,创建一个进程。
文件描述符可以帮助我们那么如何定位到文件
文件描述符位位于虚拟地址空间的内核区的PCB进程控制块
文件描述符表就是一个数组,存储了很多文件描述符,这样进程可以同时打开多个文件。这个数组的大小默认为1024,所以最多同时打开文件个数为1024。(32位系统,4G虚拟地址空间情况下)
Linux下一切皆文件,其中标准输出、输入、错误指向的当前终端就是一个设备文件。
不同的文件描述符可以对应同一个文件,当一个文件描述符被释放掉之后,其他的文件会优先使用最小的未被占用的文件描述符。
Linux系统IO函数
可通过man 2
来查询Linux系统IO函数,不同page存储不同手册
open
open有两个函数,但是这并不是函数重载。mode_t mode
是一个可选参数
前者主要用于打开已有文件
后者主要用于创建一个新的文件,因为是创建新文件,所以我们还要设置各个用户组对这个文件的权限
如果发生错误,我们可以使用perro函数打印发生的错误
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
参数:
- pathname:要打开的文件路径
- flags:对文件的操作权限设置还有其他的设置
O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
返回值:返回一个新的文件描述符,如果调用失败,返回-1
errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。
#include <stdio.h>
void perror(const char *s);作用:打印errno对应的错误描述
s参数:用户描述,比如open,最终输出的内容是 open:xxx(实际的错误描述)
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
// 打开一个文件
int fd = open("a.txt", O_RDONLY);
if(fd == -1) {
perror("open");
}
// 读写操作
// 关闭
close(fd);
return 0;
}
相较上一个函数,这个open多了个mode_t mode
参数用来设置文件权限,mode_t mode
为3位8进制来表示不同用户/用户组对这个文件的权限
我们使用ll
查看目录下方文件信息的时候,前面有一串字母,lrwxrwxrwx
中第一个字母表示文件类型,后面每三个一组表示不同用户/用户组对该文件的权限,依次为:当前用户,当前用户组,其他组
r
读权限
w
写权限
x
执行权限
对于单个用户组,我们可以用三位二进制表示权限
例如:rwx对应二进制111,我们可以采用8进制表示,也就是7,那么对于三个用户组:rwxrwxrwx,对应的8进制数就是777
但是我们直接设置权限可能发生不合理的情况,因此再传入mode参数后,还需要进一步处理,最终的值为mode & ~umask
umask用于抹去一些不合理的权限设置,我们可以直接输入umask查看当前用户的umask
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
参数:
- pathname:要创建的文件的路径
- flags:对文件的操作权限和其他的设置
- 必选项:O_RDONLY, O_WRONLY, O_RDWR 这三个之间是互斥的
- 可选项:O_CREAT 文件不存在,创建新文件
- mode:八进制的数,表示创建出的新的文件的操作权限,比如:0775
最终的权限是:mode & ~umask
umask的作用就是抹去某些权限。
flags参数是一个int类型的数据,占4个字节,32位。
flags 32个位,每一位就是一个标志位,通过按位或的方式
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// 创建一个新的文件
int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
if(fd == -1) {
perror("open");
}
// 关闭
close(fd);
return 0;
}
read & write
代码如下(示例):
/*
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:需要读取数据存放的地方,数组的地址(传出参数)
- count:指定的数组的大小
返回值:
- 成功:
>0: 返回实际的读取到的字节数
=0:文件已经读取完了
- 失败:-1 ,并且设置errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:要往磁盘写入的数据,数组
- count:要写的数据的实际的大小
返回值:
成功:实际写入的字节数
失败:返回-1,并设置errno
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
// 1.通过open打开english.txt文件
int srcfd = open("english.txt", O_RDONLY);
if(srcfd == -1) {
perror("open");
return -1;
}
// 2.创建一个新的文件(拷贝文件)
int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
if(destfd == -1) {
perror("open");
return -1;
}
// 3.频繁的读写操作
char buf[1024] = {0};
int len = 0;
while((len = read(srcfd, buf, sizeof(buf))) > 0) {
write(destfd, buf, len);
}
// 4.关闭文件
close(destfd);
close(srcfd);
return 0;
}
lseek
/*
标准c库函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
Linux系统函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
fd:文件描述符
offset:偏移量
whence:
SEEK_SET:设置文件指针的偏移量
SEEK_CUR:设置偏移量 = 当前位置 + 第二个参数offset的值
SEEK_END:设置偏移量 = 文件大小 + 第二个参数offset的值
返回值:返回文件指针的位置
作用:
1. 移动文件指针到文件头
lseek(fd, 0, SEEK_SET);
2. 获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
3. 获取文件长度
lseek(fd, 0, SEEK_END);
4. 拓展文件的长度,当前文件10b,可以通过该函数增加100b
lseek(fd, 100, SEEK_END);
注意:需要写一次数据
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd = open("hello.txt", O_RDWR);
if (fd == -1) perror("open:");
// 扩展文件的长度
int ret = lseek(fd, 100, SEEK_END);
if(ret == -1) perror("lseek");
// 写入一个空数据
write(fd, " ", 1);
// 关闭文件
close(fd);
return 0;
}
注意:需要写一次数据
扩展文件长度的作用在于
假设我们需要下载5G的资料,但是同时我们还要使用磁盘,这有可能造成下到一半磁盘不够了。这时候我们可以lseek实现扩展出5G的文件,然后往扩展出来的文件当中写入数据
stat & lstat
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
pathname:操作文件的路径
statbuf:结构体变量(产出参数)用于保存获取到的文件的信息
返回值:
成功:返回0;
失败:返回-1 ,并设置errno
int lstat(const char *pathname, struct stat *statbuf);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
struct stat statbuf;
int res = stat("a.txt", &statbuf);
if (res == -1) perror("stat:");
printf("size: %ld\n", statbuf.st_size);
return 0;
}
在Linux中可以通过stat
命令来查看文件的相关信息
其中stat结构体变量参数有
struct stat {
dev_t st_dev; // 文件的设备编号
ino_t st_ino; // 节点
mode_t st_mode; // 文件的类型和存取的权限
nlink_t st_nlink; // 连到该文件的硬连接数目
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 设备文件的设备编号
off_t st_size; // 文件字节数(文件大小)
blksize_t st_blksize; // 块大小
blkcnt_t st_blocks; // 块数
time_t st_atime; // 最后一次访问时间
time_t st_mtime; // 最后一次修改时间
time_t st_ctime; // 最后一次改变时间(指属性)
};
其中st_mode
的信息存储方式如下
如果要判断文件的权限,可通过按位与的操作;
如果想要查看文件的类型,要先和掩码按位与的操作(st_mode & S_IFMT)==S_IFREG
,再和八进制宏指进行判断。
stat和lstat的区别是,如果b.txt链接到a.txt那么stat获取的是a.txt的信息,而lstat获取的是b.txt的信息,也就是链接的信息
模拟实现ls -l命令
使用ls -l
命令返回以下信息
- 文件类型
- 文件权限
- 连接数
- 文件所属用户
- 文件所属组
- 文件大小
- 文件上次修改时间
- 文件名
判断传入参数是否正确
int main(int argc, char* argv[]) {
//判断输入的参数是否正确
if (argc < 2) {
printf("%s filename\n", argv[0]);
return -1;
}
}
第一个参数,int型的argc,为整型,用来统计程序运行时发送给main函数的命令行参数的个数,在VS中默认值为1。
第二个参数,char*型的argv[],为字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:
argv[0]指向程序运行的全路径名
argv[1]指向在DOS命令行中执行程序名后的第一个字符串
argv[2]指向执行程序名后的第二个字符串
argv[3]指向执行程序名后的第三个字符串
argv[argc]为NULL
通过stat函数获取文件信息
//通过stat获取用户传入的文件信息
struct stat st;
int res = stat(argv[1], &st);
if (res == -1) {
perror("stat");
return -1;
}
获取文件类型和文件权限
根据st_mode
变量判断文件类型和权限
文件类型:st_mode & S_IFMT
后与各个宏比较
文件权限:st_mode & S_IRUSR
(这里以文件所有者读权限为例),直接看和宏按位与后的真假,若为真就有权限,假则无权限
//获取文件类型和文件权限
char perms[11] = {0}; //用于保存文件类型和文件权限的字符串
switch (st.st_mode & __S_IFMT)
{
case __S_IFLNK:
perms[0] = 'l';
break;
case __S_IFDIR:
perms[0] = 'd';
break;
case __S_IFREG:
perms[0] = '-';
break;
case __S_IFBLK:
perms[0] = 'b';
break;
case __S_IFCHR:
perms[0] = 'c';
break;
case __S_IFSOCK:
perms[0] = 's';
break;
case __S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
//判断文件访问权限
//文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
//文件所在组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
//其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
硬连接数&文件大小
//硬连接数
int linkNum = st.st_nlink;
//文件大小
off_t fileSize = st.st_size;
文件拥有者 & 所属用户组
st_uid
获取用户ID
st_gid
获取组ID
但是实际上的ls -l
命令显示的用户名和组名,所以我们要使用getpwuid
和getgrgid
两个函数把ID转换为名字
可以使用man手册来查询这两个函数的用法和所需的头文件
//文件所有者
char* fileUsr = getpwuid(st.st_uid)->pw_name;
//文件所在组
char* fileGrp = getgrgid(st.st_gid)->gr_name;
文件上次修改时间
st_mtime
获取文件上次修改时间距离1970年开始的秒数
我们可以使用ctime函数转换成世纪时间,需要注意的是ctime得到的字符串自带末尾换行,需要处理一下
//文件修改时间
char* time = ctime(&st.st_mtime);
char mtime[512] = {0};
strncpy(mtime, time, strlen(time) - 1);
打印字符串
使用sprintf函数来连接字符串
由于 sprintf 跟 printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出
sprintf语法:
int sprintf(char *string, char *format [,argument,…]);
string:指向字符数组的指针,该数组存储了C字符串。
format:格式化的字符串
argument:根据语法格式替换format中%标签
char buf[1024];
sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUsr, fileGrp, fileSize, mtime, argv[1]);
printf("%s\n", buf);
运行结果为
文件属性操作函数
access
/*
#include <unistd.h>
int access(const char *pathname, int mode);
作用:
判断某个文件是否有某个权限,或者判断文件是否存在
参数:
pathname:判断的文件路径
mode:
R_OK:判断是否有读权限
W_OK:判断是否有写权限
X_OK:判断是否有执行权限
F_OK:判断文件是否存在
返回值:
成功返回0,失败返回-1
*/
#include <unistd.h>
#include <stdio.h>
int main() {
int res = access("a.txt", F_OK);
if (res == -1)
perror("access");
printf("文件存在!\n");
return 0;
}
chown
/*
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
作用:
修改文件的权限
参数:
pathname:需要修改文件的路径
mode:需要修改的权限值,八进制数
返回值:
成功返回0,失败返回-1
*/
#include <stdio.h>
#include <sys/stat.h>
int main() {
int res = chmod("a.txt", 0664);
if (res == -1) perror("chmod");
return 0;
}
chown
使用vim /etc/passwd
,vim /etc/group
命令可以查询用户和组的id
chown也就是change owner改变拥有者和用户组
int chown(const char *pathname, uid_t owner, gid_t group);
truncate
/*
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:
缩减或者拓展文件的尺寸到指定的大小
参数:
path;需要修改的文件的路径
length:需要最终文件变成的大小
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
int res = truncate("a.txt", 5);
if (res == -1) perror("truncate");
return 0;
}
a.txt文件大小由12变为5
目录操作函数
mkdir
/*
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
pathname: 创建的目录的路径
mode: 权限,八进制的数
返回值:
成功返回0, 失败返回-1
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
int ret = mkdir("aaa", 0777);
if(ret == -1) {
perror("mkdir");
return -1;
}
return 0;
}
由上图可知生成了一个“aaa”的目录文件,权限为0775,并非为函数设置的0777(不要漏掉0,不然默认10进制
),那是因为最终权限为mode & ~umask
,一个目录一定要有可执行权限才能进入目录。
rmdir
//删除空目录
int rmdir(const char *pathname);
rename
/*
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
更改目录名
*/
#include <stdio.h>
int main() {
int ret = rename("aaa", "bbb");
if(ret == -1) {
perror("rename");
return -1;
}
return 0;
}
chdir & getcwd
chdir
修改进程的工作目录,类似shell当中的cd
getcwd
:类似shell中的pwd
,获取当前的工作目录
/*
#include <unistd.h>
int chdir(const char *path);
作用:
修改进程的工作目录
比如在/home/zhou/启动了一个可执行程序a.out,进程的工作目录就是/home/zhou/
参数:
path:需要修改的工作目录
返回值:
成功返回0,失败返回-1
#include <unistd.h>
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
buf:存储的路径,指向的是一个数组(传出参数)
size:数组的大小
返回值:
返回的指向一块内存,这个数据就是第一个参数
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
//获取当前工作目录
char buf[128];
getcwd(buf, sizeof(buf));
printf("当前工作目录为:%s\n", buf);
//修改工作目录
int res = chdir("/home/zhou/Linux/lesson13");
if (res == -1) perror("getcwd");
//创建一个新的文件
int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
if (fd == -1) perror("open");
close(fd);
//获取当前工作目录
char buf1[128];
getcwd(buf1, sizeof(buf));
printf("当前工作目录为:%s\n", buf1);
return 0;
}
目录遍历函数
万物皆文件,目录也可以看作是一个文件
对于一个目录我们也有打开,读取,关闭的相关函数,他们分别为
opendir 打开目录
readdir 读取目录
closedir 关闭目录
其中readdir返回的是一个struct dirent 类型的结构体
统计目录下普通文件的个数
首先我们需要先统计输入参数是否正确
由于一个目录下还会有另一个目录,所以需要递归的调用
然后我们要一个个统计文件,所以我们函数里要有循环,那么这个函数的基本结构就是下面这样的
首先打开目录
进入循环,循环条件为是否读取到末尾
循环内部一个判断分支
如果是目录,就递归这个函数
如果是文件,计数器加1
最后关闭目录
返回计数器
/*
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
作用:打开一个目录
参数:需要打开的目录的名称
返回值:DIR*的类型(目录流)
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
作用:读取目录里的数据
参数:通过opendir返回的值
返回值:struct dirent代表读到的文件的信息,读到末尾或者失败会返回NULL
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
作用:关闭目录
*/
#define _DEFAULT_SOURCE
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getFileNum(const char* path);
int main(int argc, char* argv[]) {
if(argc < 2) {
printf("%s path\n", argv[0]);
return -1;
}
int Num = getFileNum(argv[1]);
printf("普通文件的个数为:%d\n", Num);
return 0;
}
//用于获取目录下所有普通文件的个数
int getFileNum(const char* path) {
//1. 打开目录
DIR* dir = opendir(path);
if (dir == NULL) {
perror("opendir");
exit(0);
}
struct dirent* ptr;
int total = 0;
while ((ptr = readdir(dir)) != NULL) {
//获取名称
char* dname = ptr->d_name;
if (strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
continue;
}
//判断是普通文件还是目录
if (ptr->d_type == DT_DIR) {
//递归的读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname);
total += getFileNum(newpath);
}
if (ptr->d_type == DT_REG) {
total++;
}
}
//关闭文件
closedir(dir);
return total;
}
文件描述符相关函数
dup
/*
#include <unistd.h>
int dup(int oldfd);
作用:复制一个新的文件描述符
fd = 3; int fd1 = dup(fd);
此时fd和fd1指向的是同一个文件,并且是从空闲的文件描述符中找一个最小的且未被使用过的作为新的文件描述符
*/
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main() {
int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
if (fd == -1) perror("open");
int fd1 = dup(fd);
printf("fd = %d, fd1 = %d\n", fd, fd1);
close(fd);
char* str = "Hello World";
int res = write(fd1, str, strlen(str));
if (res == -1) perror("write");
close(fd1);
return 0;
}
dup2
重定向文件描述符
两个文件a,b,两个文件描述符fd,fd1
原来fd指向a,fd1指向b
使用dup2之后可以吧fd1指向a,以后fd1读写操作就是对a文件的操作
/*
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
oldfd指向a.txt,newfd指向b.txt
调用函数成功后:newfd和b.txt做close(),newfd指向a.txt
oldfd必须是一个有效的文件描述符
oldfd和newfd值相同时,相当于什么都没有做
*/
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main() {
int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
if (fd == -1) perror("open");
int fd1 = open("b.txt", O_RDWR | O_CREAT, 0664);
if (fd1 == -1) perror("open");
printf("fd = %d, fd1 = %d\n", fd, fd1);
int fd2 = dup2(fd, fd1);//此时fd1指向a.txt
if (fd2 == -1) perror("dup2");
//通过fd1去写数据,实际操作的是a.txt人不是b.txt
char* str = "Hello Dup2";
int res = write(fd1, str, strlen(str));
if (res == -1) perror("write");
printf("fd = %d, fd1 = %d, fd2 = %d\n", fd, fd1, fd2);
close(fd);
close(fd1);
return 0;
}
fcntl
复制文件描述符
设置/获取文件的状态
/*
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... );
参数:
fd:需要操作的文件描述符
cmd:对文件描述符进行如何操作
F_DUPFD:复制文件描述符,复制的是第一个参数fd,返回一个新的文件描述符
int fd1 = fcntl(fd, F_DUPFD);
F_GETFl:获取指定文件描述符的文件状态flag
获取到的flag和通过open函数传递flag是一个东西
F_SETFL:设置文件描述符文件状态
必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
可选性:O_APPEND, O_NONBLOCK
- O_APPEND 表示追加数据
- NONBLOK 设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
阻塞: 阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回干不完不准回来
非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main() {
// 1.复制文件描述符
// int fd = open("1.txt", O_RDONLY);
// int ret = fcntl(fd, F_DUPFD);
// 2.修改或者获取文件状态flag
int fd = open("1.txt", O_RDWR);
if (fd == -1) perror("open");
//获取文件描述符状态flag
int flag = fcntl(fd, F_GETFL);
flag = flag | O_APPEND;
//修改文件描述符状态
fcntl(fd, F_SETFL, flag);
char* str = "nihao!";
write(fd, str, strlen(str));
close(fd);
return 0;
}