目录
6.5 多次使用open函数打开同一个文件时,这多个文件描述符是不共享文件光标的
1.使用文件IO完成,将源文件中的所有内容进行加密(大写转小写、小写转大写)后写入目标文件中,源文件内容不变
思维导图:
学习内容:
1. 文件描述符
1> 文件描述符本质上是一个整数,当文件存储在本地磁盘时,属于静态文件。当使用open函数打开文件时,该文件就属于动态文件,后期需要对文件进行操作的话,需要基于一个句柄来完成。这个句柄就是文件描述符,用一个整数表示。
2> 文件描述符是一个非负整数,一个进程能够使用的文件描述符默认为1024个。【0,1023】,可以通过指令 ulimit -a查看,并且可以通过ulimit -n进行修改进程能够打开的文件描述符个数
3> 使用原则:最小未分配原则
4> 特殊的文件描述符:0、1、2,分别对应了标准输入、标准输出、标准出错
例如:
#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;
}
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:打开方式:如果参数2中的打开方式有创建文件操作,那么就必须写第三个参数,如果没有创建工作,就无需写第三个参数
O_RDONLY:只读的形式打开
O_WRONLY:只写的形式打开
O_RDWR:读写的形式打开
---------------------以上三种方式必须要包含其中一种,下面的是可选的-------------------------
O_CREAT:创建的形式打开,如果文件不存在,则创建文件
O_TRUNC:清空的形式打开,一般跟O_CREAT一起使用
O_APPEND:以追加的形式打开
O_EXCL:判断文件是否存在,如果存在则报错,如果文件不存在,则可以联合O_CREAT一起创建文件
如果有多个打开方式,中使用位或连接:
例如:"w": O_WRONLY|O_CREAT|O_TRUNC
"w+":O_RDWR|O_CREAT|O_TRUNC
"r":O_RDONLY
"r+":O_RDWR
"a":O_WRONLY|O_CREAT|O_APPEND
"a+":O_RDWR|O_CREAT|O_APPEND
参数3:创建权限,只有参数2中有O_CREAT的时候,才需要写第三个参数
文件最终的权限是:用户给的权限&~umask
一般普通文件的权限是0666 --> 0664,目录文件权限最大为0777 ->0775
如果需要更改umask的值,有两种方式:
1、在终端上输入指定:umask 新值 --->更改该终端的umask的值,其他终端无效
2、在函数体内调用umask函数:umask(新值) --->直接更改程序中的umask的值
返回值:成功返回文件的文件描述符,失败返回-1并置位错误码
3. close关闭文件
#include <unistd.h> int close(int fd);
功能:关闭文件描述符
参数:要关闭的文件描述符
返回值:成功返回0,失败返回-1并置位错误码
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;
}
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;
}
6. 文件描述符拷贝问题
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;
}
6.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;
}
6.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;
}
6.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;
}
6.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. 使用文件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;
}
课外作业:
1.使用文件IO完成,将源文件中的所有内容进行加密(大写转小写、小写转大写)后写入目标文件中,源文件内容不变
解析:
int main(int argc, char const *argv[])
{
int a, b;
if (argc != 3)
{
printf("intput file error\n");
printf("usage:./a.out xxx xxx\n");
return -1;
}
// 以只读模式打开命令行参数1指定的文件,如果失败,则打印错误信息并退出程序
if ((a = open(argv[1], O_RDONLY)) == -1)
{
perror("open error");
return -1;
}
// 以写入、创建和截断模式打开命令行参数2指定的文件,如果失败,则打印错误信息并退出程序
if ((b = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1)
{
perror("open error");
return -1;
}
int res;
char buf[128] = "";
// 循环读取输入文件的内容,直到读取完毕
while ((res = read(a, buf, sizeof(buf))) > 0)
{
// 遍历读取到的每个字符,将小写字母转换为大写,大写字母转换为小写
for (int i = 0; i < res; i++)
{
if (islower(buf[i]))
{
buf[i] = toupper(buf[i]);
}
else if (isupper(buf[i]))
{
buf[i] = tolower(buf[i]);
}
}
// 将转换后的字符写入输出文件
write(b, buf, sizeof(buf));
}
printf("加密成功\n");
// 关闭文件
close(a);
close(b);
return 0;
}
2.并发和并行的区别
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。
3. 什么是进程
狭义定义:进程就是一段程序的执行过程。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
4. 进程的状态有哪些
进程的状态有运行态,就绪态,阻塞态,挂机态
5. 系统中的多个进程的调度机制都有哪些
-
先来先服务(FCFS, First-Come, First-Served):
按照进程到达的顺序进行调度,最先请求CPU的进程最先被服务。 -
短作业优先(SJF, Shortest Job First):
优先调度估计执行时间最短的进程,以减少进程的平均等待时间。 -
优先级调度:
根据进程的优先级进行调度,优先级高的进程更可能被选中执行。 -
时间片轮转(RR, Round Robin):
每个进程被分配一个固定的时间片,进程可以在这个时间片内运行。时间片用完后,CPU转移到下一个进程。 -
多级队列(MLQ, Multi-Level Queue):
将进程分为不同的类别或优先级队列,每个队列有自己的调度算法。 -
多级反馈队列(MFQ, Multi-Level Feedback Queue):
结合了时间片轮转和多级队列的特点,允许进程根据其行为在不同优先级的队列之间移动。 -
基于优先级的抢占式调度:
当一个高优先级的进程到达时,如果CPU正在执行低优先级的进程,它可以抢占CPU。 -
公平共享调度(FSS, Fair Share Scheduling):
确保每个进程公平地获得CPU时间,通常用于防止某些进程长时间占用CPU。