linux系统下文件和目录操作

系统调用说明

系统调用: 内核提供的函数,由操作系统实现并提供给外部应用程序的编程接口, 是应用程序同操作系统之间交互数据的桥梁

为了保证系统的安全性, manPage中的系统调用都是对系统调用的一次浅封装, 比如open对应的是sys_open…

系统调用和库函数的比较:预读入和缓输出

使用strace工具可以跟踪一个程序执行时所需的系统调用

如果规定逐字节的进行拷贝, 用库函数会比用系统调用快很多, 因为有预读入和缓输出机制:

预读入和缓输出机制

操作系统不会让用户逐字节的向磁盘上写数据, 实际上它维护了一个系统级缓冲, 只有当从用户空间过来的数据在该缓冲上写满时, 才会一次性将数据冲刷到Disk上

当使用系统调用的方法时, 要不断的在用户空间和内核空间进行来回切换, 这会消耗大量时间

而使用fputc(库函数)时, 在设计之初自己在用户空间维护了一个缓冲, 这样在用户空间把自己的缓冲写满, 再一次性写入内核缓冲(写入了内核缓冲就认为写到了磁盘上), 可见这样大大减少了在用户空间和内核空间来回切换的次数

read和write函数常被称为UnbufferedIO, 指无用户级缓冲区, 但不保证不使用内核缓冲区

文件及相关操作

文件描述符

文件描述符

  • PCB中有一个指针, 指向了该进程的文件描述符表, 每个表项都是一个键值对, 其中的value是指向文件结构体的指针, 其中的索引是fd, 操作系统暴露给用户的唯一操作文件的依据
  • 新打开的文件描述符一定是所有文件描述符表中可用的, 最小的那个文件描述符
  • 文件描述符最大1023, 说明一个进程最多能打开1024个文件

open

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode); //mode_t是一个8进制整型,指定文件权限,只有当参2指定了CREAT才有用

flag的参数:

  • O_RDONLY
  • O_WRONLY
  • O_RDWR
  • O_APPEND
  • O_CREATE
  • O_EXCL
  • O_TRUNC
  • O_NONBLOCK

成功返回文件描述符, 失败返回-1并设置errno;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc,char* argv[]){
int fd1=0;
int fd2=0;

fd1=open("./dirt.txt",O_RDONLY|O_CREAT|O_TRUNC,0644);
/*打开的文件不存在*/
fd2=open("./dirt2.txt",O_RDONLY);

printf("fd1=%d\n",fd1);
printf("fd2=%d,errno=%d:%s\n",fd2,errno,strerror(errno));
close(fd1);
close(fd2);
return 0;
}

创建文件权限时, 指定文件访问权限, 权限同时受umask影响:文件权限=mode&(~umask)

read和write

  • read:从文件中读数据到缓冲区
1
2
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t count);//count是缓冲区的大小;

成功返回实际读到的字节数, 返回0时意味着读到了文件末尾, 失败返回-1并设置errno

  • wirte:从缓冲区中读数据到文件
1
2
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count); //count是数据的大小(字节数);

成功返回实际写入的字节数, 失败返回-1, 并设置errno

read和write实现文件拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(int argc,char* argv[]){
char buf[1024];
int n=0;

int fd1=open(argv[1],O_RDONLY);
if(fd1==-1){
perror("open argv1 error");
exit(1);
}

int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd2==-1){
perror("open argv2 error");
exit(1);
}

while((n=read(fd1,buf,sizeof(buf)))!=0){
if(n<0){
perror("open argv2 error");
break;
}
write(fd2,buf,n);
}

close(fd1);
close(fd2);
return 0;

阻塞和非阻塞

阻塞:当进程调用一个阻塞的系统调用时,该进程被置于睡眠状态,这时内核调度其他进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包或者调用sleep指定的睡眠时间到了)它才可能继续运行。与睡眠状态相对的是运行状态。

正在被调度执行的进程:cpu处于该进程的上下文环境中,程序计数器中保存着该进程的指令地址,通用寄存器中保存着进程运算过程中的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。

常规读文件是不会阻塞的,不论读多少字节,read一定会在有限时间内返回。从终端或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于阻塞多长时间也是不确定的,如果一直没有数据到达就会一直阻塞。同样,写常规文件也是不会阻塞的,而向终端设备或网络写则不一定。

echo程序
1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc,char* argv[]){
int n=0;
char buf[10];

n=read(STDIN_FILENO,buf,sizeof(buf));
if(n==-1){
perror("read error");
exit(1);
}

write(STDOUT_FILENO,buf,n);
return 0;
}

当不敲入换行符时, read会一直阻塞等待用户输入

阻塞是设备文件, 网络文件的属性

非阻塞方式从tty中读数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int main(int argc,char* argv[]){
int fd=0;
char buf[10];
int n=0;

/*以非阻塞方式打开终端文件*/
fd=open("/dev/tty",O_RDONLY|O_NONBLOCK);
if(fd<0){
perror("open /dev/tty error");
exit(1);
}

tryagain:
n=read(fd,buf,sizeof(buf));
/*当read的返回值小于0*/
if(n<0){
/*errno不是EWOULDBLOCK,说明出现了其他问题*/
if(errno!=EWOULDBLOCK){
perror("read /dev/tty error");
exit(1);
}else{
/*errno是EWOULDBLOCK,说明读到为空,则打印提示信息,并再次尝试*/
write(STDOUT_FILENO,"try again\n",strlen("try again\n"));
sleep(2);
goto tryagain;
}
}

/*当read的返回值大于0,说明读到了数据,写到标准输出上*/
write(STDOUT_FILENO,buf,n);
close(fd);
return 0;

read函数返回-1, 并且errno=EAGAIN或EWOULDBLOCK, 说明不是read失败, 而是read在以非阻塞方式读一个设备文件网络文件, 而文件中无数据。

阻塞方式存在的问题也正是网络IO中select, poll和epoll函数存在的原因。

fcntl修改文件的属性

改变一个已经打开的文件的访问控制属性。

1
2
3
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

用fcntl改写上面的程序, 不用重新打开文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int main(int argc,char* argv[]){
int fd=0;
char buf[10];
int n=0;
int ret=0;
int flags=0;

/*获取原来的flags*/
flags=fcntl(STDIN_FILENO,F_GETFL);
if(flags==-1){
perror("fcntl error");
exit(1);
}

/*位或上新的属性*/
flags|=O_NONBLOCK;
/*将新的flags设置回去*/
ret=fcntl(STDIN_FILENO,F_SETFL,flags);
if(ret==-1){
perror("fcntl error");
exit(1);
}

/*与上面的相同*/
tryagain:
n=read(STDIN_FILENO,buf,sizeof(buf));
if(n<0){
if(errno!=EWOULDBLOCK){
perror("read /dev/tty error");
exit(1);
}else{
write(STDOUT_FILENO,"try again\n",strlen("try again\n"));
sleep(2);
goto tryagain;
}
}

write(STDOUT_FILENO,buf,n);
close(fd);
return 0;
}

文件的flags是一个位图, 每一位代表不同属性的真假值

lseek函数

文件偏移:每个打开的文件都记录着当前读写位置,打开文件时候写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移动多少个字节。例外:如果以O_APPEND方式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移动到新的文件末尾lseek和标准I/O库的fseek函数类似,可以移动当前读写位置(或偏移量)。

1
2
3
4
5
6
7
#include <sys/types.h>
#include <unistd.h>

int fseek(FILE *stream, long offset, int whence); //成功返回0,失败返回-1.特别:超出文件末尾位置返回0,往回超出文件头位置,返回-1

off_t lseek(int fd, off_t offset, int whence); //失败返回-1,成功:返回的值是较文件起始位置向后的偏移量
//特别:lseek允许超过文件结尾设置偏移量,未见会因此被拓展。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(int argc,char* argv[]){
int fd=0;
int n=0;
char msg[]="It's a lseek test\n";
char c;

fd=open("./lseek.txt",O_CREAT|O_RDWR,0644);
if(fd==-1){
perror("open error");
exit(1);
}

write(fd,msg,strlen(msg));

/*如果这里不进行lseek,由于读写共用同一个偏移位置,下面的读会从文件末尾开始读,读不到任何数据*/
lseek(fd,0,SEEK_SET);

while((n=read(fd,&c,1))){
if(n==-1){
perror("read error");
exit(1);
}
write(STDOUT_FILENO,&c,n);
}
close(fd);
return 0;
}

用lseek获取文件大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc,char* argv[]){
int fd=open(argv[1],O_RDWR);
if(fd==-1){
perror("open error");
exit(1);
}
/*从0开始向后偏移到结尾,返回值表示偏移量,即为文件大小*/
int size=lseek(fd,0,SEEK_END);
printf("The file's size:%d\n",size);

close(fd);
return 0;
}

使用lseek拓展文件大小: 要想使文件大小真正拓展, 必须引起IO操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc,char* argv[]){
int fd=open(argv[1],O_RDWR);
if(fd==-1){
perror("open error");
exit(1);
}
/*从文件的结束位置开始,向后偏移110,被填入的是文件空洞*/
int size=lseek(fd,110,SEEK_END);
printf("The file's size:%d\n",size);
/*然后写入一个空字符*/
write(fd,"\0",1);
close(fd);
return 0;
}

以HEX查看文件:od -tcx filename

也可以使用truncate拓展文件大小:

1
int ret=truncate("dict.cp",250);

目录操作

目录项和inode

文件存储的关键点:inode, denty,数据存储,文件系统。

inode: 本质为结构体,存储文件的属性信息。如:权限、大小、时间、用户、盘块位置…也叫做文件属性管理结构,大多数的inode都存储在磁盘上。

可以用stat命令,查看某个文件的inode信息:
stat example.txt

inode的大小:
inode会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令:

df -i

denty: 目录项,本质依然是结构体,重要成员变量:文件名,inode。文件内容(data)保存在磁盘块中。

增加文件的硬链接只是增加dentry, 指向相同的inode

同样, 删除硬链接也只是删除dentry, 要注意删除文件并不会让数据在磁盘消失, 只是OS丢失了inode, 磁盘只能覆盖, 不能擦除

stat函数

stat函数作用:获取文件属性(从inode中获取)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char* pathname, struct stat* statbuf);
//参数:
//path:文件路径;
//buf(传出参数)存放文件属性;
//返回值: 成功返回0, 失败返回-1并设置errno;

/*结构体信息*/
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */

/* Since Linux 2.6, the kernel supports nanosecond precision for the following timestamp fields.For the details before Linux 2.6, see NOTES. */

struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */

#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};

利用stat获取文件大小:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc,char* argv[]){
struct stat sbuf;
int ret=0;
ret=stat(argv[1],&sbuf);
if(ret==-1){
perror("stat error");
exit(1);
}
printf("file size:%ld\n",sbuf.st_size);
return 0;
}

使用宏函数获取文件属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc,char* argv[]){
struct stat sbuf;
int ret=0;
ret=stat(argv[1],&sbuf);
if(ret==-1){
perror("stat error");
exit(1);
}
/*宏函数一般返回布尔值*/
if(S_ISREG(sbuf.st_mode))
printf("It's a regular\n");
else if(S_ISDIR(sbuf.st_mode))
printf("It's a dir\n");
else if(S_ISFIFO(sbuf.st_mode))
printf("It's a pipe\n");
else if(S_ISLNK(sbuf.st_mode))
printf("It's a symbol");
/*and so on...*/
return 0;
}

lstat

ln -s makefile makefile.soft:创建软连接

mkfifo f1:创建管道文件

stat穿透: 当用stat获取软连接的文件属性时, 会穿透符号连接直接返回软连接指向的本尊的文件属性(vim,cat命令也有穿透作用)

解决方法: 换lstat函数

S_IFMT是一个文件类型掩码(文件类型那四位全1), st_mode与它位与后就可以提取出文件类型(后面的权限位被归零)

特殊权限位:包含三个二进制位。依次是:设置组ID位:setGID; 设置用户ID位setID; 黏住位sticky

黏住位: 早期计算机内存紧张,只有精要的常用的程序可以常驻物理内存,剩下的要暂存在磁盘中。**当内存不够的时候会将该部分程序存回磁盘,腾出内存空间。若文件设置了黏住位,即使在内存比较吃紧的情况下也不会将该文件回存到磁盘上。**现阶段操作系统的虚拟内存管理分页算法比较完善,该功能已经被废弃。

仍然可以对目录设置黏住位。被设置了该位的目录,其内部文件只有:

  • 超级管理员
  • 该目录所有者
  • 改文件的所有者

这三种用户有权限做删除、修改操作。其他用户可以读、创建,但不能随意删除。

link函数

可以为已经存在的文件创建目录项(硬链接)

ln makefile makefile.hard:为makefile创建硬连接

1
int link(const char *oldpath, const char *newpath);

使用link和unlink函数实现mv命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc,char* argv[]){
int ret=0;
ret=link(argv[1],argv[2]);
if(ret==-1){
perror("link error");
exit(1);
}

ret=unlink(argv[1]);
if(ret==-1){
perror("unlink error");
exit(1);
}
return 0;
}

Linux下的文件删除机制: 不断的将文件的st_nlink-1, 直到减到0为止. 无目录项对应的文件, 会被操作系统择机释放。因此删除文件, 从某种意义上来说只是让文件具备了被删除的条件

unlink函数的特征:清除文件时, 如果文件的硬连接计数减到了0, 没有dentry与之对应, 但该文件仍不会马上被释放掉. 要等到所有打开该文件的进程关闭该文件, 系统才会择机将文件释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main(int argc,char* argv[]){
int fd=0;
int ret=0;
char* p="test of unlink\n";
char* p2="after write something\n";

fd=open("temp.txt",O_RDWR|O_TRUNC|O_CREAT,0644);
if(fd<0)
perr_exit("open file error");

ret=write(fd,p,strlen(p));
if(ret==-1)
perr_exit("write error");

printf("hello,I'm printf\n");
ret=write(fd,p2,strlen(p2));
if(ret==-1)
perr_exit("write error");
printf("Entry key to continue\n");
/*程序在此阻塞等待用户输入*/
getchar();
close(fd);
/*删除该文件*/
ret=unlink("temp.txt");
if(ret==-1)
perr_exit("unlink error");
return 0;
}

如果在unlink之前诱发段错误, 程序崩溃, temp.txt就会存活下来. 所以将unlink这一步放到打开文件之后紧接着就unlink掉

虽然文件被unlink掉了, 用户用cat查看不到磁盘上的对应文件, 但是write函数拿到fd写文件是向内核的buffer中写, 仍可正常写入

隐式回收:

进程运行结束时, 所有该进程打开的文件会被关闭, 申请的内存空间会被释放, 系统的这一特性称为隐式回收系统资源

文件目录权限

readlink m1.soft:查看软连接的内容

Linux下所见皆文件, 如果用vim打开一个目录,目录也是”文件”。文件内容是该目录下所有子文件的目录项dentry。

r w x
文件 文件内容可以被查看,cat、more、less… 内容可以被修改vi… 运行产生一个进程 ./文件名
目录 目录可以被浏览 创建、删除、修改文件mv, touch, mkdir… 可以被打开、进入 cd

目录操作函数

文件名不能超过255个字符, 引文dirent中的d_name长度为256, 再算上\0, 有255个字符可用。

1
2
3
4
5
6
7
8
9
10
11
12
#include <dirent.h>
DIR* opendir(const char* name); /*返回的是一个目录结构体指针*/
int closedir(DIR* dirp);

struct dirent* readdir(DIR* dirp);
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};

用目录操作函数实现ls的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int main(int argc,char* argv[]){
DIR* dp;
struct dirent* sdp;

/*根据输入的内容打开一个目录文件,拿到一个类似文件描述符的东西dp*/
dp=opendir(argv[1]);
if(dp==NULL)
perr_exit("opendir error");

/*循环从dirent流中读取数据*/
while((sdp=readdir(dp))!=NULL){
/*跳过当前目录和上一级目录*/
if(!strcmp(sdp->d_name,"."))
continue;
if(!strcmp(sdp->d_name,".."))
continue;
/*打印文件名*/
printf("%s\n",sdp->d_name);
}
printf("\n");

/*关闭文件*/
closedir(dp);
return 0;
}

递归遍历目录

思路:

  • 判断命令行参数, 获取用户要查询的目录名-argv[1]

    • 注意如果argc==1, 说明要查询的是当前目录./
  • 判断用户指定的是否是目录: stat S_ISDIR()->封装函数isFile()

  • 读目录:
1
2
3
4
5
6
7
opendir(dir);	
while(readdir()){
普通文件:直接打印;
目录文件:拼接目录访问绝对路径:sprintf(path,"%s%s",dir,d_name);
递归调用自己:opendir(path), readdir, closedir;
}
closedir();

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*参2是回调函数名*/
void fetchdir(const char* dir,void(*fcn)(char*)){
char name[PATH_LEN];
struct dirent* sdp;
DIR* dp;
/*打开目录失败*/
if((dp=opendir(dir))==NULL){
fprintf(stderr,"fetchdir:can't open %s\n",dir);
return;
}
/*循环读取内容*/
while((sdp=readdir(dp))!=NULL){
/*遇到当前目录和上一级目录,跳过,否则会陷入死循环*/
if((strcmp(sdp->d_name,".")==0)||(strcmp(sdp->d_name,"..")==0))
continue;
/*路径名是否越界*/
if(strlen(dir)+strlen(sdp->d_name)+2>sizeof(name)){
fprintf(stderr,"fetchdir:name %s %s is too long\n",dir,sdp->d_name);
}else{
/*拼接为一个路径,传给isFile函数*/
sprintf(name,"%s/%s",dir,sdp->d_name);
(*fcn)(name);
}
}
closedir(dp);
}

void isFile(char* name){
struct stat sbuf;
/*获取文件属性失败*/
if(stat(name,&sbuf)==-1){
fprintf(stderr,"isFile:can't access %s\n",name);
exit(1);
}
/*这是一个目录文件:调用函数fetchdir*/
if((sbuf.st_mode&S_IFMT)==S_IFDIR){
fetchdir(name,isFile);
}
/*不是目录文件:是一个普通文件,打印文件信息*/
printf("%ld\t\t%s\n",sbuf.st_size,name);
}

int main(int argc,char* argv[]){
/*不指定命令行参数*/
if(argc==1)
isFile(".");
else{
while(--argc>0)
isFile(*++argv);
}
return 0;
}

dup和dup2

cat makefile > m1:将cat的结果重定向到m1(此时m1与makefile内容相同)

cat makefile >> m1:将cat的结果重定向并追加到m1后面(此时m1是双份的makefile)

1
2
3
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.

传入已有的文件描述符, 返回一个新的文件描述符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main(int argc,char* argv[]){
/*open或创建一个文件,拿到文件描述符fd1*/
int fd1=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd1==-1){
perror("open error");
exit(1);
}
/*fd2作为fd1的副本,拿着fd2也可以向被open的文件写入*/
int fd2=dup(fd1);
if(fd2==-1){
perror("dup error");
exit(1);
}
printf("fd1=%d fd2=%d\n",fd1,fd2);
/*向fd2(fd1)中写入一句话*/
write(fd2,"love you\n",8);
return 0;
}

dup的返回值fd2相当于fd1的副本, 拿着它也可以操作fd1

dup2:后面的指向前面的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc,char* argv[]){

int fd1=open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0644);

int fd2=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0644);
/*dup2后fd2也指向了fd1的文件*/
int fdret=dup2(fd1,fd2);
printf("fdret=%d\n",fdret);

int ret=write(fd2,"love you\n",9);
printf("ret=%d\n",ret);
/*现在标准输出也指向了fd1*/
dup2(fd1,STDOUT_FILENO);
printf("--------love you--------\n");

return 0;
}

fcntl实现dup描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc,char* argv[]){
int fd1=open(argv[1],O_RDWR|O_CREAT,0644);
printf("fd1=%d\n",fd1);

int newfd1=fcntl(fd1,F_DUPFD,0);
printf("newfd1=%d\n",newfd1);

int newfd2=fcntl(fd1,F_DUPFD,8);
printf("newfd2=%d\n",newfd2);

int ret=write(newfd2,"fuckyou\n",8);
printf("ret=%d\n",ret);

return 0;
}
//fcntl 参3传0, 则从0开始向下寻找可用的文件描述符返回给newfd1
//fcntl 参3传8, 则从8开始向下寻找可用的文件描述符返回给newfd2