一、引言
- 理解设备和文件的关系
- 理解系统调用和系统API等关系
- 掌握系统调用的工作过程
- 掌握系统调用open/read/write/fcntl等使用
二、Linux中的设备管理
1、设备无关性
为了提高操作系统的可适应性和可扩展性,目前几乎所有的操作系统都实现了设备的独立性
概念:Linux采用文件系统管理硬件设备,所有的设备都看成是特殊的文件(目录、套接字也可以是文件),从而将硬件设备的特性及管理细节对用户隐藏起来,实现设备无关性。
如何理解Linux采用文件系统管理硬件设备?
硬件设备是指显示器、鼠标、键盘等,而这些硬件设备在Linux中是以文件系统存在
2、设备管理的特点
- 每个设备都对应文件系统中的一个索引节点,都有一个文件名。对设备的使用类似于对文件的存取。
- 应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。
- 设备驱动程序都是系统内核的一部分,它们必须为系统内核或者它们的子系统提供一个标准的接口,他们使用一些标准的内核服务,如内存分配等。
3、设备分类
- 按设备信息交换单位来分:字符设备、块设备。
- 按设备属主关系:系统设备、用户设备。
- 按设备共享属性来分:独享设备、共享设备(打印机,U盘)。
4、Linux设备操作
设备或文件操作的两种方式:用户编程接口 API、系统调用。如下图
三、系统调用
系统调用是操作系统提供给用户的一组“特殊”接口。用户态主动要求切换到内核态,从而使用内核提供的各项服务,运行后将结果返回给应用程序(内核态–>用户态)。比如,Linux创建进程时中用户态的fork()
会调用到内核态的sys_fork()
和do_fork()
等。
1、函数库调用与系统调用区别
函数库调用 | 系统调用 |
在所有啊ANSIC编译器版本中,C库函数是相同的 | 各个操作系统的系统调用是不同的 |
调用函数库中的一段程序(或函数) | 调用系统内核的服务 |
与用户程序相联系 | 是操作系统的一个入口函数 |
在用户地址空间执行 | 在内核地址空间执行 |
它的运行时间属于“用户时间” | 它的运行时间属于“系统”时间 |
属于过程调用,调用开销较小 | 要在用户空间和内核上下文环境间切换,开销较大 |
C库中大约300个函数 | UNIX中大约有90个系统调用 |
典型的C库调用:system fprintf malloc | 典型的系统调用:chdir fork write br |
- 因为系统调用是直接调用内核函数,所以效率高,但内核函数都是由linux提供的,导致可移植性降低。
- 函数库可以与内核交互,虽然效率会比系统调用低,但提高了可移植性。
2、 C库的文件操作
3、文件描述符fd
每个进程PCB结构中有文件描述符指针,指向files_struct的文件描述符表,记录每个进程打开的文件列表。
系统内核不允许应用程序访问进程的文件描述符表,只返回这些结构的索引即文件描述符ID给应用程序。Linux系统中,应用程序通过这些文件描述符来实现让内核对文件的访问每个进程能够访问的文件描述符是有限制的,通过ulimit –n可以查看。
特殊文件描述符号
- 0 STDIN_FILENO 标准输入流
- 1 STDOUT_FILENO 标准输出流
- 2 STDERR_FILENO 标准错误流
每个进程被加载后,默认打开0,1,2这三个文件描述符。
4、open系统调用
有几种方法可以获得允许访问文件的文件描述符。最常用的是使用open()(打开),首先来看看如何获取系统调用的头文件
在linux下可以通过man +函数名来查看函数头文件、函数原型、返回值等等。
例:输入man 2 open如下图所示
open函数原型:
①int open(const char *path, int flags);
②int open(const char *path, int flags,mode_t mode);参数:
- path:文件的名称,可以包含(绝对、相对)路径
- flags:文件打开模式
- mode:用来规定对该文件的所有者,文件的用户组及系统中其他用户的访问权限;如果想要给这个用户可读可写可执行的权限,则需要在open调用之前设置umask(0),然后mode写成0777
返回值:
打开成功,返回文件描述符;打开失败,返回-1
打开文件的方式(flags的值) :红色部分是主要的
访问权限(mode的值)
6、close系统调用
作用:为了重新利用文件描述符,用close()系统调用释放打开的文件描述符
函数原型:int close(int fd);
参数:
fd :要关闭的文件的文件描述符
返回值:成功返回0,出错返回-1并设置errno
7、read系统调用
作用:一旦有了与一个打开文件描述相连的文件描述符,只要该文件是用O_RDONLY或O_RDWR标志打开的,就可以用read从该文件中读取字节
函数原型:int read(int fd, void *buf, size_t nbytes);
参数:
- fd :想要读的文件的文件描述符
- buf: 指向内存块的指针,从文件中读取来的字节放到这个内存块中
- nbytes: 从该文件复制到buf中的字节个数
返回值:
成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0
8、write系统调用
作用:将数据写到一个文件中
函数原型:int write(int fd,void *buf,size_t nbytes);
参数:
- fd :要写入的文件的文件描述符
- buf: 指向内存块的指针,从这个内存块中读取数据写入到文件中
- nbytes: 要写入文件的字节个数
返回值:成功返回写入的字节数,出错返回-1并设置errno
示例代码
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
void main()
int outfd = 0, r_size = 0;
char buf[ ] = "Hello hml!!";
fd = open("test",O_WONLY | O_TRUNC | O_CREAT,S_IRWXU);
if(outfd>0)
{
r_size = write(outfd,buf,sizeof(buf));
if(r_size>0)
{
printf("成功写入数据!");
}
close(fd);
}
}
出现的问题、原因及解决方法:
1、include头文件出现红线:
原因:因为这里的include不是用win10系统的打包,而是ubuntu(这边导入的<stdio.h>在ubuntu下),如果连不是肯定报红线(分三种情况)
- 1)ip地址连不上虚拟机(要在同一个网段,关闭防火墙,ping命令测试是否连通)
- 2)ip地址连上虚拟机,虚拟机锁屏了(睡眠状态)【通过系统设置--亮度和锁屏--关闭锁屏、多少时间锁屏设置为从不;电源--从不挂起、不显示】;
- 3)VS或者虚拟机长时间运行,内存反应很慢,需要在连接管理器重新连接。vs2019打开选项-跨平台-点击编辑,重新输入密码,重新连接虚拟机
2、cout打印中文,控制台出现中文乱码
原因:win10和ubuntu编码不一致
解决:点击扩展-管理拓展-搜索utf-8,下载安装重启即可
四、 封装函数
封装创建文件和拷贝文件的函数
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//打开
#include <unistd.h>//读
#include <string.h>
#include <stdio.h>
#include <iostream>//cout
using namespace std;
//创建文件并写入初始数据
void caeateFile(char *fileName);
//拷贝文件
void copyFile(char* srcFile, char* dstFile);
int main()
{
caeateFile("/root/projects/1.txt");
copyFile("/root/projects/1.txt", "/root/projects/copy1.txt");
return 0;
}
void caeateFile(char* fileName)
{
int read_fd = 0;
int res = 0;
char buf[60] = {"hello world 你好!\n"};
umask(0);//默认为0022
read_fd = open(fileName,O_CREAT | O_WRONLY,0777);
if (read_fd < 0)//打开文件失败,返回-1
{
perror("open file error");
}
else {
//因为buf的字符实际长度不一定为60,用strlen不用sizeof
res = write(read_fd,buf,strlen(buf));
close(read_fd);
}
cout << "caeateFile finished " << endl;
}
void copyFile(char* srcFile, char* dstFile)
{
int readfd = 0,writefd = 0;
int res1 = 0,res2 = 0;
char buf[50] = { 0 };
umask(0);
//只读的方式打开
readfd = open(srcFile, O_RDONLY, 0777);
//文件存在——只写
writefd = open(dstFile, O_CREAT | O_WRONLY, 0777);
if (readfd < 0 || writefd < 0)//打开文件失败,返回-1
{
perror("open file error");
}
else {
//读取的内容不为空就继续读取
while ((res1 = read(readfd, buf, sizeof(buf))) > 0)
{
cout << "res1 = " << res1 << endl;
res2 = write(writefd, buf, res1);
cout << "res2 = " << res2 << endl;
//读取完一次就清空,为下次做准备
bzero(buf, sizeof(buf));
}
close(readfd);
close(writefd);
}
cout << "copyFile finished " << endl;
}
测试效果:
小试牛刀 :
进入下一篇文章,我们将学习其他的一些系统调用、以及作业讲评等等