Linux编程中的的IO函数以及相关概念

1 文件描述符表

sched.h 是一个与操作系统调度相关的头文件。
你可以使用 locate sched.h命令来查找这个文件的位置。
这个头文件里有很多有意思的东西。就比如文件描述符表

在结构体 PCB 的成员变量有这么一个变量
查看PCB结构体,里面有这么一个成员变量file_struct *file

struct task_struct {
/* Open file information: */
struct files_struct *files;
}

1.1 文件描述符

还记得Linux的哲学吗?万物皆文件。Linux上的万物皆是文件,屏幕,键盘,socket,都是文件。Linux通过一个名叫文件描述符表的结构来管理当前进程的所有文件。
文件描述表中维护了一个线性数组,文件描述符的本质就是一个整型的数,内核通过某种机制将这个整数和具体的文件关联起来了。

1.2 初窥文件描述符

我们可以通过一个小的案例来直观的看到文件描述符。

  1. 我们先准备一个简单的程序。一个死循环程序。
    int main(){
    while(1){}
    return 0;
    }
  2. 编译并运行该程序,此时这个程序就会一直挂在后台
    gcc main.c
    ./a.out
  3. 查找当前进程的PID 145424
    ubuntu@ubuntu:~/sys_code/io$ ps -aux | grep a.out
    ubuntu 145424 100 0.0 2644 1024 pts/1 R+ 21:16 10:42 ./a.out
  4. 查看/proc/145424该进程的目录,会在里面看到一个名为fd的文件夹。
    ubuntu@ubuntu:~/sys_code/io$ cd /proc/145424
    ubuntu@ubuntu:/proc/145424$ ls
    cgroup fd maps oom_score_adj smaps timerslack_ns
    ubuntu@ubuntu:/proc/145424$
  5. 查看fd里的内容。里面有三个软连接,0 1 2,都指向了/dev/pts/1
    ubuntu@ubuntu:/proc/145424/fd$ ll
    总计 0
    dr-x------ 2 ubuntu ubuntu 3 9月 10 21:17 ./
    dr-xr-xr-x 9 ubuntu ubuntu 0 9月 10 21:16 ../
    lrwx------ 1 ubuntu ubuntu 64 9月 10 21:17 0 -> /dev/pts/1
    lrwx------ 1 ubuntu ubuntu 64 9月 10 21:17 1 -> /dev/pts/1
    lrwx------ 1 ubuntu ubuntu 64 9月 10 21:17 2 -> /dev/pts/1

/dev/pts/1 是一个特殊的文件路径,它代表的是伪终端设备(Pseudo-Terminal Master)的一个实例。
而这里的0,1,2分别就是C语言里的 标准输入,标准输出,标准错误输出

上面的程序里啥也没有,就一个死循环,所以可以确定的是,这三个东西绝对不是我们弄出来的,那我们有没有办法增加点东西呢?

我们修改一下程序,按照上面的步骤再看一下fd文件下会有什么。

需要再同目录下准备a.txt文件,里面可以啥也不写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(){
int fd = open("a.txt",O_WRONLY);//这行代码的作用就是以只写的方式打开a.txt
printf("fd is:%d\n",fd); // 打印打开文件后的返回值
while(1){}
return 0;
}

运行程序,打印出了3

ubuntu@ubuntu:~/sys_code/io$ ./a.out
fd is:3

查看fd,里面多了一个名为3的软连接指向我们打开的文件。

ubuntu@ubuntu:/proc/145635/fd$ ll
总计 0
dr-x------ 2 ubuntu ubuntu 4 9月 10 21:41 ./
dr-xr-xr-x 9 ubuntu ubuntu 0 9月 10 21:41 ../
lrwx------ 1 ubuntu ubuntu 64 9月 10 21:41 0 -> /dev/pts/1
lrwx------ 1 ubuntu ubuntu 64 9月 10 21:41 1 -> /dev/pts/1
lrwx------ 1 ubuntu ubuntu 64 9月 10 21:41 2 -> /dev/pts/1
l-wx------ 1 ubuntu ubuntu 64 9月 10 21:41 3 -> /home/ubuntu/sys_code/io/a.txt

1.3 文件描述符表的上限

文件描述符表是有默认上限的,这意味着我们不能无休止的打开文件。使用ulimit -a命令可以看到open files的最大值是1024。可以通过ulimit -n 4096 修改。但默认的一般就够了。

ubuntu@ubuntu:~$  ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 61970
max locked memory (kbytes, -l) 1992780
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 61970
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

2 文件

还是哲学万物皆文件,那这里的文件在Linux上是怎么描述的呢?
Linux使用了一个结构体FILE来描述,这里面的内容很多,我们只关注一下几个

  • fmode_t f_mode; 文件访问权限
  • unsigned int f_flags; 文件打开标志
  • struct address_space *f_mapping; 文件内核缓冲区的首地址
  • struct file_operations *f_op; 文件内容操作函数指针
  • struct inode_operations 文件属性操作函数指针

    在 /usr/src/linux-headers-6.5.0-18-generic/include/linux/fs.h 文件里

3 系统编程提供的IO

3.1 open/close

3.1.1 opan

通过man手册第2卷可以看到open函数的形参,返回值类型,所属头文件。
pathname就是文件名称,可以写绝对路径和相对路径。
flags:

NAME
open, openat, creat - open and possibly create a file
SYNOPSIS
#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);
flags:
O_RDONLY: 只读方式打开文件。
O_WRONLY: 只写方式打开文件。
O_RDWR: 读写方式打开文件。
O_CREAT: 如果文件不存在,则创建该文件。
O_EXCL: 当与O_CREAT一起使用时,如果文件已存在,则打开失败。
O_TRUNC: 打开文件后清空文件内容。如果文件是只读打开,则忽略此标志。
更多详见man

示例代码

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>

int main(void) {
int fd;
// 打开或创建一个名为"example.txt"的文件,以读写方式打开,如果不存在则创建
fd = open("example.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("Error opening file");
return 1;
}
// 文件打开成功,可以进行读写操作
printf("File opened successfully, file descriptor is %d\n", fd);

// 关闭文件描述符
close(fd);
return 0;
}

3.1.2 close

关闭文件

#include <unistd.h>
int close(int fd);
RETURN VALUE
close() returns zero on success. On error, -1 is returned, and errno is set appropri‐ately.

3.2 read/write

read/write既可以写文件也可以写socket,因为它们都是文件
read、write 函数常常被称为 Unbuffered I/O。指的是无用户及缓冲区。但不保证不使用内核 缓冲区。
相较于C库提供的fgetc、fputc等函数,是没有用户级缓冲区的、

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

失败返回-1;成功:返回的值是读/写字节数。

3.3 lseek

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

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

失败返回-1;成功:返回的值是较文件起始位 置向后的偏移量。

4 end