三、文件IO
是使用系统调用(内核提供的函数)来完成数据的读写操作,不提供缓冲区,基于文件描述符操作文件。每进行一次文件io操作,进程就会从用户空间向内核空间进行一次切换,效率没有标准io高。
3.1 文件描述符
1> 文件描述符本质上是一个整数,当文件存储在本地磁盘时,属于静态文件。当使用open函数打开文件时,该文件就属于动态文件,后期需要对文件进行操作的话,需要基于一个句柄来完成。这个句柄就是文件描述符,用一个整数表示。
2> 文件描述符是一个非负整数,一个进程能够使用的文件描述符默认为1024个。【0,1023】,可以通过指令 ulimit -a查看,并且可以通过ulimit -n进行修改进程能够打开的文件描述符个数
3> 使用原则:最小未分配原则
4> 特殊的文件描述符:0、1、2,分别对应了标准输入、标准输出、标准出错
stdin->_fileno ===> 0 ===> STDIN_FILENO
stdout->_fileno ===> 1 ===> STDOUT_FILENO
stderr->_fileno ===> 2 ===> STDERR_FILENO
#include<myhead.h>
int main(int argc, const char *argv[])
{
printf("stdin->_fileno = %d\n", stdin->_fileno); //0
printf("stdout->_fileno = %d\n", stdout->_fileno); //1
printf("stderr->_fileno = %d\n", stderr->_fileno); //2
FILE *fp = NULL;
if((fp = fopen("./file.txt", "w")) == NULL)
{
perror("fopen error");
return -1;
}
printf("fp->_fileno = %d\n", fp->_fileno); //3 最小未分配
return 0;
}
3.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);
功能:以指定的方式打开指定的文件
参数1:要打开的文件路径
参数2:文件打开标识
必须包含以下三者之一:
O_RDONLY:以只读的形式打开文件
O_WRONLY:以只写的形式打开文件
O_RDWR:以读写的形式打开文件
以下的可以选择性加入,使用位或完成:
O_CREAT:如果文件存在,就直接打开,如果文件不存在,则创建文件,此时,参数3必须要加,表示创建的文件权限
O_TRUNC:表示清空文件内容
O_APPEND:以追加的形式打开文件
O_EXCL:确保打开的是不存在的文件,通常跟O_CREAT一起使用,表示本次操作必须创建新文件,如果文件存在,则open函数报错,错误码为:EEXIST
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
"r":O_RDONLY
"r+":O_RDWR
"w":O_WRONLY|O_CREAT|O_TRUNC
"w+":O_RDWR|O_CREAT|O_TRUNC
"a":O_WRONLY|O_APPEND|O_CREAT
"a+":O_RDWR|O_APPEND|O_CREAT
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
参数3:当参数2中有O_CREAT属性时,参数3必须加上,表示本次创建文件的文件权限,但是,最终的权限不是用户给定的权限
文件最终的权限是 给定的权限mode&~umask的值
umask可以通过指令umask进行查看当前终端的默认掩码
可以通过 umask -n 来将掩码更改成 n,但是,这种方式更改的umask只在当前终端有效,其他终端无效
也可以通过umask(n)来更改umask的值
#include <sys/stat.h>
mode_t umask(mode_t cmask);
功能:更改当前的umask的值
参数:要更改的umask的值
普通文件权限一般为:0664
目录文件权限一般为:0775
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
返回值:成功返回一个新的文件描述符,失败返回-1并置位错误码
3.3 close关闭文件
#include <unistd.h>
int close(int fd);
功能:关闭文件描述符
参数:要关闭的文件描述符
返回值:成功返回0,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//创建一个文件描述符变量,用于存储文件描述符
int fd = -1;
if((fd = open("./file.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664)) == -1)
{
perror("open error");
return -1;
}
printf("open success fd = %d\n", fd); //3
//关闭文件
if(close(fd) == -1)
{
perror("close fd 1");
return -1;
}
return 0;
}
3.4 read\write 数据读写
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向指定的文件中,写入指定的信息
参数1:已打开的文件描述符
参数2:要写入的数据的起始地址
参数3:要写入的数据大小
返回值:成功返回写入字符的个数,失败返回-1并置位错误码
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从指定的文件中读取count个字节的数据到buf中
参数1:打开的文件描述符
参数2:要存放数据的容器起始地址
参数3:要读取的字节个数
返回值:成功返回读取字节的个数,失败返回-1并置位错误码
#include<myhead.h>
//定义结构体类型
typedef struct
{
char name[20];
int age;
double score;
}Stu;
/*********************主程序*************************/
int main(int argc, const char *argv[])
{
//创建一个文件描述符变量,用于存储文件描述符
int fd = -1;
if((fd = open("./file.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664)) == -1)
{
perror("open error");
return -1;
}
printf("open success fd = %d\n", fd); //3
//定义三个学生
Stu s[3] = {{"zhangsan", 10, 99.5},\
{"lisi", 15, 88},\
{"wangwu", 8, 60}};
//将所有学生写入文件中
write(fd, s, sizeof(s));
//关闭文件
if(close(fd) == -1)
{
perror("close fd 1");
return -1;
}
//重新打开文件
if((fd = open("./file.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
printf("open success fd = %d\n", fd); //3
Stu temp; //要接受数据的容器
read(fd, &temp, sizeof(temp));
printf("姓名:%s, 年龄:%d, 成绩:%.2lf\n", temp.name, temp.age, temp.score);
//关闭文件
close(fd);
return 0;
}
3.5 lseek 光标偏移
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:移动文件的光标
参数1:文件描述符
参数2:偏移量
>0:向后偏移
=0:不偏移
<0:向前偏移
参数3:偏移起始位置
SEEK_SET:文件开头
SEEK_END:文件结尾
SEEK_CUR:文件当前位置
返回值:成功返回文件光标当前的位置,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//打开文件,以读写的形式
int fd = -1;
if((fd = open("./gg.bmp", O_RDWR)) == -1)
{
perror("open error");
return -1;
}
//偏移光标
lseek(fd, 2, SEEK_SET);
//定义变量接受大小
int img_size = 0;
read(fd, &img_size, sizeof(img_size));
printf("img_size = %d\n", img_size);
//将光标直接偏移到文件末尾,返回值就是文件大小
printf("size = %ld\n", lseek(fd, 0, SEEK_END));
//关闭文件
close(fd);
return 0;
}
练习:使用文件IO完成两个文件的拷贝
#include<myhead.h>
int main(int argc, const char *argv[])
{
//判断传入的文件个数
if(argc != 3)
{
write(2, "input file error\n", sizeof("input file error\n"));
return -1;
}
//以只读的形式打开源文件
int srcfd = open(argv[1], O_RDONLY);
if(srcfd == -1)
{
perror("open srcfile error");
return -1;
}
//以只写的形式打开目标文件
int destfd = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0664);
if(destfd == -1)
{
perror("open destfile error");
return -1;
}
//定义搬运工
char buf[128] = "";
while(1)
{
int src = read(srcfd, buf, sizeof(buf));
if(src == 0)
{
break;
}
//将读取的数据全部写入到新文件中
write(destfd, buf, src);
}
printf("拷贝成功\n");
//关闭文件
close(srcfd);
close(destfd);
return 0;
}
3.6 文件描述符拷贝问题
1> 简单完成两个文件描述符变量的拷贝
这种情况下,没有新文件描述符产生,使用的是同一个文件描述符,共享同一个文件光标
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件描述符变量
int fd1 = -1;
//打开文件
if((fd1 = open("./file.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//定义变量存储文件描述符
int fd2 = fd1;
//通过fd2完成文件光标的偏移
lseek(fd2, 8, SEEK_SET);
//从fd1中读取数据、
char buf[128] = "";
read(fd1, buf, 5);
printf("buf = %s\n", buf); //nihao:说明fd1和fd2共用同一个光标
//hello:说明不共用光标
//关闭文件描述符
close(fd1);
return 0;
}
2> dup函数完成拷贝
该操作,会生成新的文件描述符,但是与旧的文件描述符共享同一个光标
#include <unistd.h>
int dup(int oldfd);
功能:拷贝旧文件描述符,生成新的文件描述符
参数:旧文件描述符
返回值:新文件描述符
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件描述符变量
int fd1 = -1;
//打开文件
if((fd1 = open("./file.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//定义变量存储文件描述符
int fd2 = dup(fd1);
printf("fd1 = %d, fd2 = %d\n", fd1, fd2); //3 4
//通过fd2完成文件光标的偏移
lseek(fd2, 8, SEEK_SET);
//从fd1中读取数据、
char buf[128] = "";
read(fd1, buf, 5);
printf("buf = %s\n", buf); //nihao:说明fd1和fd2共用同一个光标
//hello:说明不共用光标
//关闭文件描述符
close(fd1);
return 0;
}
3> 使用dup2拷贝文件描述符
int dup2(int oldfd, int newfd);
功能:拷贝旧文件描述符,放入新文件描述符中,如果新文件描述符之前已经打开,则在拷贝时,默认将其关闭
参数1:旧文件描述符
参数2:新文件描述符
返回值:成功返回新文件描述符,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//打开一个文件
int fd2 = -1;
if((fd2 = open("./tt.c", O_RDONLY))==-1)
{
perror("open error");
return -1;
}
printf("fd2 = %d\n", fd2); //3
//定义文件描述符变量
int fd1 = -1;
//打开文件
if((fd1 = open("./file.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//定义变量存储文件描述符
dup2(fd1 ,fd2);
printf("fd1 = %d, fd2 = %d\n", fd1, fd2); //4 3
//通过fd2完成文件光标的偏移
lseek(fd2, 8, SEEK_SET);
//从fd1中读取数据、
char buf[128] = "";
read(fd1, buf, 5);
printf("buf = %s\n", buf); //nihao:说明fd1和fd2共用同一个光标
//hello:说明不共用光标
//关闭文件描述符
close(fd1);
return 0;
}
4> dup2常用用途
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件描述符
int fd = -1;
if((fd = open("./aa.txt", O_WRONLY|O_CREAT|O_TRUNC, 0664)) == -1)
{
perror("open error");
return -1;
}
//将标准输出拷贝到fd中
dup2(fd, STDOUT_FILENO); //将标准输出重定向到文件中
dup2(fd, STDIN_FILENO); //将标准输入重定向到文件中
dup2(fd, STDERR_FILENO); //将标准出差重定向到文件中
printf("hello a\n"); //?
printf("hello a\n"); //?
printf("hello a\n"); //?
printf("hello a\n"); //?
printf("hello a\n"); //?
//关闭文件
close(fd);
return 0;
}
5> 多次使用open函数打开同一个文件时,这多个文件描述符是不共享文件光标的
#include<myhead.h>
int main(int argc, const char *argv[])
{
//定义文件描述符
int fd1 = -1;
if((fd1 = open("./file.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//定义文件描述符
int fd2 = -1;
if((fd2 = open("./file.txt", O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
//移动fd1的光标
lseek(fd1, 8, SEEK_SET);
//从fd2中读取数据
char buf[10] = "";
read(fd2, buf, 5);
printf("buf = %s\n", buf); //?
//关闭文件
close(fd1);
close(fd2);
return 0;
}
四、文件属性函数
1> 函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
功能:获取给定的文件状态属性
参数1:要获取的文件名
参数2:文件属性结构体指针,是一个地址传递,需要传递一个结构体变量的地址,通过函数调用结束后,该变量中就有内容了
struct stat {
dev_t st_dev; /* 设备号 */
ino_t st_ino; /* Inode 号 */
mode_t st_mode; /* 文件类型和文件权限 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 拥有者的用户id号 */
gid_t st_gid; /* 拥有者的组id号 */
dev_t st_rdev; /* 特殊文件的设备号 */
off_t st_size; /* 总大小,以字节为单位 */
blksize_t st_blksize; /* 块的大小 */
blkcnt_t st_blocks; /* 总块数 */
};
如何获取文件的类型和文件权限
1> 使用st_mode & S_IFMT可以得到文件的类型
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
2> 使用st_mode & 0777可以得到文件的权限
S_ISUID 04000 set-user-ID bit
S_ISGID 02000 set-group-ID bit (see below)
S_ISVTX 01000 sticky bit (see below)
S_IRWXU 00700 owner has read, write, and execute permission
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others (not in group) have read, write, and
execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
返回值:成功返回0,失败返回-1并置位错误码
#include<myhead.h>
int main(int argc, const char *argv[])
{
//判断外部传来的文件
if(argc != 2)
{
printf("input file error\n");
return -1;
}
//定义接受文件的属性结构体变量
struct stat sb; //stat buf
//调用函数,获取文件状态
if(stat(argv[1], &sb) == -1)
{
perror("stat error");
return -1;
}
//程序执行至此,sb中就记录了传入的文件的信息
//输出文件类型
switch(sb.st_mode & S_IFMT)
{
case S_IFSOCK:
{
printf("套接字文件\t");
}
break;
case S_IFLNK:
{
printf("链接文件\t");
}
break;
case S_IFREG:
{
printf("普通文件\t");
}
break;
case S_IFBLK:
{
printf("块设备文件\t");
}
break;
case S_IFDIR:
{
printf("目录文件\t");
}
break;
case S_IFCHR:
{
printf("字符设备文件\t");
}
break;
case S_IFIFO:
{
printf("管道文件\t");
}
break;
}
//输出文件权限
printf("%#o\t", sb.st_mode&0777);
//输出文件大小
printf("%ld\t", sb.st_size);
//输出文件inode号
printf("%ld\n", sb.st_ino);
return 0;
}
作业
1> 使用文件IO完成,将源文件中的所有内容进行加密(大写转小写、小写转大写)后写入目标文件中
源文件内容不变
#include <myhead.h>
#include <ctype.h>
int main(int argc, const char *argv[])
{
char ch;
//创建一个文件描述符变量,用于存储文件描述符
int fd1 = -1;
if ((fd1 = open("./source.txt", O_RDONLY | O_CREAT, 0664)) == -1) //读取文件,若没有文件则新建一个
{
perror("open error");
return -1;
}
int fd2 = -1;
if ((fd2 = open("./target.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1) //写入文件,若没有则新建一个,同时清空文件内容
{
perror("open error");
return -1;
}
ssize_t bytesRead;
while ((bytesRead = read(fd1, &ch, 1)) > 0)
{
if (isupper(ch)) //如果是大写
{
ch = tolower(ch); //转小写
}
else if (islower(ch)) //如果小写
{
ch = toupper(ch); //转大写
}
write(fd2, &ch, 1);
}
//关闭文件
if (close(fd1) == -1)
{
perror("close fd1 1");
return -1;
}
if (close(fd2) == -1)
{
perror("close fd2 1");
return -1;
}
return 0;
}
2> 查资料了解以下概念:
并发和并行的区别
并发(Concurrency)指的是在同一时间段内,宏观上有多个程序在同时运行,但在单处理器系统中,这些程序是交替执行的,即在一个时间点上只有一个程序在运行。而并行(Parallelism)则是指在同一时刻,有多条指令在多个处理器上同时执行。简言之,并发是逻辑上的同时发生,而并行是物理上的同时发生。
什么是进程
狭义定义:进程就是一段程序的执行过程。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程和程序的区别
程序是永久的,而进程是暂时存在的。
进程是程序的一次执行,而进程总是对应至少一个特定的程序。
程序是静态的,而进程是动态的。
程序是存储在某种介质上的二进制代码,进程对应了程序的执行过程,系统不需要为一个不执行的程序创建进程,一旦进程被创建,就处于不断变化的动态过程中,对应了一个不断变化的上下文环境。
进程的状态有哪些
- 新建态:进程正在被创建,尚未转到就绪状态。
- 就绪态:进程已准备好运行,等待CPU调度。
- 执行态:进程正在执行,占据CPU。
- 阻塞态:进程等待某种事件完成(如I/O操作),无法执行。
- 结束态:进程已结束执行或出现异常,等待资源回收。
系统中的多个进程的调度机制都有哪些
- 先来先服务:按照进程请求CPU的顺序进行调度。
- 短作业优先:优先调度预计执行时间最短的进程。
- 优先级调度:根据进程优先级进行调度,高优先级进程先运行。
- 轮转调度:按固定时间片轮流执行各个进程。
- 多级反馈队列:结合多种调度算法,进程在不同优先级队列间移动。
- 基于时间共享的调度:为每个进程分配固定的时间片,循环执行。
- 基于优先级的时间共享:根据进程优先级分配时间片,高优先级进程获得更多时间片。
- 实时调度:针对实时系统设计的调度机制,保证实时任务的及时执行。
- 多级队列:根据进程类型和优先级将进程放入不同队列,依次调度。
- 彩票调度:通过“彩票”的概率性分配CPU时间片,增加公平性和可预测性。