一、系统调用
1、概述
系统调用: 就是操作系统内核 提供给用户可以操作内核 一组函数接口。用户 借助 系统调用 操作内核。比如用户可以通过文件系统相关的调用请求系统打开文件、关闭文件或读写文件,可以通过时钟相关的系统调用获得系统时间或设置定时器等。
进程的空间分为:内核空间 和 用户空间。系统调用是属于操作系统内核的一部分,运行在内核态下,通过软件中断切换到内核态。
2、系统调用和库函数区别
系统调用 是内核提供的一组函数接口。
库函数 是第三方(用户提供)的函数接口。
如果库函数没有调用系统调用该库函数不能操作内核。比如:字符串操作函数strcpy, bzero
如果库函数 调用 系统调用该库函数能操作内核。比如:fopen fclose fwrite fget
3、系统调用及IO操作
系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。 文件IO中缓冲区刷新机制需要内核管理,库函数访问文件的时候根据需要,设置不同类型的缓冲区,从而减少了直接调用IO系统调用的次数,提高了访问效率。
二、文件描述符
1、概述
在 Linux的世界里一切设备皆文件,我们可以系统调用I/O 的函 数(I:input,输入;0:output..输出),对文件进行相应的操作 ( open()、close()、write()、read()等。
打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。
Linux将系统调用 打开或新建的文件 用非负整数 来表示。而这个非负整数 就是文件描述 符。
系统会为每一个进程文件 分配一个文件描述符表,管理该进程的所有文件描述符。
系统会为 每一个进程文件 打开三个文件描述符:0,1,2
- 0:标准输入设备(键盘) scanf
- 1:标准输出设备(终端)printf
- 2:标准错误输出 (终端)perror
2、文件描述符表对文件描述符的管理
文件描述符表 是通过 “位图” 来管理文件描述符。使用1024位(0-1023)二进制位管理,位数代表 的就是文件描述符,位上的值1表示打开,值0表示关闭,前三位(0-2)为标准文件描述符。打开文件的时候默认选择最小可用的文件描述符(3)给打开的文件用。
3、查看当前系统文件描述符最大数量ulimit -a
查看命令:ulimit -a
修改命令:ulimit -n 2048
三、文件IO的操作
文件常用操作IO:open close read write
1、打开文件 open
头文件:
#include<sys/type.h>
#include<sys/stat.h>
#include <funtl.h>
相关函数:
函数功能: 打开文件,如果文件不存在则可以选择创建。
//二参数open用于 打开已存在的文件
int open(const char *pathname, int flags);
//三参数open用于 打开不存在的文件 mode是文件在磁盘上的权限
int open(const char *pathname, int flags, mode_t mode);
//返回值:成功返回最小文件描述符,失败返回-1
参数:
- pathname:文件的路径及文件名
- flags:打开文件的行为标志,必选项 O_RDONLY(以只读方式打开, O_WRONLY(以只写方式打开), O_RDWR(以可读可写方式打开)
- mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的磁盘权限
flags 文件的操作权限可选项,和必选项按位或起来:
取值 | 含义 |
O_CREAT | 文件不存在则创建文件,文件存在则先删除再重新创建文件,使用此选项时需使用mode说明文件的权限 |
O_EXCL | 如果同时指定了O_CREAT,且文件已经存在,则出错(防止已有文件被删除) |
O_TRUNC | 如果文件存在,则清空文件内容 |
O_APPEND | 写文件时,数据添加到文件末尾 |
O_NONBLOCK | 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O |
mode文件在磁盘的用户权限:
磁盘文件的用户权限分类:所有拥有者权限(u)、同组用户权限(g)、其他用户权限(o)
权限分为:读(4)、写(2)、执行(1),可以相互组合:
7--->可读可写可执行 6--->可读可写 5--->可读可执行
4--->只读 3--->可写可执行 2--->只写 1--->可执行
mode的权限表示0xxx 每一个x都是4、2、1的组合,如:
前第一个0代表其为八进制数:
0777 所有者、同组用户、其他用户都是可读可写可执行
0666 所有者、同组用户、其他用户都是可读可写
查看mode的系统权限掩码umask:
查看掩码命令:umask
文件的最终权限=给定的权限异或掩码( & ~umask)
umask mode:设置掩码,mode为八进制数
umask -S:查看各组用户的默认操作权限
2、close 关闭文件描述符
头文件:#include<unistd.h>
函数:int close(int fd);
功能: 关闭已打开的文件
参数: fd : 文件描述符,open()的返回值
返回值: 成功:0 失败: -1并存放在全局变量errno中
注意:close工作步骤,先将文件描述符的数量-1,当文件描述符的数量变为0的时候 ,系统回收文件描述符所占的内核空间。
3、向文件写数据write
头文件:#include<unistd.h>
函数:size_t write(int fd, const void *buf, size_t count);
功能: 把指定数目的数据写到文件fd中
参数:
fd : 文件描述符
buf : 数据首地址
count : 写入数据的长度(字节)
返回值: 成功:实际写入数据的字节个数 失败: - 1
4、read读取文件数据
#include<unistd.h>
size_t read(int fd, void *buf, size_t count);
功能: 把指定数目的数据读到内存(缓冲区)
参数:
fd : 文件描述符
buf : 内存首地址
count : 读取的字节个数
返回值: 成功:实际读取到的字节个数,读完文件数据返回0 失败: - 1
5、综合案例:实现cp(copy)命令
举例:cp b.txt test 将b.txt文件拷贝到test目录中
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
int main(int argc,char *argv[])//argc为传参个数,指针数组argv存放每个参数的内容
{
//判断参数是否正确(./a.out a.txt test)
//若正确,argc=3,argv[0]=./a.out,argv[1]=a.txt,argy[2]=test
if(argc!=3)
{
printf("缺少参数");
return 0;
}
//以只读的方式 打开a.txt文件
int fp_r=open(argv[1],O_RDONLY);
if(fp_r<0)
{
perror("open");
return 0;
}
//以写的方式 在test目录中打开a.txt
char filename[32]="";
sprintf(filename,"%s/%s",argv[2],argv[1]);
int fp_w=read(filename,O_WRONLY|O_CREAT,0666);
if(fp_w<0)
{
perror("open");
return 0;
}
//不同的从fd_r中读取文件数据 写入fd_w文件中
while(1)
{
unsigned char buf[128]="";
int len=read(fp_r,buf,sizeof(buf));
if(len<=0)
break;
printf("读取的数据大小为%d",len);
write(fp_w,buf,sizeof(buf));
}
//关闭文件
close(fp_r);
close(fp_w);
return 0;
}
四、文件的阻塞特性
对于一些设备文件读写操作,如管道,套接字,标准设备文件,默认缓冲区没有数据读会带阻塞,默认缓存区满的状态,写也会阻塞。
文件描述符决定阻塞和非阻塞,而不是read write函数。文件描述符默认为阻塞的。
1、open打开文件 默认为阻塞特性
文件描述符 事先不存在用open。如果从终端输入的数据没有换行符,默认调用read读终端设备就会阻塞。
2、 open打开文件 设置为非阻塞特性
3、通过fcntl设置文件的阻塞特性
文件描述符事先存在用funtl。 功能:改变已打开的文件性质,fcntl针对描述符提供控制。
头文件:
#include<unistd.h>
#include<fcntl.h>
函数:
int fcntl(int fd, int cmd, ... /* arg */ );
参数:
fd:操作的文件描述符
cmd:操作方式
arg:针对cmd的值,fcntl能够接受第三个参数int arg。
返回值: 成功:返回某个其他值 失败:-1
fcntl函数有5种cmd功能:
- 复制一个现有的描述符(cmd=F_DUPFD)
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
- 获得/设置记录锁(cmd=F_GETLK, F_SETLK或F_SETLKW)
设置一个存在的文件描述符的阻塞特性的步骤
- fcntl获取文件描述符的状态标记
- 修改 获取到的 文件描述符的状态标记
- 将修改后的状态标记 使用fcntl设置到文件描述符中
五、获取文件的状态信息
头文件:
#include<sys/type.h>
#include<sys/stat.h>
#include<unistd.h>
函数:
int stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
功能: 获取文件状态信息
stat和lstat的区别: 当文件是一个符号链接时,lstat返回的是该符号链接本身的信息; 而stat返回的是该链接指向的文件的信息。
参数:
path/pathname:文件名
buf:保存文件信息的结构体
返回值: 成功: 0 失败: -1
struct 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; //最后一次改变时间(指属性)
};
st_mode(16位整数)参数说明 :
由于上述形式的测试是普通的,因此POSIX定义了其他宏,以便更简洁地编写st_mode中的文件类型测试:
S_ISREG(st_mode) | 是否是普通文件(regular file) |
S_ISDIR(st_mode) | 是否是目录文件(directory) |
S_ISCHR(st_mode) | 是否是字符设备(character device) |
S_ISBLK(st_mode) | 块设备(block device) |
S_ISFIFO(st_mode) | 管道 |
S_ISLNK(st_mode) | 符号链接(软连接) |
st_mode控件的文件模式组件定义了下列掩码值:
六、文件目录操作
读取一个目录下的所有文件名
1、打开目录(得到文件目录的句柄)
头文件:
#include<sys/types.h>
#include<dirent.h>
函数:
DIR *opendir(const char *name);
功能:打开一个目录
参数name:目录名
返回值: 成功:返回指向该目录结构体指针 失败:NULL
2、读取目录
#include<dirent.h>
struct dirent *readdir(DIR *dirp);
功能:读取目录 调用一次只能读取一个文件
参数 dirp:opendir的返回值
返回值: 成功:目录结构体指针 失败:NULL
结构体dirent说明:
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相关数据:
3、关闭目录
头文件:
#include<sys/types.h>
#include<dirent.h>
函数:
int closedir(DIR *dirp);
功能:关闭目录
参数dirp:opendir返回的指针
返回值: 成功:0 失败:-1