Day 47 Linux (文件描述符)

本文详细介绍了操作系统中进程控制块(PCB)的角色,以及如何通过文件描述符表来管理进程与文件的关系。重点讲解了标准输入、输出和错误的文件描述符,并阐述了Linux中每个打开文件对应的file结构体。此外,讨论了进程打开文件数量的限制、FILE结构体在C语言中的应用,以及并发进程中文件操作的同步问题。还提到了阻塞和非阻塞I/O的概念,以及fcntl和lseek函数在文件属性管理和偏移量调整中的作用。最后,通过示例代码展示了dup和dup2函数如何实现文件描述符的复制和重定向。

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

3. 文件描述符

3.1 PCB进程控制块

进程控制块(PCB)是系统为了管理进程设置的一个数据结构,系统用它来记录进程的外部特征,描述进程的运动变化过程

我们可以用locate sched.h来查看位置

3.2 文件描述符表

结构体PCB的成员变量files_struct *file指向文件描述符表,通过文件下表0/1/2/3....可以找到文件的结构体

而0/1/2是预设的描述符,表示标准输入,标准输出,标准报错

STDIN_FILENO                                        0

STDOUT_FILENO                                    1

STDERR_FILENO                                    2 

Linux中文件结构体代表一个打开的文件, 系统中的每个打开的文件在内核空间都有一个关联的file。它由内核在打开文件时创建,并传递给在文件.上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。


 

 3.3 最大文件打开数

一个进程默认打开文件的个数1024。
        命令查看ulimit-a查看open filesx对应值。默认为1024
        可以使用ulimit -n 4096修改。
        当然也可以通过修改系统配置文件永久修改该值,但是不建议这样操作。。
        cat /pro/s/fs/ile-max可以查看该电脑最大可以打开的文件个数。受内存大小影响。

3.4 FILE结构体

FILE是C语言文件结构定义,打开文件和对文件的操作都要通过这个结构体

struct _iobuf{
    char *_ptr;            //文件输入的下一个位置
    int _cnt;              //当前缓冲区相对位置
    char *_base;           //文件的开始位置
    int _flag;             //文件标志
    int _file;             //文件描述符编号
    int _charbuf;          //文件缓冲区
    int _bufsiz;           //缓冲区大小
    char *_tmpfname;   

};

两个进程同时打开文件,系统调用方式执行

 1. 每个进程都会独立创建一个文件描述表,读取时互不影响

 2.以读写的方式打开,先读后写,从读后的位置开始覆盖写入的新内容

进程1的代码,先读15个字符,休眠20s后在读15个字符 

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

int main()
{
	char buf[100] = "";
	int fd = open("1.txt", O_RDWR);
	if(fd < 0)
	{
		perror("error");
	}
	int ret = read(fd, buf, 15);
	printf("%d, %s\n", ret, buf);
	sleep(20);
	strcpy(buf ,"");
        ret = read(fd, buf, 15);
	printf("%d, %s\n", ret, buf);
	close(fd);
	
	return 0;
	
}

 进程2的代码,先写然后读写后位置后的10个字符

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

int main()
{
	char buf[] = "asjfkasjf";
	int fd = open("./1.txt", O_RDWR);
	if(fd < 0)
	{
		perror("error");
	}
	int ret = write(fd, buf, 40);
	printf("%d\n", ret);
	char buf1[1000] = "";
	read(fd, buf1, 10);
	close(fd);
	return 0;
}

两个进程同时打开一个文件,标准IO方式进行 

 

1.两个进程都会将开始的文件全部读入到缓冲区
2.当缓冲区满后,会再次从文件读取内容(可能会发生更新)
3.因为两个进程都同时读入了开的内容,所以可以独立操作原来的内容
4.文件的最终结果,受两个程序的综合作用
进程1:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main()
{
	FILE *fp = fopen("./1.txt", "r+");
	if(fp == NULL)
	{
		perror("Error");
	}
	char buf[100] = "";
	fread(buf, 2, 5, fp);
	printf("%s\n", buf);
	strcpy(buf, "");
	sleep(10);
	fread(buf, 2, 100, fp);
	fclose(fp);

	return 0;
}

进程2:

#include<stdio.h>
#include<string.h>

int main()
{
	FILE *fp = fopen("./1.txt", "r+");
	if(fp == NULL)
	perror("Error");
	char buf[100] = "哈哈哈哈哈哈哈哈哈哈";
	fwrite(buf, 2, 50, fp);
	strcpy(buf ,"");
	fread(buf, 2, 50, fp);
	fclose(fp);
	return 0;	

}

他们会各执行各的,当程序都执行完了,才会刷新最后结果

 4. 阻塞、非阻塞

    读常规文件是不会阻塞的,不管读多少字节,read 一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read 读终端设备就会阻塞,如果网络上没有接收到数据包,调用read 从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果- -直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。 
    现在明确一下阻塞(Block)这个概念。 当进程调用一个阻塞的系统函数时, 该进程被置于睡眠(Sleep) 状态,这时内核调度其它进程运行,直到该进程等待的事件发生"了(此如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:
        1.正在被调度执行,CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。.
        2.就绪状态,该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程, 所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度進执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢

阻塞读终端:[ block readtty.c]
非阻塞读终端:[ nonblock_ readtty.c]
非阻塞读终端和等待超时:[ nonblock_ timeout.c]
注意,阻塞与非阻塞是对于文件而言的。而不是read、 write 等的属性。read 终端,默认阻塞读。
小结:
        阻塞是设备文件,网络文件的属性
        产生阻塞的场景:读设备文件,读网络文件。(读常规文件无阻塞概念)比如: /devtty -终端文件。

//查看tty的阻塞现象
#include <unistd 。h>
#include <std1ib.h>
#include <stdio. h>
{
    int main(void)
    char buf[10] ;
    int n;
    n = read(STDIN_ FILENO, buf, 10); 
    if(n < 0)
    {
        perror("read STDIN FILENO");
        //printf("%d",errno);
        exit(1);
    }
    write(STDOUT_ FILENO,buf, n);
    return 0;
}

设备文件,不需要关闭,可以重新打开,改变文件属性

//查看tty设置为非阻塞属性
#include <unistd .h>
#include <fcnt1. h>
#include <errno. h>
#include <stdio. h>
#include <std1ib.h>
#include <string.h>

int main(void)
{
    char buf[10];
    int fd,n
    fd = open(" /dev/tty",0_ RDONLY |0_ NONBLOCK) ;
    if(fd<0)
    {
        perror("open /dev/tty");
        exit(1);
    }
tryagain: 
    n = read(fd, buf, 10);
    if(n<0)
    {
        if Cerrno != EAGAIN)
        { // if(errno != EWOULDBLOCK)
            perror("read /dqv/tty");
            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;
}

优化代码,防止无限等待

//查看tty设置为非阻塞属性,优化代码
#include <unistd.h>
#include <fcnt1 .h>
#include <errno .h>
#include <stdio.h>
#include <std1ib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
    char buf[10];
    int fd, n;
    fd = open(" /dev/tty", 0_ RDONLY 10_ NONBLOCK);
    if(fd<0)
    {
        perror("open /dev/tty");
        exit(1);
    }
tryagain:
    n = read(fd, buf, 10);
    if(n<0)
    {
        if (errno != EAGAIN)
        { // if(errno != EWOULDBLOCK)
            perror("read /dqv/tty");
            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;
}

 优化代码,防止无限等待

//查看tty设置为非阻塞属性,优化代码
#include <unistd.h>
#include <fcnt1 .h>
#include <errno .h>
#include <stdio .h>
#include <std1ib.h>
#include <string.h>
#include <unistd .h>
int main(void)
{
    char buf[10];
    int fd, n,i;
    fd = open(" /dev/tty", 0 RDONLY|O_ NONBLOCK);
    if(fd<0)
    {
        perror("open /dev/tty");
        exit(1);
    }
    printf("open /dev/tty ok... %d\n",fd);
    for(i=0;i<5;i++)
    {
        n = read(fd, buf, 10);
        if(n>0)
        {
            break;
        }
        if(n<0)
        {
            if (errno != EAGAIN)
            { // if(errno != EWOULDBLOCK)
                perror("read /dqv/tty");
                exit(1);
            }
            else
            {
                write(STDOUT_ FILENO,"try again\n", strlen("try again\n"));
                sleep(2);
            }
        }
    }
    if(i==5)
    {
        write (STDOUT_ FILENO,"time out \n", strlen("time out \n"));
    }
    else
    {
        write(STDOUT_ _FILENO,buf, n);
    }
    close(fd);
    return 0;
}



5. fcntl修改文件属性

改变一个[已经打开]的文件的访问控制属性。
重点掌握两个参数的使用,F. _GETFL和F_ SETFL

//查看tty设置为非阻塞属性,优化代码
#include <unistd.h>
#include <fcnt1. h>
#include <errno. h>
#include <stdio. h>
#include <std1ib.h>
#include <string.h>
int main(void)
{
    char buf[10];
    int fd,n,i, flags ;
    flags = fcnt1(STDIN_ FILENO,F_ GETFL); //获取stdi n属性信息
    if(flags = -1)
    {
        perror("fcnt1 error");
        exit(1);
    }
    f1ags |= 0 NONBLOCK;
    int ret = fcnt1(STDIN FILENO, F_ SETFL, flags);
    if(ret = -1)
    {
    perror("fcnt1 error");
    exit(1);
    }
    for(i=0;i<5;i++)
    {
        n = read(fd, buf, 10);
        if(n>0)
        {
            break;
        }
        if(n<0)
        {
            if (errno != EAGAIN)
            { // if(errno != EWOULDBLOCK)
                perror("read /dev/tty");
                exit(1);
            }
            else
            {
                write(STDOUT_ FILENO,"try again\n", strlen("try again\n"));
                sleep(2) ;
            }
        }
    }
    if(i==5)
    {
        write(STDOUT_ FILENO,"time out \n", strlen("time out \n"));
    }
    else
    {
        write(STDOUT_ FILENO, buf, n);
    }
    close(fd);
    return 0;
}

小结:

        int flags = fcntl(fd, F_GETFL)

        获取文件状态:F_GETFL

        设置文件状态:F_SETFL

6. Iseek函数

Linux中可使用系统函数Iseek来修改文件偏移量(读写位置)。
每个打开的文件都记录着当前读写位置,系统默认打开文件时读写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移多少个字节。
但是有一一个例外,如果以0_ APPEND方式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。
lseek和标准V/0库的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
回忆fseek的作用及常用参数。SEEK SET、SEEK_ CUR、SEEK_ END。

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

off_ t 1seek(int fd,off t offset,int whence) ; 
返回值: 失败: -1;
成功:是文件起始位置向后的偏移量
特别的: lseek 允许超过文件结尾设置偏移,文件会因此拓展(假拓展)

 对offset的解释取决于whence的值:
        ●若whence == SEEK .SET, 则将文件偏移量设为距文件开头offset个字节,此时offset必须为非负
        ●若whence == SEEK _CUR, 则将文件偏移量设为当前值+ offset, 此时ffset可正可负
        ●若whence == SEEK END,则将文件偏移量设为文件长度+ offset, 此时offset可正可负
注意文件"读"和"写"使用同一偏移位置。

6.2 truncate函数:截短文件

#include <unistd.h>
#include <sys/types .h>
int truncate(const char *path,off _t length);
//返回值:
        成功0; 
        失败-1:比如文件不存在
//参数1:需要处理的文件
//参数2: off _t需要截短的字节数(文件的最终大小字节数)

 Iseek仅将新的文件偏移量记录在内核中,它并不弓起任何IO操作,因此它不是系统调用IO,但该偏移量会用于下一次read/write操作
管道、FIFO和套接字不支持设置文件偏移量,不能对其调用Iseek

6.3 od显示文件或流

这对于访问或可视地检查文件中不能直i接显示在终端上的字符很有用。
od -A x -tcx filename                //查看文件:地址16进制,数据ASCII字 符&&16进制
od -A d -tcd filename                //查看文件:地址10进制,数据ASCII 字符&&10进制

 7. dub和dub2函数重定向

功能:复制一个已有的文件描述符,实现指向同一个文件。
        int dup(int o1dfd);
参数:oldfd已有文件描述符
成功:返回一个新文件描述符:
失败: -1设置errno为相应值。

 

 测试dub

#include <stdio .h>
#include <std1ib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcnt1.h>
int main(int argc, char *argv[])
{
    int fd = open(argv[1], 0_ RDWR);
    int newfd = dup(fd) ; 
    printf("newfd = %d\n",newfd);
    int ret01=write(fd,"argv[1]\n" ,strlen("argv[1]\n"));
    printf("fd..... ret01=%d\n", ret01); 
    int ret02=wri te(newfd, "newfd\n" , strlen("newfd\n")); 
    printf("newf..... ret02=%d\n",ret02);
    return 0
}

 测试dub2

功能:实现newfd文件 描述符的指向,重新指向到o1 dfd文件描述符所指向的文件
int dup2(int oldfd, int newfd);
成功:返回值=newfd文件描述符
失败: -1设置errno为相应值。

#include <stdio.h>
#include <std1ib .h>
#include <string.h>
#include <unistd.h>
#include <sys/types .h>
#include <sys/stat .h>
#include <fcnt1 .h>
int main(int argc, char *argv[])
{
    int fd01 = open(argv[1], 0_ RDWR |0_ APPEND);
    int fd02 = open(argv[2], 0_ RDWR |0_ APPEND);
    printf("fd02 = %d\n", fd02) ;
    int newfd = dup2 (fd01 , fd02) ;
    printf("newfd = %d\n",newfd) ;
    int ret01-write(fd01, "argv[1]\n",strlen("argv[1]\n"));
    printf("f..... ret01=%d\n" ,ret01);
    int ret02=wri te(fd02 , "newfd\n" , strlen("newfd\n"));
    printf("newf..... ret02=%d\n" , ret02); 
    return 0:
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值