C语言学习3==文件描述符、系统调用操作文件

本文介绍了Linux系统中文件描述符的概念及其使用方法,包括如何通过open、close、read、write和lseek等函数进行文件的打开、关闭、读取、写入及定位操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

15. 文件描述符

在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。

打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。

程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。

1

#define STDIN_FILENO  0 //标准输入的文件描述符
2

#define STDOUT_FILENO 1 //标准输出的文件描述符
3

#define STDERR_FILENO 2 //标准错误的文件描述符

在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。

最大打开的文件个数

Linux 中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用 close() 函数关闭文件。

  • 查看当前系统允许打开最大文件个数:

    cat /proc/sys/fs/file-max

  • 当前默认设置最大打开文件个数1024

    ulimit -a

  • 修改默认设置最大打开文件个数为4096

    ulimit -n 4096

16. 常用文件IO函数(以下均为系统调用)

Linux的man手册共有以下几个章节:

1、Standard commands (标准命令)

2、System calls (系统调用)

3、Library functions (库函数)

4、Special devices (设备说明)

5、File formats (文件格式)

6、Games and toys (游戏和娱乐)

7、Miscellaneous (杂项)

8、Administrative Commands (管理员命令)

使用系统调用函数依然是需要引入头文件的,要引入哪些可以通过man命令查看

man 2 命令

16.1 open函数

man 2 open

#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);
功能:
    打开文件,如果文件不存在则可以选择创建。
参数:
    pathname:文件的路径及文件名
    flags:打开文件的行为标志,必选项 O_RDONLY, O_WRONLY, O_RDWR
    mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
    成功:成功返回打开的文件描述符
    失败:-1

flags详细说明

必选项:

取值含义
O_RDONLY以只读的方式打开
O_WRONLY以只写的方式打开
O_RDWR以可读、可写的方式打开

可选项,和必选项按位或起来

取值含义
O_CREAT文件不存在则创建文件,使用此选项时需使用mode说明文件的权限
O_EXCL如果同时指定了O_CREAT,且文件已经存在,则出错
O_TRUNC如果文件存在,则清空文件内容
O_APPEND写文件时,数据添加到文件末尾
O_NONBLOCK对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O

mode补充说明

1) 文件最终权限:mode & ~umask

2) shell进程的umask掩码可以用umask命令查看

Ø umask:查看掩码(补码)

Ø umask mode:设置掩码,mode为八进制数

Ø umask -S:查看各组用户的默认操作权限

取值八进制含义
S_IRWXU00700文件所有者的读、写、可执行权限
S_IRUSR00400文件所有者的读权限
S_IWUSR00200文件所有者的写权限
S_IXUSR00100文件所有者的可执行权限
S_IRWXG00070文件所有者同组用户的读、写、可执行权限
S_IRGRP00040文件所有者同组用户的读权限
S_IWGRP00020文件所有者同组用户的写权限
S_IXGRP00010文件所有者同组用户的可执行权限
S_IRWXO00007其他组用户的读、写、可执行权限
S_IROTH00004其他组用户的读权限
S_IWOTH00002其他组用户的写权限
S_IXOTH00001其他组用户的可执行权限

16.2 close函数

#include <unistd.h>
int close(int fd);
功能
    关闭已打开的文件
参数:
    fd : 文件描述符,open()的返回值
返回值:
    成功:0
    失败: -1, 并设置errno

需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。

但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

16.3 write函数

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
    把指定数目的数据写到文件(fd)
参数:
    fd :  文件描述符
    buf : 数据首地址
    count : 写入数据的长度(字节)
返回值:
    成功:实际写入数据的字节个数
    失败: - 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//write file
int main(void)
{
        int fd=-1;
        int ret=-1;
        char *str="hello test";
//打开或者创建一个文件,返回文件描述符。文件名就叫做txt
        fd=open("txt",O_WRONLY | O_CREAT,0644);
        if(-1==fd){
                perror("open");
                return 1;
                }
        printf("fd=%d\n",fd);
//往文件中写入内容
        ret=write(fd,str,strlen(str));
        if (-1==ret){
                perror("write");
                return 1;
        }
//长度应该是hello test共10个char占10个字节
        printf("write len:%d\n",ret);
        close(fd);
        return 0;
}

16.4 read函数

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:
    把指定数目的数据读到内存(缓冲区)
参数:
    fd : 文件描述符
    buf : 内存首地址
    count : 读取的字节个数
返回值:
    成功:实际读取到的字节个数
    失败: - 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//read file
#define SIZE 128
int main(void)
{
       int fd=-1;
       int ret=-1;
       char *str="hello test";
        //打开或者创建一个文件,返回文件描述符。文件名就叫做txt
       fd=open("txt",O_RDONLY);
       if(-1==fd){
              perror("open");
              return 1;
                }
       printf("fd=%d\n",fd);
       //往文件中写入内容
       char buf[SIZE];
       memset(buf,0,SIZE);
       ret=read(fd,buf,SIZE);
       if (-1==ret){
perror("read");
return 1;}
//长度应该是hello test共10个char占10个字节
printf("read len:%d\n",ret);
close(fd);
return 0;
}

阻塞和非阻塞的概念

读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。

从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。

同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

【注意】阻塞与非阻塞是对于文件而言的,而不是指read、write等的属性。

以非阻塞方式打开文件程序示例:

#include <unistd.h> //read
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h> //EAGAIN


int main(
{
    // /dev/tty --> 当前终端设备
    // 以不阻塞方式(O_NONBLOCK)打开终端设备,0 1 2进程启动就被占用了,所以最小fd为3
    int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
​
    char buf[10];
    int n;
    n = read(fd, buf, sizeof(buf));
    if (n < 0)
    {
        // 如果为非阻塞,但是没有数据可读,此时全局变量 errno 被设置为 EAGAIN
        if (errno != EAGAIN)
        {
            perror("read /dev/tty");
            return -1;
        }
        printf("没有数据\n");
    }
    return 0;
}
#include <stdio.h>
int main(void){
//阻塞
char ch=-1;
ch=getchar();
putchar(ch);
}

16.5 lseek函数

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:
    改变文件的偏移量
参数:
    fd:文件描述符
    offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。

    whence:其取值如下:
        SEEK_SET:从文件开头移动offset个字节
        SEEK_CUR:从当前位置移动offset个字节
        SEEK_END:从文件末尾移动offset个字节
返回值:
    若lseek成功执行, 则返回新的偏移量
    如果失败, 返回-1

所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。

读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了 O_APPEND 。

17. 扩展-ctags使用

第一步: 安装ctags

deng@itcast:~/linux-4.16.12$ sudo apt install exuberant-ctags

第二步:/usr/include中生成tags文件

切换到/usr/include

deng@itcast:/usr/include$ pwd /usr/include

生成ctags文件

deng@itcast:/usr/include的密码: ls -l tags -rw-r--r-- 1 root root 5271877 5月 31 13:00 tags deng@itcast:/usr/include$

第二个tags

deng@itcast:/usr/src/linux-headers-4.10.0-28 sudo ctags -Rn .

第三步: 配置vimrc

在~/.vimrc文件中最后一行添加如下内容:

set tags+=/usr/include/tags

set tags+=/usr/src/linux-headers-4.10.0-28/tags

第四步: 生效vimrc

执行如下命令

deng@itcast:~/linux-4.16.12$ source ~/.vimrc

Ctrl + ] 表示跟踪代码

Ctrl + t 表示回去

deng@itcast:~$ vim -t STDIN_FILENO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值