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:
}