系统调用与库函数
文件操作的系统调用,系统调用的实现在内核中,需要用户态切换到内核态使用。
文件操作用库函数和系统调用很相似,但是库函数一般不会切到内核。
库函数的实现在库中,是用户自己的代码。
四个系统调用:
打开:open();
int open(const char* pathname, int flags);//用于打开一个已存在的文件
int open(const char* pathname, int flags,mode_t mode);//用于新建一个文件,
并设置访问权限
参数介绍:
pathname:将要打开的文件路径和名称
flags : 打开标志,如 O_WRONLY 只写打开
O_RDONLY 只读打开
O_RDWR 读写方式打开
O_CREAT 文件不存在则创建
O_APPEND 文件末尾追加
O_TRUNC 清空文件,重新写入
mode: 权限 如: “0600”
返回值: 为文件描述符
读:read();
ssize_t read(int fd, void* buf, size_t count);
参数介绍:
fd 对应打开的文件描述符
buf 存放数据的空间
count 计划一次从文件中读多少字节数据
返回值: 为实际读到的字节数
写:write();
ssize_t write(int fd, const void* buf,size_t count);
参数介绍:
fd 对应打开的文件描述符
buf 存放待写入的数据
count 计划一次向文件中写多少数据
关闭:close();
int close(int fd);
参数介绍:
fd 要关闭的文件描述符
文件描述符
Linux系统没有文本和二进制之分,所有的东西都是文件,文件描述符是一个整形数字,储存在PCB的文件描述符表中(一个数组),它从0开始递增,是文件的一个id。
每打开一个文件,就会在文件表中占一个位置,文件表默认大小为1024(可以打开1024个文件)。
文件表中的每一个指针都指向一个文件的结构体,里面有文件打开信息,使用量(状态),inode和文件偏移量。每一个文件都有一个inode,它包含了文件详细信息。
进程开始后会有三个文件默认被打开:标准输入(stdin) 0,标准输出(stdout) 1,标准错误输出(stderr) 2;这三个文件被存在文件表中
文件操作
文件操作分三步:
打开文件
读/写
关闭文件
文件放在磁盘上,可以永久存储,由内核管理文件(文件系统)。
打开文件需要文件名,r/w,(权限);
写(文件描述符,“数据”,大小)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("a.txt",O_WRONLY|O_CREAT,0600);//打开,权限0600第一个0是占位符
printf("fd = %d\n",fd);
write(fd,"hello",5);//写
close(fd);//关闭
exit(0);
}
因为三个默认文件把012占用了,所以这个输出文件描述符为3
1表示屏幕标准输入,可以直接向屏幕上写数据
write(1,"hello",5);
读(文件描述符,读到的位置,大小)
int fd = open("a.txt",O_RDONLY);
if(fd==-1)//打开失败
{
printf("open a.txt err\n");
}
char buff[128]={0};//申请空间
int n = read(fd,buff,127);//期望读127个字符,但因为只有hello五个字符,所 以只读五个
printf("n=%d,buff=%s\n",n,buff);
close(fd);
exit(0);
如果一次读不完,需要读多次到文件末尾,当某一次读到0个字节时,文件内容读完,即n=0;每次读都会接着上一次的位置继续读。
应用:复制文件
复制文件就是一个文件一直读,另一个文件一直写。
int main()
{
//打开文件
int fdr = open("passwd",O_RDONLY);
int fdw = open("newpasswd",O_WRONLY|O_CREAT,0600);
if(fdr==-1||fdw==-1)
{
printf("open file err\n");
exit(0);
}
//读、写
char buff[1024]={0};
int n=0;
while((n=read(fdr,buff,1024))>0)
{
write(fdw,buff,n);
}
//关闭文件
close(fdr);
close(fdw);
exit(0);
}
复制成功:
其他方法
可以将需要复制的文件名作为参数传入主函数
char* filename=argv[1];
char* newfilename = argv[2];
int fdr = open(filename,O_RDONLY);
int fdw = open(newfilename,O_WRONLY|O_CREAT,0600);
运行时输入./main passwd newpasswd
也可以完成上面的复制。
父、子进程读同一文件
当先打开文件再复制进程,父、子进程会共享这份文件(都读这一份),读的偏移量会相互影响。
int main()
{
int fd = open("a.txt",O_RDONLY);
if(fd==-1)//文件打开失败
{
printf("open file a.txt faild\n");
exit(0);
}
pid_t pid = fork();
if(pid==-1)//复制进程失败
{
printf("fork err\n");
exit(0);
}
if(pid==0)//子进程执行读
{
char buff[128]={0};
read(fd,buff,1);
printf("child read:%s\n",buff);
sleep(1);
read(fd,buff,1);
printf("child read:%s\n",buff);
}
else//父进程执行读
{
char buff[128]={0};
read(fd,buff,1);
printf("parent read:%s\n",buff);
sleep(1);
read(fd,buff,1);
printf("parent read:%s\n",buff);
}
close(fd);
exit(0);
}
但当先复制进程再打开文件读,子进程和父进程是各自读各自的
int main()
{
pid_t pid = fork();//复制进程
if(pid==-1)
{
printf("fork err\n");
exit(0);
}
int d = open("a.txt",O_RDONLY);//打开文件
if(fd==-1)
{
printf("open file a.txt faild\n");
exit(0);
}
if(pid==0)
{
char buff[128]={0};
read(fd,buff,1);
printf("child read:%s\n",buff);
sleep(1);
read(fd,buff,1);
printf("child read:%s\n",buff);
}
else
{
char buff[128]={0};
read(fd,buff,1);
printf("parent read:%s\n",buff);
sleep(1);
read(fd,buff,1);
printf("parent read:%s\n",buff);
}
close(fd);
exit(0);
}
因为文件打开后文件描述符存放在文件表中,当进程复制时,PCB会先被复制一份,文件表会随着PCB一起复制
当先打开文件,再复制进程,进程被复制后,父、子进程文件表中共用一个文件文件表,共享文件及其偏移量。
当先复制进程再打开文件,这两个进程都会单独打开一个,不再共享。