文章目录
  1. 1. open
  2. 2. read
  3. 3. write
  4. 4. creat
  5. 5. 验证&验证
  6. 6. 验证close(文件描述符的功能)
    1. 6.1. 存疑

大事件 超人毁灭

下面主要介绍文件IO操作的4个主要系统调用:open()、read()、write()、close()
Linux程序中可以像文件那样使用磁盘文件、串口、打印机。也就是说Linux任何事物都可以用一个文件来操作。

你只需要很少的函数就可以对文件和设备进行访问和控制。这些函数被称为系统调用
系统调用由UNIX(和Linux)直接提供,它们本身通向操作系统本身的接口

Linux系统各种函数与用户设备驱动内核硬件关系图

open

1
fd=open(pathname,flags,mode)

函数打开pathname所标识的文件,并返回文件描述符,用以在后续函数中指代打开的文件。

函数原型

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

int open(const char *pathname, int flags, .../*mode_t mode*/);

open()将返回一文件描述符,用于后续函数调用中指代该文件

若发生错误,则返回-1,并将errno置为相应的错误标志

  • flags标志位
标志位 用途
O_RDONLY 以只读方式打开文件
O_WRONLY 以只写方式打开文件
O_RDWR 以可读写方式打开文件.

上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合.

补充

|标志位|用途|
|:———- :|:——————-:|
|O_CREAT | 若文件不存在则创建 |

标志O_NOCTTY可以告诉UNIX这个程序不会成为这个端口上的“控制终端”。如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。而有些程序比如getty(1M/8)则会在打开登录进程的时候使用这个特性,但是通常情况下,用户程序不会使用这个行为.|

标志位 用途
O_TRUNC 截断已有文件,使其长度为零
O_DIRECT 无缓冲的输入/输出
O_NOCTTY 不要让pathname*(所指向的终端设备)*称为控制终端。
O_NDELAY 标志则是告诉UNIX,这个程序并不关心DCD信号线的状态——也就是不关心端口另一端是否已经连接。如果不指定这个标志的话,除非DCD信号线上有space电压否则这个程序会一直睡眠。
O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败.
O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失.
O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面.
O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中.
O_SYNC 以同步的方式打开文件.
O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败.
O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。注:此为Linux2. 2 以后特有的旗标, 以避免一些系统安全问题.

参数mode
当你使用O_CREAT标志的open调用来创建文件,你必须使用3个参数的格式的open调用。

第3个参数mode是几个标志按位或后得到的,这些标志在头文件sys/stat.h中定义

则有下列数种组合, 只有在建立新文件时才会生效, 此外真正建文件时的权限会受到umask 值所影响, 因此该文件权限应该为 (mode-umaks).

标志位 说明
S_IRWXU 00700 权限 代表该文件所有者具有可读、可写及可执行的权限
S_IRUSR 或S_IREAD, 00400 权限 代表该文件所有者具有可读取的权限
S_IWUSR 或S_IWRITE, 00200 权限 代表该文件所有者具有可写入的权限
S_IXUSR 或S_IEXEC, 00100 权限 代表该文件所有者具有可执行的权限
S_IRWXG 00070 权限 代表该文件用户组具有可读、可写及可执行的权限
S_IRGRP 00040 权限 代表该文件用户组具有可读的权限
S_IWGRP 00020 权限 代表该文件用户组具有可写入的权限
S_IXGRP 00010 权限 代表该文件用户组具有可执行的权限
S_IRWXO 00007 权限 代表其他用户具有可读、可写及可执行的权限
S_IROTH 00004 权限 代表其他用户具有可读的权限
S_IWOTH 00002 权限 代表其他用户具有可写入的权限
S_IXOTH 00001 权限 代表其他用户具有可执行的权限

返回值:若所有欲核查的权限都通过了检查则返回0 值, 表示成功, 只要有一个权限被禁止则返回-1.

实例程序

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
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(){
int fd;
char *leds = "/dev/leds";
char *test1 = "/bin/test1";
char *test2 = "/bin/test2";

if((fd = open(leds,O_RDWR|O_NOCTTY|O_NDELAY))<0){
printf("open %s failed!\n",leds);
}
printf("\n%s fd is %d\n",leds,fd);

if((fd = open(test1,O_RDWR,0777))<0){ /*文件可读可写*/
printf("open %s failed!\n",test1);
}
printf("%s fd is %d\n",test1,fd);

if((fd = open(test2,O_RDWR|O_CREAT,0777))<0){ /*如果文件不存在,则创建一个文件*/
printf("open %s failed!\n",test2);
}
printf("%s fd is %d\n",test2,fd);
}

错误代码:
|代码 |说明 | |
|:————-:|:——————:|
|EEXIST 参数pathname| 所指的文件已存在, 却使用了O_CREAT 和O_EXCL 旗标.|
|EACCESS 参数pathname |所指的文件不符合所要求测试的权限.|
|EROFS |欲测试写入权限的文件存在于只读文件系统内.|
|EFAULT 参数pathname| 指针超出可存取内存空间.|
|EINVAL 参数mode| 不正确.|
|ENAMETOOLONG 参数 pathname| 太长.|
|ENOTDIR 参数pathname| 不是目录.|
|ENOMEM |核心内存不足.|
|ELOOP 参数pathname |有过多符号连接问题.|
|EIO |I/O 存取错误.|


read

读取文件内容read(),调用从文件描述符fd所指代的打开文件中读取数据

函数原型

1
2
3
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

系统调用read:从与文件描述符fildes相关联的文件读入nbytes个字节的数据,并把他们放到数据去buff中。它返回实际读入的字节数,这可能会小于请求的字节数。

如果read返回0,就表示未读入任何数据,已达到文件尾部。同样,如果返回的是-1,就表示read调用出现错误。

UNIX和Linux中比较重要的设备文件有3个: /dev/console、/dev/tty和/dev/null

  1. /dev/console
    这个设备代表是系统控制台。

    错误信息通常会被发送到这个设备

  2. /dev/tty
    如果一个进程有控制终端的话,那么特殊文件/dev/tty就是这个控制终端*(键盘和显示屏,或键盘窗口)的别名(逻辑设备)*

  3. /dev/null
    /dev/null文件是空(null)设备。所有写向这个设备的输出都将被丢弃,而读这个设备会立刻返回一个文件标志.

    所以在cp命令里可以把它用做复制文件的源文件

目录
目录是用于保存其他文件的节点号和名字的文件。目录文件中的每个数据项都是指向某个文件节点的链接,删除文件名就等于删除与之对应的链接。

文件的节点号可以通过ls -i命令查看

你可以通过使用ln命令在不同的目录中创建指向同一个文件的链接。

incode编号

文件除了本身包含的内容以外,还包含“管理信息”,它包含文件的创建/修改日期和它的访问权限。这些属性被保存在文件的inode节点中,

它是文件系统中的一个特殊的数据块,它同时还包含文件的长度和文件在磁盘上的存放的位置。系统使用的文件的inode编号,目录结构为文件命名仅仅是为了方便人们使用。

write

原型

1
2
#include<unistd.h>
size_t write(int fildes,const void*buf,size_t nbytes);

系统调用write的作用是把缓冲区buf的前nbytes个字节写入与文件描述符fildes关联的文件中。它返回实际写入的字节数。如果文件描述符有错或者底层的驱动设备对数据块长度比价敏感,返回值可能会小于nbytes

fildes文件描述符*(即fd)*。

标准文件描述符

文件描述符 用途 POSIX名称 stdio流
0 标准输入 STDIN_FILENO stdin
1 标准输出 STDOUT_FILENO stdout
2 标准错误 STDERR_FILENO stderr

在程序中指代这些文件描述符时,可以使用数字(0、1、2)表示,或者采用<unistd.h>所定义的POSIX标准名称。

注意使用POSIX标准命名,一定要加头文件unistd.h如果使用数字则不一定要。

实例程序1.

1
2
3
4
5
6
7
8
9
10
#include<unistd.h>
#include<stdlib.h>

int main()
{
if((write(1,"Here is some data\n",18))!=18)
write(2,"A write error has occured on file descriptor 1\n",46);

exit(0);
}

实例程序2

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 <stdio.h>

//文件操作函数头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

main()
{
int fd;
char *testwrite = "/bin/testwrite";
ssize_t length_w;
char buffer_write[] = "Hello Write Function!";

if((fd = open(testwrite, O_RDWR|O_CREAT,0777))<0){
printf("open %s failed\n",testwrite);
}

//将buffer写入fd文件
length_w = write(fd,buffer_write,strlen(buffer_write));
if(length_w == -1)
{
perror("write");
}
else{
printf("Write Function OK!\n");
}
close(fd);
}

creat

打开文件creat函数

1
int creat(const char *pathname, mode_t mode);`

验证&验证

验证结论 执行close(1);后面指令将无法正常执行。

1 表示标准输出

验证close(文件描述符的功能)

实例程序1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd=0;
char *file="/bin/testwrite";
fd=close(0);
printf("./colose(0)\t fd=%d\n",fd);
printf("./colose(0)_double\t fd=%d\n",fd);
fd=close(1);
printf("./close(1)\t fd=%d\n",fd);
fd=close(3);
printf("./close(3)\t fd=%d\n",fd);
fd=open(file,O_CREAT);
printf("%d\n",fd);
return 0;
}

运行结果

1
2
3
4
5
root@ACER:/home/takethat/workspace/linux_program/io/open# gcc open.c -o run_open
root@ACER:/home/takethat/workspace/linux_program/io/open# ./run_open
./colose(0) fd=0
./colose(0)_double fd=0
root@ACER:/home/takethat/workspace/linux_program/io/open#

实例程序2.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd=close(1);
printf("fd is %d\n",fd);
printf("hello,close(1)\n");
return 0;

}

该程序执行后无任何输出。

程序执行完 fd=close(1); 就不再执行了。

但程序执行close(0); 、 close(2);程序则正常运行

存疑

open返回文件描述符3是什么?

文件描述符不是只有0、1、2吗?

是不是跟函数返回类型定义有关?因为

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);

write函数定义

1
2
#include<unistd.h>
size_t write(int fildes,const void*buf,size_t nbytes);

实例程序:fd返回3

声明/home/takethat/workspace/linux_program/io/open/file 这个文件是存在的

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdlib.h>
#include<stdio.h>

int main(void)
{
int fd;
fd=open("/home/takethat/workspace/linux_program/io/open/file",O_RDONLY);
printf("fd is %d\n\n",fd);
}

执行结果

1
2
3
root@ACER:/home/takethat/workspace/linux_program/io/open# gcc fd_open0.c -o run_fd_open0
root@ACER:/home/takethat/workspace/linux_program/io/open# ./run_fd_open0
fd is 3

fd返回3

插曲:倘若用在该代码预先用close(0);关闭标准输入,open返回0

fd_open.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdlib.h>
#include<stdio.h>

int main(void)
{
int fd,fd0;
fd0=close(STDIN_FILENO);/*close file descriptor 0*/
printf("fd0 is %d\n",fd0);

fd=open("/home/takethat/workspace/linux_program/io/open/file",O_RDONLY);
printf("fd is %d\n\n",fd);
}

运行结果

1
2
fd0 is 0
fd is 0

若把 程序fd_open.c

1
fd0=close(STDIN_FILENO);/*close file descriptor 0*/

换成

1
fd0=close(2);/*close file descriptor 2*/

程序运行则是:

1
2
fd0 is 0
fd is 2

这是因为0被关闭。则0是未被占用的。然后被open使用,所以输出是2

SUSV3固定,如果调用open()成功,必须保证其返回值为进程未使用文件描述符数值最小者。

文章目录
  1. 1. open
  2. 2. read
  3. 3. write
  4. 4. creat
  5. 5. 验证&验证
  6. 6. 验证close(文件描述符的功能)
    1. 6.1. 存疑