目录
(2)实现对任意文件的cp拷贝功能(输入源文件名和目的文件名)
【举例】父、子进程同时对文件内容进行读操作并输出(2种情况)
【举例】调用read()从键盘读入数据并打印(键盘的文件描述符为0)
一、文件描述符、Linux操作文件的系统调用
1.C语言中对文件的操作
对于文件的管理:由操作系统~文件系统进行。
文件操作的系统调用 open read write close (系统调用:实现在内核中【用户态->内核态】—“陷入内核”)
C语言操作文件的库函数 fopen fread fwrite fclose (C语言fopen库函数的执行底层是调用Linux系统调用open();printf()库函数底层是调用write()系统调用实现的)
2.Linux中对文件的操作
文件描述符fd:唯一描述该文件。(类似学号是学生的标志)
(1)打开文件 open() 系统调用(实现在内核中)
open()的返回值为整型的文件描述符。
若文件存在:open() //文件名,打开方式:r,w
若文件不存在:open() //文件名,打开方式:r,w|creat,设置文件权限
参数flags为标志位:O_WRONLY 只写打开; O_RDONLY 只读打开;O_RDWR 读写方式打开; O_CREAT 文件不存在则创建 ;O_APPEND 文件末尾追加 ;O_TRUNC 清空文件,重新写入。
参数mode为打开文件时文件不存在需要重建文件设置新文件的访问权限(文件已经创建好时权限已规定且无法修改)。
(2)向文件中写入数据 write()
返回值为实际写入的字节数。
参数fd为open的返回值(文件描述符),从buf指针指向位置开始向文件中写入buf指针指向内容的count个字节。
(3)读取一个文件 read()
返回值为实际读取的字节数。若read()返回值为0,表示已读到文件末尾!
参数fd为open的返回值(文件描述符),从文件中向buf指针指向位置读入count个字节。
注意:存在文件偏移量,当已经读取10字节后,再进行读取,则会从已读入的字节后开始读入。
(4)关闭文件 close()
每次执行完文件操作别忘记关闭文件!!!
3. 实际应用
(1)直接使用
(2)实现对任意文件的cp拷贝功能(输入源文件名和目的文件名)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
//read返回值==0~读到末尾
int main(int argc, char* argv[])
{
if(argc!=3)//argv[0]=./mycp argv[1]=源文件名 argv[2]=目的文件名
{
printf("argc err\n");
exit(1);
}
char *s_name = argv[1];
char *s_newname=argv[2];
int fd1=open(s_name,O_RDONLY);
if(fd1==-1)
{
printf("File:%s not exist\n",s_name);
exit(1);
}
int fd2=open(s_newname,O_WRONLY|O_CREAT,0600);
if(fd2==-1)
{
printf("Cerate File:%s error\n", s_newname);
exit(1);
}
char buff[128] = {0};
int num = 0;
while ((num=read(fd1,buff,128))>0)
{
write(fd2, buff, num);//由于不确定每次读128个,所以每次写入读到的num个
}
close(fd1);
close(fd2);
exit(0);
}
(3)fork复制文件描述符~父子进程共享文件偏移量
先open再fork:父子进程共享文件偏移量;先fork后open:文件偏移量不共享。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
int fd = open("a.txt", O_RDONLY);
if(fd==-1)
{
printf("open file error\n");
exit(1);
}
//fork
pid_t pid = fork();
if(pid==-1)
{
printf("fork error\n");
exit(1);
}
if(pid==0)//child 先open再fork:父子进程共享文件偏移量;先fork后open:文件偏移量不共享
{
char buff[32] = {0};
read(fd, buff, 1);
printf("child buff=%s\n", buff);
sleep(1);
read(fd, buff, 1);
printf("child buff=%s\n", buff);
}
else//parent
{
char buff[32] = {0};
read(fd, buff, 1);
printf("parent buff=%s\n", buff);
sleep(1);
read(fd, buff, 1);
printf("parent buff=%s\n", buff);
}
close(fd);
exit(0);
}
对于a.txt(内容为abcdefg),运行结果为:
二、文件表的概念
1. 文件表的概念及理解
C语言中:
标准输入(文件) 键盘 File* stdin 文件描述符fd=0
标准输出 屏幕 File* stdout 文件描述符fd=1
标准错误输出 屏幕 File* stderr 文件描述符fd=2
Linux中:
PCB中包含文件表(左边一栏文件表的下标代表文件描述符>=0,右边一栏对应打开的文件),关闭文件后文件描述符对应的文件消失,对于接下来打开的文件:描述符总选择最小且未被使用的。
【举例】父、子进程同时对文件内容进行读操作并输出(2种情况)
若文件为a.txt(内容:abcdef):
(1) 先打开文件再fork,则输出a,b,c,d;(父、子进程文件偏移量共享!父进程的开的文件(文件描述符)会复制到子进程,父子进程共享该文件)
当父进程执行结束关闭文件close()时,其与struct file的联系断开,r.cout变为1,但子进程仍可以对文件进行操作。故若要避免此情况,应该在父、子进程中均执行close()关闭文件,共执行两次。
(2)fork()后打开文件,则输出a,a,b,b(文件偏移量不共享)
2. printf()的输出和键盘的输入底层原理
printf()最终调用write()输出在屏幕终端上,write()为系统调用,内核实现。printf()攒够数据到缓冲区后便由用户态->内核态调用write()全部打印。
【举例】使用write()向屏幕上写数据
写到屏幕即stdout,调用write参数文件描述符为1
此处的write(1,"hello",5); 就等价于printf("hello");
【举例】输出什么? BAA
解释:父进程先将A放在缓冲区,父进程打印出B,fork()连同缓冲区(A)一同复制给子进程,程序运行结束再调用write()刷新缓冲区输出AA。
【举例】调用read()从键盘读入数据并打印(键盘的文件描述符为0)
3. 库函数和系统调用的区别
库函数:printf(),strlen(),还包括自己实现的函数add()等。库函数的实现在该库中。
系统调用:getpid()等。由内核实现,为内核代码的一部分。系统调用本质:陷入内核,传递系统调用号和参数,执行正确的系统调用函数,把返回值带回用户空间。
man 操作手册:1 命令 2 系统调用 3 库函数