Day02 Liunx高级程序设计2-文件IO
系统调用
概念
是操作系统提供给用户使其可以操作内核提供服务的一组函数接口
用户态和内核态
其中
ring 0
权限最高,可以使用所有
CPU
指令,
ring 3
权限最低,仅能使用
常规
CPU
指令,这个级别的权限不能使用访问硬件资源的指令,比如
IO
读写、网卡
访问、申请内存都不行,都没有权限
Linux
系统内核采用了:
ring 0
和
ring 3
这
2
个权限
ring 0:
内核态
,
完全在 操作系统内核 中运行,由专门的 内核线程 在
CPU
中
执行其任务
ring 3:
用户态
,
在 应用程序 中运行,由 用户线程 在
CPU
中执行其任务
Linux
系统中所有对硬件资源的操作都必须在 内核态 状态下执行,比如
IO
的
读写,网络的操作
区别:
1,
用户态的代码必须由 用户线程 去执行、内核态的代码必须由 内核线程 去执行
2,
用户态、内核态 或者说 用户线程、内核线程 可以使用的资源是不同的,尤体现在
内存资源上。
Linux
内核对每一个进程都会分配
4G
虚拟内存空间地址
用户态:
-->
只能操作
0-3G
的内存地址
内核态:
--> 0-4G
的内存地址都可以操作,尤其是对
3-4G
的高位地址必须由
内核态去操作,因为所有进程的
3-4G
的高位地址使用的都是同一块、专门留给 系统
内核 使用的
1G
物理内存
3.
所有对 硬件资源、系统内核数据 的访问都必须由内核态去执行
如何切换内核态
使用软件中断
软件中断与硬件中断
软件中断
软件中断是由软件程序触发的中断,如系统调用、软中断、异常等。软件中断不是
由硬件设备触发的,而是由软件程序主动发起的,一般用于系统调用、进程切换、异常
处理等任务。软件中断需要在程序中进行调用,其响应速度和实时性相对较差,但是具
有灵活性和可控性高的特点。
如程序中出现的内存溢出
,
数组下标越界等
硬件中断
硬件中断是由硬件设备触发的中断,如时钟中断、串口接收中断、外部中断等。当
硬件设备有数据或事件需要处理时,会向
CPU
发送一个中断请求,
CPU
在收到中断请求
后,会立即暂停当前正在执行的任务,进入中断处理程序中处理中断请求。硬件中断具
有实时性强、可靠性高、处理速度快等特点。
如当点击按钮扫描系统高低电频时等
系统调用与库函数的关系
库函数可以调用系统调用提供的接口,也可以不调用系统提供的接口
如
不调用系统调用的库函数
:strcpy,bzero
等
调用系统调用的库函数
:fread,printf
等
注意:
系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核
代码时,
CPU
工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转
入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时
间。
文件操作
文件描述符概念
文件描述符是一个非负整数
,
代表已打开的文件。
每一个进程都会创建一张文件描述符表 记录的是当前进程打开的所有文件描述符。
每一个进程默认打开三个文件描述符:
0(
标准输入设备
scanf)
1(
标准输出设备
printf)
2(
标准错误输入设备
perror)
。
新打开的文件描述符 为最小可用文件描述符。
扩展
ulimit
是一个计算机命令,用于
shell
启动进程所占用的资源,可用于修改系统资源限
制。使用
ulimit
命令用于临时修改资源限制,如果需要永久修改需要将设置写入配置文
件
/etc/security/limits.conf
。
ulimit -a
查看
open files
打开的文件最大数。
ulimit -n
最大数 设置
open files
打开的文件最大数
文件读写
文件磁盘权限
第一位说是文件还是文件夹
2~4
位说明所有者权限
5~7
位说明同组用户权限
8~10
位说明其他用户权限
r 4
w 2
x 1
注意
man 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);
参数
pathname:打开的文件地址
flags:代码操作文件的权限
必选项
O_RDONLY 以只读的方式打开
O_WRONLY 以只写的方式打开
O_RDWR 以可读、可写的方式打开
可选项
O_CREAT 文件不存在则创建文件,使用此选项时需使用 mode
说明文件的权限
O_EXCL 如果同时指定了 O_CREAT
,且文件已经存在
,
则打开
,
如果文件不存在则新建
O_TRUNC 如果文件存在,则清空文件内容
O_APPEND 写文件时,数据添加到文件末尾
O_NONBLOCK 对于设备文件,
以
O_NONBLOCK
方式打开可以做非阻塞
I/O
mode:文件在磁盘中的权限
格式:
0ddd
d的取值:4(
可读
),2(
可写
),1(
可执行
)
第一个d:所有者权限
第二个d:同组用户权限
第三个d:其他用户权限
如果需要可读可写就是6,
可读可执行
5
等
如:
0666:所有者可读可写,
同组用户可读可写
,
其他用户可读可写
0765:所有者可读可写可执行,
同组用户可读可写
,
其他用户可读可执行
返回值:
成功:得到最小可用的文件描述符
失败:-1
经验
:
操作已有文件使用两参
新建文件使用三参
close
关闭文件
所需头文件
#include <unistd.h>
函数
int close(int fd);
参数
关闭的文件描述符
返回值
成功:
0
失败:
-1,
并设置
errno
示例
:
以读的方式打开关闭文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//1打开文件
// man 2 系统调用函数名
// int open(const char *pathname, int flags);
// int open(const char *pathname, int flags, mode_t mode);
int fileTag = open("a.txt",O_RDONLY);
if (fileTag < 0)
{
printf("读取文件不存在,文件标识符为:%d\n",fileTag);
return 0;
}
printf("文件打开成功,文件标识符为:%d\n",fileTag);
int tag = close(fileTag);
if (tag < 0)
{
printf("关闭文件失败\n");
}
else
{
printf("关闭文件成功\n");
}
return 0;
}
示例
:
以写的方式打开关闭文件
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd_w = open("text.txt",O_WRONLY | O_CREAT,0666);
if(fd_w < 0)
{
printf("文件打开失败\n");
return 0;
}
printf("文件打开成功,文件标识符是:%d\n",fd_w);
int tag = close(fd_w);
if(tag < 0)
{
printf("文件关闭失败\n");
}
else
{
printf("文件关闭成功\n");
}
return 0;
}
write
写入
所需头文件
#include <unistd.h>
函数
ssize_t write(int fd, const void *buf, size_t count);
int len = write(filename,str,sizeof(str)-1);
参数
fd:写入的文件描述符
buf:写入的内容首地址
count:写入的长度
,
单位字节
返回值
成功:
返回写入的内容的长度
,
单位字节
失败:-1
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int filename = open("text.txt",O_WRONLY | O_CREAT | O_APPEND,0766);
if(filename < 0)
{
printf("文件打开失败\n");
return 0;
}
printf("文件打开成功,文件标识符是:%d\n",filename);
char str[] = "hello";
int len = write(filename,str,sizeof(str)-1);
if(len < 0)
{
printf("文件写入失败\n");
}
else {
printf("文件写入成功len=%d,%d\n",len,sizeof(str));
}
int tag = close(filename);
if(tag < 0)
{
printf("文件关闭失败\n");
}
else
{
printf("文件关闭成功\n");
}
return 0;
}
read
读取
所需头
#include <unistd.h>
函数
ssize_t read(int fd, void *buf, size_t count);
参数:
fd:文件描述符
buf:内存首地址
count:读取的字节个数
返回值:
成功:
实际读取到的字节个数
失败:-1
示例:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd_r = open("text.txt",O_RDONLY);//读文件
if(fd_r < 0)
{
printf("文件打开失败\n");
return 0;
}
printf("文件打开成功\n");
char huange[] = "hello";
int len = read(fd_r,huange,sizeof(huange)-1);
if(len < 0)
{
printf("读取文件失败\n");
}
else{
printf("读取文件成功len = %d,%d\n",len,sizeof(huange));
}
int tag = close(fd_r);
if(tag < 0)
{
printf("文件关闭失败\n");
}
else{
printf("文件关闭成功\n");
}
return 0;
}
示例
:
文件复制
示例:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd_r = open("text.txt",O_RDONLY);
if(fd_r < 0)
{
printf("文件打开失败\n");
}
int fd_w = open("b.txt",O_WRONLY | O_CREAT | O_APPEND,0766);
if(fd_w < 0)
{
printf("文件打开失败\n");
}
while(1)
{
char str[3] = "";
int len1 = read(fd_r,str,sizeof(str));
int len2 = write(fd_w,str,len1);
if(len1 < sizeof(str))
{
break;
}
}
int tag = close(fd_r);
if(tag < 0)
{
printf("文件fd_r关闭失败\n");
}
int tag2 = close(fd_w);
if(tag2 < 0)
{
printf("文件fd_w关闭失败\n");
}
return 0;
}
文件的阻塞特性
概述:
read
默认为阻塞。如果读不到数据,将阻塞不继续执行 知道有数据可读,才继续往下
执行。
非阻塞特性:如果没数据,立即返回,继续执行。
注意
:
阻塞与非阻塞是对于文件而言的
,而不是指
read
、
write
等的属性。
示例:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int filename = open("/dev/tty",O_RDONLY | O_NONBLOCK);
if(filename < 0)
{
printf("文件打开失败\n");
return 0;
}
char str[] = "helo";
// int len = read(filename,str,sizeof(str)-1);
// if(len < 0)
// {
// printf("文件读取成功,标识符是:%d\n",filename);
// }
// printf("文件读取成功,标识符是:%d\n",filename);
printf("开始读取\n");
read(filename,str,sizeof(str));
printf("读取结束\n");
return 0;
}
非阻塞状态:直接将程序执行完,不会等
阻塞状态(默认):如果读不到数据,将阻塞不继续执行 知道有数据可读,才继续往下执行。
问题:
通过
open
打开的文件可以设置非阻塞
,
但是如果不是通过
open
打开的文件怎么办
?
通过
fcntl
函数来解决
fcntl
函数
作用:
针对已经存在的文件描述符设置阻塞状态
所需头文件
#include <unistd.h>
#include <fcntl.h>
函数
:
int fcntl(int fd, int cmd, ... /* arg */);
功能
:
改变已打开的文件性质,fcntl
针对描述符提供控制。
参数:
fd:操作的文件描述符
cmd:操作方式
arg:针对
cmd
的值,
fcntl
能够接受第三个参数
int arg
。
返回值:
成功:返回某个其他值
失败:-1
fcntl
函数有
5
种功能:
1)
复制一个现有的描述符(
cmd=F_DUPFD
)
2)
获得/设置文件描述符标记
(cmd=F_GETFD
或
F_SETFD)
3)
获得/设置文件状态标记
(cmd=F_GETFL
或
F_SETFL)
4)
获得/设置异步
I/O
所有权
(cmd=F_GETOWN
或
F_SETOWN)
5)
获得/设置记录锁
(cmd=FGETLK, F_SETLK
或
F_SETLKW)
使用步骤
1,
获取文件状态标记
2,
将得到的文件状态标记设置为非阻塞
3,
将修改后的文件非阻塞状态标记
,
设置到当前文件描述符中
示例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
//1,获取文件标记状态
int status = fcntl(0,F_GETFL);
//2修改状态为非阻塞状态
status = status | O_NONBLOCK;
//3,设置文件标记状态为非阻塞状态
fcntl(0,F_SETFL,status);
char buf[32]="";
printf("开始读取\n");
//0(标准输入设备scanf)
int len = read(0,buf,sizeof(buf));
printf("结束读取,读取到的内容为:%s\n",buf);
return 0;
}
文件状态
语法:
作用
:
获取文件状态信息
所需头
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
函数
int stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
参数
1参
:
文件地址
2参
:
保存文件信息的结构体
返回值
0:成功
-1:失败
stat
与
lstat
的区别
当文件是一个符号链接时
lstat 返回的是该符号链接本身的信息
,(链接)
stat 返回的是该链接指向的文件的信息。(文件本身)
stat
结构体解释:
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为 1
uid_t st_uid; //用户 ID
gid_t st_gid; //组 ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的 I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
stat
结构体
st_mode
属性
一个由16
个字节组成
,
简称
16
位
0~2其他人权限
3~5所属组权限
6~8所有者权限
12~15文件类型
具体参考下图
存储权限
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
示例:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
struct stat st;
stat("text.txt",&st);
if(S_ISREG(st.st_mode))
{
printf("普通文件\n");
}
else if(S_ISDIR(st.st_mode))
{
printf("目录文件\n");
}
if((st.st_mode & S_IRUSR) == S_IRUSR)
{
printf("所有者可读权限\n");
}
if((st.st_mode & S_IWUSR) == S_IWUSR)
{
printf("所有者可写权限\n");
}
if((st.st_mode & S_IXUSR) == S_IXUSR)
{
printf("所有者可执行权限\n");
}
printf("文件大小:%d\n",st.st_size);
return 0;
}
目录操作
语法
打开目录:
作用
:
打开目录
opendir
所有头文件
:
#include <sys/types.h>
#include <dirent.h>
函数
:
DIR *opendir(const char *name);
参数:
name:目录名
返回值:
成功:返回指向该目录结构体指针(DIR *)
失败:NULL
DIR:
中文名称句柄
,
其实就是目录的结构体指针
读取目录
作用
:
读取目录
readdir
所需头文件
#include <dirent.h>
函数
struct dirent *readdir(DIR *dirp);
参数:
dirp:read
dir
的返回值
返回值:
成功:目录结构体指针
失败:NULL
注意
:
一次读取一个文件。
相关结果体
相关结构体说明:
struct dirent
{
ino_t d_ino; // 此目录进入点的
inode
off_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度
,
不包含
NULL
字符
unsigned char d_type; // d_type 所指的文件类型
char d_name[256]; // 文件名
};
d_type
说明
:
DT_BLK这是一个块设备。
(
块设备如
:
磁盘
)
DT_CHR这是一个字符设备。
(
字符设备如
:
键盘
,
打印机
)
DT_DIR这是一个目录。
DT_FIFO这是一个命名管道(
FIFO
)。
DT_LNK这是一个符号链接。
DT_REG这是一个常规文件。
DT_SOCK这是一个
UNIX
域套接字。
DT_UNKNOWN文件类型未知。
关闭目录
作用
:
关闭目录
closedir
所需头文件
#include <sys/types.h>
#include <dirent.h>
函数
int closedir(DIR *dirp);
参数:
dirp:opendir 返回的指针
返回值:
成功:0
失败:-1
示例
:
扫描文件目录
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
DIR *dir = opendir("./");
if(dir == NULL)
{
printf("打开文件夹失败");
return 0;
}
while(1)
{
struct dirent * d = readdir(dir);
if (d == NULL)
{
break;
}
if (d->d_type == DT_DIR)
{
printf("%s是个文件夹\n",d->d_name);
}
else if(d->d_type == DT_REG)
{
printf("%s是个普通文件\n",d->d_name);
}
}
return 0;
}
示例
2:
扫描文件目录
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
void blDIR(char *path)
{
char filedir[256] = "";
strcpy(filedir,path);
DIR* dir = opendir(filedir);
if(dir == NULL)
{
printf("文件夹打开失败\n");
return;
}
while(1)
{
struct dirent*d = readdir(dir);
if(d == NULL)
{
break;
}
if(d->d_type == DT_DIR && strcmp(d->d_name,".") != 0 && strcmp(d->d_name,"..")!=0)
{
printf("%s是个文件夹\n",d->d_name);
strcat(filedir,"/");
strcat(filedir,d->d_name);
blDIR(filedir);
}
else if(d->d_type == DT_REG)
{
printf("%s是一个普通文件\n",d->d_name);
return 0;
}
}
closedir(dir);
}
int main(int argc, char const *argv[])
{
blDIR("./");
return 0;
}