19 文件接口

文件概念

文件指的是文件内容+属性,对文件的操作无外乎就是对内容或者属性的操作

为什么平时不用文件接口

我们运行程序访问文件,本质是进程在访问文件,向硬件写入内容,只有操作系统有这个权限。普通用户想写入内容呢?所以操作系统提供了文件类的系统调用,为什么我们没有接触过

一个是因为比较难,c语言这些对接口做了封装,为了更好的使用,这也导致了不同的语言有不同的文件访问接口,但是都是对系统接口的封装。这样的os文件接口只有一套,因为os只有一个

另一个是方便跨平台,每个os都有不同的接口,一旦使用了系统接口编写代码,就无法在其他平台运行,不具备跨平台性。各类语言把平台的代码都实现一遍,条件编译,动态裁剪,这样到不同的平台都可以运行

一切皆文件(感性)

显示器也是硬件,向显示器打印也是一种写入,和磁盘写入文件没有本质区别
曾经理解的文件的操作就是read和write。而显示器的printf是一种write,键盘的scanf是一种read,站在内存的角度,都是input和output
在这里插入图片描述

什么叫做文件?
站在系统的角度,能够被input读取,或者能够output写出的设备叫做文件
狭义的文件:普通的磁盘文件
广义的文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有外设,都可以被称为文件

fopen函数

在这里插入图片描述

权限说明

在这里插入图片描述
r+和w+都为可读可写,区别为文件不在会不会创建文件
w写的方式每次都会清空文件内容,a会在原有内容后面追加

当前路径

一个w权限打开文件的程序,文件会创建在哪里

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    FILE* fd = fopen("log.txt", "w+");
    if (fd == NULL)
    {
        perror("fopen");
        return 0;
    }

    fclose(fd);

    return 0;
}

在这里插入图片描述
log文件创建在了代码源文件的地方,但当把test程序放在上级目录,又会生成在上级目录里。也就是会和程序在同一个目录吗?

将程序改为死循环,进程里查找这个程序,用id找到进程目录
在这里插入图片描述
exe代表可执行程序在磁盘的路径
cwd是进程的当前工作路径

当一个进程启动的时候,会记录这两个路径。进程知道程序的路径,在cwd后面加上需要创建的文件,就是文件创建的路径

c语言函数

在之前学过的写文件函数中,有三个,用这些函数向log文件里写入内容

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fprintf(FILE *stream, const char *format, …);
int fputs(const char *s, FILE *stream);

int main()
{
    FILE* fd = fopen("log.txt", "w+");
    if (fd == NULL)
    {
        perror("fopen");
        return 0;
    }
    
    char* str1 = "hello fwrite\n";
    fwrite(str1, strlen(str1), 1, fd);
    char* str2 = "hello fprintf\n";
    fprintf(fd, "%s", str2);
    char* str3 = "hello fputs\n";
    fputs(str3, fd);\

    fclose(fd);

    return 0;
}

c语言字符串规定最后必须是\0,上面的strlen需要加1保存\0吗?
不需要,\0是c语言的规定,写入文件后,文件不需要遵守,只保存有效数据,读的时候也只读有效数据

在这里插入图片描述

将三个写入注释掉,只用w方式打开文件看看
在这里插入图片描述

文件什么内容都没有输出,w权限打开文件先清空原内容,才会写入。情况文件的功能就可以这样,w权限打开直接关闭

当打开权限改为a后,不会情况内容,会往后面追加
在这里插入图片描述

实现cat命令

利用main传入参数,作为打开的文件,然后用r权限读取内容打印输出

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printf("参数过少\n");
        return 0;
    }

    FILE* fd = fopen(argv[1], "r");
    if (fd == NULL)
    {
        perror("fopen");
        return 0;
    }

    //按行读
    char line[64];
    while (fgets(line, sizeof(line),fd) != NULL)  //fgets,string,自动加\0
    {
       // printf("%s", line);
       fprintf(stdout, "%s", line);
    }
    fclose(fd);

    return 0;
}

在这里插入图片描述

fopen 以w方式打开,默认先情况文件
fopen 以a方式打开,追加,不断向文件最后新增内容

上面的fprintf传入的stdout是标准输出流,将内容打印在显示器上,一个进程默认会打开三个流,标准输入,输出和错误,键盘和显示器都是硬件,是怎么通过FILE*写入到硬件上的

stdin
stdout
stderr

系统接口

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);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
 O_RDONLY: 只读打开
 O_WRONLY: 只写打开
 O_RDWR : 读,写打开
 这三个常量,必须指定一个且只能指定一个
 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
 O_APPEND: 追加写
返回值:
 成功:新打开的文件描述符
 失败:-1

这些宏其实都是一个个标志位,可以打开查看

vim /usr/include/asm-generic/fcntl.h

在这里插入图片描述

如何给函数传标志位

上面的全大写的参数就是宏,open函数控制打开方式的参数只有一个int,这一个标志位int就可以实现各种功能的选择。怎么样用宏控制函数的不同功能?

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
void show(int flag)
{
    if (flag & ONE)
    {
        printf("ONE\n");
    }
    if (flag & TWO)
    {
        printf("TWO\n");
    }
    if (flag & THREE)
    {
        printf("THREE\n");
    }
}
int main()
{
    show(ONE);
    show(TWO);
    show(ONE | TWO);
    show(ONE | TWO | THREE);

    return 0;
}

定义了三个宏,二进制分别为只有每一位有1,show函数通过和每个标志位&判断,可以得到有没有这个参数。传入的时候想传多个参数或以下宏参数,这样就可以只用一个int传入多个标志参数,实现不同功能
在这里插入图片描述

返回值

为了研究open的返回值是什么意思,我们像开始一样写一个简单的打开文件,然后输出返回值
在这里插入图片描述

报错没有这个文件。之前的fopen没有文件会自动创建,这里的系统接口并不会。在应用层看到的一个简单的动作,在系统接口层面甚至os层面,可能需要很多动作才能完成

参数只有一个O_CREAT,文件不存在会创建,所以加上该参数
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd = open("log.txt", O_WRONLY|O_CREAT);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }

    printf("fd = %d", fd);
    return 0;
}

文件是创建成功了,可是发现权限是随机的。这时候就需要用open函数第二个形式,加入权限的参数。第一个形式一般用来读取文件
在这里插入图片描述

s,表示set UID或set GID。位于user或group权限组的第三位置。如果在user权限组中设置了s位,则当文件被执行时,该文件是以文件所有者UID而不是用户UID 执行程序。如果在group权限组中设置了s位,当文件被执行时,该文件是以文件所有者GID而不是用户GID执行程序。

int fd = open(“log.txt”, O_WRONLY|O_CREAT, 0666); //rw- rw- rw-

在这里插入图片描述

发现权限还是和设置的不一样,这时因为有默认的umask默认掩码的存在,可以用函数设置掩码
在这里插入图片描述

在打开文件前先设置uamsk,设为0
在这里插入图片描述
在这里插入图片描述

返回值是3,打开成功,小于0才是失败。那么012去哪了,为什么是3

先看看打开后怎么关闭

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口

close

在这里插入图片描述

write

在这里插入图片描述

往打开的文件里写入一个字符串

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    
    char* str = "hello write\n";
    write(fd, str, strlen(str));
    printf("fd = %d", fd);
    close(fd);
    return 0;
}

在这里插入图片描述

再写入aa看看结果
在这里插入图片描述
并不像w的权限一样会清空写入,而是从开始位置覆盖
有一个选项可以清空内容
在这里插入图片描述

加入选项后就会清空内容
在这里插入图片描述

a的权限追加实际上是把情况换为APPEND
在这里插入图片描述

在这里插入图片描述

read

在这里插入图片描述
返回值是实际读到的字符串个数
修改代码为从文件读取内容,read读取后不会自动添加\0,需要自己添加

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    umask(0);
    int fd = open("log.txt", O_RDONLY, 0666);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    
   // char* str = "aa\n";
   char buff[64];
   memset(buff, '\0', sizeof(buff));  //read不会加\0
   // write(fd, str, strlen(str));
   read(fd, buff ,sizeof(buff));
   printf("%s\n", buff);
    printf("fd = %d\n", fd);
    close(fd);
    return 0;
}

在这里插入图片描述

open的返回值

认识返回值之前,回顾一下操作系统的图
在这里插入图片描述

文件描述符fd

创建方式打开四个文件,看看它们的返回值

int main()
{
    umask(0);
    int fd1 = open("log.txt", O_WRONLY|O_CREAT, 0666);
    int fd2 = open("log1.txt", O_WRONLY|O_CREAT, 0666);  
    int fd3 = open("log2.txt", O_WRONLY|O_CREAT, 0666);  
    int fd4 = open("log3.txt", O_WRONLY|O_CREAT, 0666);  
    if (fd1 < 0)
    {
        perror("open");
        return 1;
    }
    printf("fd1 = %d\n", fd1);
    printf("fd2 = %d\n", fd2);
    printf("fd3 = %d\n", fd3);
    printf("fd4 = %d\n", fd4); 
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    return 0;
}

在这里插入图片描述
返回值从3开始,依次增加。既然这样,那么0,1,2这三个去哪了。前面我们说过,进程会默认打开三个文件,stdin,stdout,stderr,类型是FILE*,这三个刚好对应这里的0,1,2
在这里插入图片描述

怎么证明上面的说法,可以用两次输出到标准输出的函数对比

int main()
{
   fprintf(stdout, "%s", "hello fprintf\n");
   write(1, "hello write\n", strlen("hello write") + 1);
    return 0;
}

在这里插入图片描述
1也实现了输出到stdout相同的结果,所以三个标准文件刚好对应了前三个数

文件描述符就是从0开始的小整数,当打开文件时,操作系统在内存中创建相应的数据结构描述文件,有了file结构体,表示一个已经打开的文件。进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张表file_STRUCT,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针。所以本质上,文件描述符就是该数组的下标,拿到文件描述符,就能找到对应的文件

FILE*

fopen的返回值是FILE*,,是一个结构体,库函数内部一定调用了系统调用,系统角度只认fd,不认FILE,所以FILE内部一定对fd进行了封装

int main()
{
    printf("%d\n",stdin->_fileno);
    printf("%d\n", stdout->_fileno);
    printf("%d\n", stderr->_fileno);

    return 0;
}

在这里插入图片描述

打印出了stdin,stdout,stderr的三个值,正好是对应的三个文件标识符

fd是什么

进程要访问文件,必须先打开文件,一个进程可以打开多个文件,一般而言,进程:打开的文件都是1:n的。文件要被访问,就要加载到内存中,进如果多个进程都打开自己的文件呢?系统中就会存在大量被打开的文件,os要把这些文件管理起来,需要先描述,再组织。内核中构建了file的文件结构,包含了文件的属性和下一个文件的地址

在这里插入图片描述

用双链表将所有的文件组织了起来,那么怎么管理和访问呢?
进程的PCB结构里面保存文件的数组下标,所有文件结构的地址保存在一个数组中,open打开一个文件后先创建这个文件结构,在数组里找一个没有用的下标写入,然后返回下标,fd在内核中本质就是数组下标,进程的PCB保存了指针,指向了这个文件数组
在这里插入图片描述

文件fd结构

在这里插入图片描述
PCB里保存了files的指针,指向的结构体里保存了一个fd_array数组,类型是file的数组,这个file就保存了文件的所有属性。所以fd的本质就是fd_array的下标,通过fd就可以索引到进程的每个文件

在这里插入图片描述

对于一个文件的操作,系统内部其实是做了很多工作才找到了对应的文件

fd分配规则

关闭fd为0的文件,然后打开创建一个文件,输出fd看看是多少

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    close(0);
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);

    printf("%d\n", fd);

    return 0;
}

在这里插入图片描述

关闭后分配的fd是0
创建文件时,每次都在文件下标数组里遍历,从0开始,找到没有用的返回,所以关掉0后返回了0

重定向

如果我们关闭1的文件,然后创建一个文件,再往标准输出打印内容呢?

int main()
{
    close(1);
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);

    printf("%d\n", fd);
    printf("%d\n", fd);
    printf("%d\n", fd);
    fprintf(stdout, "%d\n", 33);
    fwrite("hello\n",6, 1, stdout);

    return 0;
}

在这里插入图片描述

当打开文件的下标返回了1后,本来往标准输出打印的东西都打印到了文件里,这个现象就叫做重定向

进程会默认打开3个文件,再次打开文件时会在file_struct里遍历,找到没有使用的下标填入这个文件的地址,因为关闭了1下标,所以就将地址填入了1下标。linux下一切皆文件,无论是键盘还是显示器,调用库函数,向stdout打印,并不关心stdout里面是什么,只知道里面封装了文件标识符的值是1,write也只是向1里面的文件打印,所以本应该显示到屏幕里的内容却打印到了文件里

在这里插入图片描述

事实上重定向并不是这样先关闭再打开写入实现的,只是利用了fd分配的规则实现了功能相同的重定向

dup2系统调用

函数原型

#include <unistd.h>
int dup2(int oldfd, int newfd);

函数描述
在这里插入图片描述

newfd作为oldfd的拷贝,意思就是newfd会和oldfd的内容一样,用old拷贝new

这里面有oldfd和newfd,意思就是替换fd里面的内容,假设文件下面是3,想把输出重定向到3里,哪个是old,哪个是new?只需要想清楚1里面的要和3里面的一样才可以重定向,所以1就是new,3就是old

重写一下前面的重定向功能

int main(int argc, char* argv[])
{

    int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    if (fd < 0)
    {
        perror("open");
        return 0;
    }
    
    dup2(fd, 1);
   // printf("hello dup2\n");
    fprintf(stdout, "%s\n", "hello dup2");
  //close(fd);
    return 0;
}

将内容重定向输入到log.txt文件中

虚拟文件系统

虚拟文件系统(VFS)是由Sun microsystems公司在定义网络文件系统(NFS)时创造的。它是一种用于网络环境的分布式文件系统,是允许和操作系统使用不同的文件系统实现的接口。

再进一步理解一切皆文件,这时linux设计的哲学,体现在操作系统的软件设计层面
linux是用c语言写的,如何用c语言实现面向对象,甚至运行时多态

我们都知道类有成员属性和方法,struct可以保存属性,那怎么保存方法。可以利用函数指针来指针一个个功能函数
在这里插入图片描述

在驱动层面,底层不同的 硬件都是外设。对应不同的操作方法,每一个设备的核心访问函数,都可以是read,write,也就是I/O。所有的设备都有自己的read和write,但是,代码的实现一定是不一样的。文件结构里只需要保存函数指针就可以指向不同设备的功能,这就是Os和驱动的耦合

在这里插入图片描述

在linux的文件结构里,有一个结构体指针,指向的结构里面保存很多功能函数的函数指针,用来指向和调用不同的文件功能。这就是一切皆文件的设计和管理模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/535390.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【canvas】canvas基础使用(六):图形阴影

简言 学习使用canvas的阴影属性。 阴影 shadowBlur 阴影模糊 CanvasRenderingContext2D.shadowBlur 是 Canvas 2D API 描述模糊效果程度的属性&#xff1b;它既不对应像素值也不受当前转换矩阵的影响。默认值是 0。 语法&#xff1a; ctx.shadowBlur level; 选项值&#x…

【寒假集训营总结笔记——7道优质好题】

牛客寒假集训营总结笔记——7道优质好题 一、Trie树的应用&#xff1a; 题目链接&#xff1a;Tokitsukaze and Min-Max XOR 1、题意 2、题解 1、首先这道题的答案和元素本身的顺序是无关的&#xff0c;因为假如你选择了一些数字&#xff0c;它是默认必须排好序才能记作是答案…

瑞芯微RK3568/RK3588+鸿蒙,矿鸿工控屏、矿鸿工控板、矿鸿网关,推动矿业数智化变革

4月10日至12日&#xff0c;以“绿色智能创新&#xff0c;携手共赢未来”为主题的第二届中国国际矿业装备与技术展览会在西安举行。信迈科技携矿鸿解决方案及产品亮相&#xff0c;赋能矿山行业数智化升级和国产化改造进程全面提速。 作为华为矿山军团矿鸿生态使能合作伙伴&#…

【LeetCode刷题笔记】LeetCode 1365.有多少小于当前数字的数字

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…

Java8关于Function接口

Java学习-Function接口 1 函数式接口简介和学习地址2 两种常见的函数式接口2.1 Runnable&#xff1a;执行接口&#xff0c;不接收参数&#xff0c;也无返回结果。2.2 Consumer&#xff1a;作为消费接口&#xff0c;接收一个参数&#xff0c;无返回结果。 3 初识3.1 定义Functio…

渗透入门靶场大盘点

写给新手朋友入门&#xff0c;有了靶场丰富自己思路&#xff0c;也巩固自己的技术。当然新手老手都可以玩玩。 这期盘点渗透靶场&#xff0c;排名不分前后&#xff0c;还有其他靶场欢迎留言提出&#xff01;以及在留言当中评论出你最喜欢的靶场并附上理由。 本期是盘点入门必刷…

Java中学习Lambda表达式和stream中parallel并行方法

Java中学习Lambda表达式和stream中parallel并行方法 一、介绍二、整体代码 一、介绍 Lambda表达式是Java中一种方便的函数式编程风格&#xff0c;可以在集合操作中大大简化代码。而parallel()方法则是Java 8中引入的并发处理方法&#xff0c;可以让集合操作更快速高效。下面我…

qt进阶2:windows下可执行程序崩溃生成dmp,定位崩溃问题。

系列文章目录 文章目录 系列文章目录前言一、dmp文件生成二、使用步骤1.代码案例2.运行截图 前言 qt编译的可执行程序在windows下崩溃可生成dmp文件&#xff0c;用于调试定位崩溃原因。 一、dmp文件生成 略 二、使用步骤 1.代码案例 代码如下&#xff08;示例&#xff09;&…

Doodle Jump — 使用FlutterFlame开发游戏真不错!

前言 最近网上冲浪的时候&#xff0c;我偶然发现了一个国外的游戏网站&#xff0c;里面聚集了各种有趣的小游戏&#xff0c;类似于国内的4399。在浏览时&#xff0c;我遇到了一款经典的小游戏&#xff1a;Doodle Jump。上一次玩还是在上小学的时候&#xff0c;那时候父母在厨房…

2024.4.11

1.思维导图 2.指针形式验证大小端存储 #include<myhead.h>int main(int argc, const char *argv[]) {int num 0x12345678;char* ptr (char *)&num;if(*ptr 0x12){printf("big endian\n");}else if(*ptr 0x78){printf("little endian\n");}r…

LangChain-10 Agents langchainhub 共享的提示词Prompt

LangChainHub 的思路真的很好&#xff0c;通过Hub的方式将Prompt 共享起来&#xff0c;大家可以通过很方便的手段&#xff0c;短短的几行代码就可以使用共享的Prompt。 我个人非常看好这个项目。 官方推荐使用LangChainHub&#xff0c;但是它在GitHub已经一年没有更新了&#x…

Linux函数学习 epoll

1、Linux epoll函数 1.1、创建epoll实例 int epoll_create1(int flag); 返回值&#xff1a;-1 失败&#xff0c;非负数 成功 flag &#xff1a;默认传入0 1.2、管理epoll对象 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epfd &#xff1a;e…

.a和.so库文件是什么?

我们在编译开源代码后&#xff0c;通常会生成.a和.so这两个库文件&#xff0c;这两个文件有什么区别&#xff1f;又如何使用&#xff1f; 在 Linux 中&#xff0c;.a 和 .so 文件都是库文件&#xff0c;但它们有一些区别&#xff1a; 静态库文件&#xff08;.a&#xff09;&am…

PaddleDetection 项目使用说明

PaddleDetection 项目使用说明 PaddleDetection 项目使用说明数据集处理相关模块环境搭建 PaddleDetection 项目使用说明 https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.7/configs/ppyoloe/README_cn.md 自己项目&#xff1a; https://download.csdn.net/d…

融中财经专访 | 欧科云链:从跟随行业到引领行业

导读 THECAPITAL 新行业中的经验“老兵”。 本文4089字&#xff0c;约5.8分钟 作者 | 吕敬之 编辑 | 吾人 来源 | 融中财经 &#xff08;ID&#xff1a;thecapital&#xff09; 一个新兴行业从发展到成熟需要几个必要的推手&#xff1a;人才、产品、制度。 Web3.0&…

每天五分钟深度学习:逻辑回归算法的损失函数和代价函数是什么?

本文重点 前面已经学习了逻辑回归的假设函数,训练出模型的关键就是学习出参数w和b,要想学习出这两个参数,此时需要最小化逻辑回归的代价函数才可以训练出w和b。那么本节课我们将学习逻辑回归算法的代价函数是什么? 为什么不能平方差损失函数 线性回归的代价函数我们使用…

Hystrix:实现分布式系统的延迟处理和容错保护机制

文章目录 一、Hystrix的概念与作用1.1、资源隔离1.2、熔断器模式1.3、命令模式1.4、监控和报警 二、Hystrix的使用方法三、总结 一、Hystrix的概念与作用 Hystrix是Netflix开源的一个库&#xff0c;用于处理分布式系统中的延迟和容错。它通过在服务调用之间添加保护机制&#…

Leetcode刷题-字符串详细总结(Java)

字符串 字符串可能在算法处理上面和数组是类似的&#xff0c;但是String和数组的数据结构还是有一些不一样的 1、反转字符串 344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; 双指针的经典应用&#xff0c;两个指针同时向中间移动 public void reverseString(char[…

VMware启动显示“打开虚拟机时出错: 获取该虚拟机的所有权失败”

提示框&#xff08;忘截图了&#xff09;里提示目录C:\Users\mosep\Documents\Virtual Machines\VM-Win10 x64\中的某个文件&#xff08;在我这里好像是VM-Win10 x64.vmx&#xff0c;VM-Win10 x64是我给虚拟机取的名字&#xff09;在被使用中。 找到这个目录&#xff0c;删除.…

Python+Selenium+Unittest 之Unittest4(断言)

在unittest框架的TestCase类也提供了多种断言的方法。 断言常用方法 断言方法检查内容assertEqual(a,b)判断a是否等于b&#xff08;判断两个是不是同一个值&#xff09;assertNotEqual(a, b)判断a是否不等于b&#xff08;判断两个是不是同一个值&#xff09;assertTrue(a)判断a…