一、基本概念和常识
1.linux系统中,几乎一切都是文件,所以程序可以像使用文件那样使用打印机,磁盘文件,串行口和其它设备
2.linux系统中,比较重要的文件设备有三个:
/dev/console(系统控制台),错误信息或诊断信息通常会发送到这个设备
/dev/tty(进程的控制终端的别名(逻辑设备))
/dev/null(空设备),所有写向这个设备的输出都将被丢弃,读这个设备将立刻返回一个文件尾标志
3.设备分为字符设备和块设备,两者的区别在于访问设备时是否需要一次读写一整块
二、系统调用和设备驱动程序
系统调用:通过少量函数达到对文件和设备的访问和控制,这些函数被称为系统调用
设备驱动程序:系统的核心部分,即内核,为一组设备驱动程序,是一组对系统硬件进行控制的底层接口
下面是访问设备驱动程序的底层函数,即系统调用
open:打开文件或设备
read:从打开的文件或设备中读取数据
write:向打开的文件或设备写数据
close:关闭打开的文件或设备
ioctl:把控制信息传递给驱动程序
三、库函数
输入输出的操作直接使用底层系统调用的效率比较低的原因:
1.linux系统中,执行系统调用,必须从用户模式切换到内核模式执行系统调用,执行完毕后,再从内核模式切换到用户模式,用户模式与内核模式的来回频繁切换非常影响系统的性能,减少这种开销的一个方法是,在程序中尽量减少系统调用的次数,并让每次系统调用尽量完成可能多的工作
2.硬件会限制底层系统调用一次能够读写的数据大小
四、底层文件访问
1.一个程序开始运行时,一般会有3个已经打开的文件描述符
0:标准输入
1:标准输出
2:标准错误
2.open系统调用
头文件:
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
函数:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
成功返回一个文件描述符,失败返回-1
pathname参数:文件路径名
flags参数:打开方式
O_RDONLY 只读
O_WDONLY 只写
O_RDWD 读写
O_APPEND 把写入数据追加在文件的末尾
O_TRUNC 把文件长度置为0,丢弃已有的内容
O_CREAT 按照参数mode中给出的访问模式创建文件
O_EXCL 与O_CREAT一起使用,确保创建出文件,使open为一个原子操作,防止两个程序同时创建同一个文件,如果文件已经存在,open调用将失败
mode参数:权限标志
S_IRUSR 文件拥有者可读
S_IWUSR 文件拥有者可写
S_I XUSR 文件拥有者可执行
S_IRGRP 文件组成员可读
S_IWGRP 文件组成员可写
S_IXGRP 文件组成员可执行
S_IROTH 其他用户可读
S_IWOTH 其他用户可写
S_IXOTH 其他用户可执行
3.read系统调用
头文件:
#include<unistd.h>
函数:
ssize_t read(int fd, void *buf, size_t count);
从文件描述符fd相关联的文件里读入count个字节的数据,并把它们放到数据区buf中
4.write系统调用
头文件
#include <unistd.h>
函数:
ssize_t write(int fd, void *buf, size_t count)
把缓冲区buf的前count字节写入文件描述符fd相关联的文件中
5.close系统调用
头文件
#include <unistd.h>
函数:
int close(int fd); //关闭文件描述符fd
成功返回0,失败返回-1
6、实现复制文件功能
版本1源码:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("input param error\n");
return -1;
}
int s_fd = open(argv[1], O_RDONLY);
if (s_fd == -1) {
printf("open %s error\n", argv[1]);
return -1;
}
int d_fd = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (d_fd == -1) {
printf("open %s error\n", argv[2]);
return -1;
}
char ch; <span style="white-space:pre"> </span>//一次读写一个字节
while (true) {
int rdRes = read(s_fd, &ch, 1);
if (rdRes == -1) {
printf("read %s error\n", argv[1]);
return -1;
} else if (rdRes == 0) {
printf("copy %s success\n", argv[1]);
break;
} else if (rdRes == 1) {
int wrRes = write(d_fd, &ch, 1);
if (wrRes != 1) {
printf("write %s error\n", argv[2]);
return -1;
}
} else {
printf("unknow error\n");
return -1;
}
}
return 0;
}
编译,链接,执行程序
版本2源码:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#define BUFF_SIZE 1024
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("input param error\n");
return -1;
}
int s_fd = open(argv[1], O_RDONLY);
if (s_fd == -1) {
printf("open %s error\n", argv[1]);
return -1;
}
int d_fd = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (d_fd == -1) {
printf("open %s error\n", argv[2]);
return -1;
}
char buf[BUFF_SIZE]; <span style="white-space:pre"> </span>//一次读写BUFF_SIZE个字节
while (true) {
int rdRes = read(s_fd, buf, sizeof(buf));
if (rdRes == -1) {
printf("read %s error\n", argv[1]);
return -1;
} else if (rdRes == 0) {
printf("copy %s success\n", argv[1]);
break;
} else if (rdRes > 1) {
int wrRes = write(d_fd, buf, rdRes);
if (wrRes != rdRes) {
printf("write %s error\n", argv[2]);
return -1;
}
} else {
printf("unknow error\n");
return -1;
}
}
return 0;
}
编译,链接,执行程序
结论:
拷贝同一个文件,处理1消耗了4s多时间,处理2消耗了低于10毫秒时间,这是因为处理1每次只读写1个字节,处理2每次读写1024个字节,处理1来回切换用户模式和内核模式的次数比处理2多得多,而来回切换模式是需要消耗时间和cpu的,所以我们应该每次调用系统调用时,每次应尽量多的处理多的数据,从而减少来回地切换用户模式和内核模式的次数,提高系统性能。