一.系统调用
1.1 系统调用概述
系统调用,操作系统提供给用户程序调用的一组API接口,是应用程序同系统之间数据交互的桥梁。
Linux 的运行空间分为内核空间与用户空间,它们各自运行在不同的级别中,逻辑上相互隔离。
用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数。
但存在很多情况,用户进程需要获得系统服务,那就要利用系统调用了。
1.2 用户态和内核态
CPU可以在不同的特权级别下运行,相应的操作系统也有不同的运行级别,那就是用户态和内核态。
内核态下的运行进程可以毫无限制的访问各种资源,而在用户态下的用户进程的各种操作则都有着限制,例如用户态下的用户进程不能随意访问内存。
属于内核的系统调用一定是运行在内核态下,他是操作系统内核的一部分的,以软件中断的方式从用户态切换到内核态。
1.3 系统调用和库函数
Linux 下对文件操作有两种方式:系统调用(system call)和库函数调用(Library functions)。
库函数调用(Library functions)则分为需要系统调用和不需要系统调用两种。
系统调用是需要消耗时间的,程序中频繁的使用系统调用会降低程序的运行效率,所以库函数访问文件的时候根据需要,会设置不同类型的缓冲区,从而减少了直接调用 IO 系统调用的次数,提高了访问效率。
1.4 虚拟地址空间
每个进程都会分配虚拟地址空间,在32位机器上,该地址空间为4G 。
二.文件
2.1 概述
Linux中,一切皆文件。文件为操作系统服务和设备提供了一个简单而一致的接口。这意味着程序完全可以像使用文件那样使用磁盘文件、串行口、打印机和其他设备。
文件通常由两部分组成: 内容 + 属性,即管理信息,包括文件的创建修改日期和访问权限等。属性均保存在 inode 节点中。
inode - "索引节点",储存文件的元信息,比如文件的创建者、文件的创建日期、文件的长度和文件在磁盘上存放的位置等等。每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
目录是用于保存其他文件的节点号和名字的文件,每个数据项为指向文件节点的链接。
2.2 文件描述符
我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。
打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。
程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。
1)0: 标准输入 STDIN_FILENO
2)1: 标准输出 STDOUT_FILENO
3)2: 标准错误 STDERR_FILENO
三.常用文件IO操作
3.1 open、close函数
open函数:创建一个新的文件描述符(文件或设备)。
#include
#include
#include
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
mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
成功:成功返回打开的文件描述符
失败:-1
flags必选项:
取值 | 说明 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打开 |
O_RDWR | 以可读、可写的方式打开 |
可选项,需要和必选项结合使用
取值 | 说明 | |
---|---|---|
O_CREAT | 文件不存在则创建文件,使用此选项时需使用mode说明文件的权限 | |
O_EXCL | 如果同时指定了O_CREAT,且文件已经存在,则出错 | |
O_TRUNC | 如果文件存在,则清空文件内容 | |
O_APPEND | 写文件时,数据添加到文件末尾 | |
O_NONBLOCK | 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O |
close函数
#include
int close(int fd);
功能:
关闭已打开的文件
参数:
fd : 文件描述符,open()的返回值
返回值:
成功:0
失败: -1, 并设置errno
示例代码
#include
#include
#include
#include
#include
int main(){
// open 函数和close函数示例
int fd = open("helloworld.log",O_RDWR | O_CREAT,0644);
if(-1 == fd){
perror("open error");
return -1;
}
printf("文件描述符 【%d】\n",fd);
close(fd);
return 0;
}
3.2 write
#include
ssize_t write(int fd, const void *buf, size_t count);
功能:
把指定数目的数据写到文件(fd)
参数:
fd : 文件描述符
buf : 数据首地址
count : 写入数据的长度(字节)
返回值:
成功:实际写入数据的字节个数
失败: - 1
示例代码
#include
#include
#include
#include
#include
#include
int main(){
// write 函数和read函数
int fd = open("new.log",O_RDWR | O_CREAT,0644);
if(-1 == fd){
perror("open error");
return -1;
}
// 写入数据
char *str = "helloworld";
write(fd,str,strlen(str));
close(fd);
return 0;
}
3.3 read
#include
ssize_t read(int fd, void *buf, size_t count);
功能:
把指定数目的数据读到内存(缓冲区)
参数:
fd : 文件描述符
buf : 内存首地址
count : 读取的字节个数
返回值:
成功:实际读取到的字节个数
失败: - 1
示例代码
#include
#include
#include
#include
#include
#include
int main(){
// read函数
int fd = -1;
int ret = -1;
char buf[128];
fd = open("new.log",O_RDWR | O_CREAT,0644);
if(-1 == fd){
perror("open error");
return -1;
}
memset(buf,0,128);
ret = read(fd,buf,128); // 最多读取128个字节保存到buf中
if(-1 == ret){
perror("read error");
return -1;
}
printf("读取到内容[%s]\n",buf);
close(fd);
return 0;
}
3.4 lseek函数
#include
#include
off_t lseek(int fd, off_t offset, int whence);
功能:
改变文件的偏移量
参数:
fd:文件描述符
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节
返回值:
若lseek成功执行, 则返回新的偏移量
如果失败, 返回-1
代码示例
#include
#include
#include
#include
#include
#include
int main(){
// read函数
int fd = -1;
int ret = -1;
char buf[128];
fd = open("new.log",O_RDWR | O_CREAT,0644);
if(-1 == fd){
perror("open error");
return -1;
}
memset(buf,0,128);
ret = read(fd,buf,128); // 最多读取128个字节保存到buf中
if(-1 == ret){
perror("read error");
return -1;
}
printf("读取到内容[%s]\n",buf);
// 此时文件已经到末尾,移动文件指针到开头再次读取
lseek(fd,0,SEEK_SET);
memset(buf,0,128);
ret = read(fd,buf,128); // 最多读取128个字节保存到buf中
if(-1 == ret){
perror("read error");
return -1;
}
printf("读取到内容[%s]\n",buf);
close(fd);
return 0;
}
3.5 perror和error
errno是一个全局变量, 当系统调用后若出错会将errno进行设置,
常用错误代码的取值和含义如下:
l EPERM: 操作不允许
l ENOENT: 文件或目录不存在。
l EINTR: 系统调用被中断。
l EAGAIN: 重试,下次有可能成功!
l EBADF: 文件描述符失效或本身无效
l EIO: I/O错误。
l EBUSY: 设备或资源忙。
l EEXIST: 文件存在。
l EINVL: 无效参数。
l EMFILE: 打开的文件过多。
l ENODEV: 设备不存在。
l EISDIR: 是一个目录。
l ENOTDIR: 不是一个目录。
两个有效函数可报告出现的错误: strerror 和 perror。
perror 函数也把errno 变量中报告的当前错误映射成一个字符串,并把它输出到标准错误输出流。
strerror函数把错误代号映射成一个字符串,该字符串对发生的错误类型进行说明。
3.6 阻塞和非阻塞
阻塞和非阻塞是文件的属性,譬如
read函数在读取普通文件时,是非阻塞的,
read函数在读取设备文件时,是阻塞的,
read函数在读取管道、套接字时,也是阻塞的
普通文件不用说,上面代码可以验证,
读取设备文件,示例如下
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
//读标准输入
char buf[1024];
memset(buf, 0, sizeof(buf));
int n = read(STDIN_FILENO, buf, sizeof(buf));
printf("n==[%d], buf==[%s]\n", n, buf);
return 0;
}
四.文件操作汇总
4.1 fstat、stat和lstat系统调用
fstat 作用:获取文件的状态信息,该信息将会写入一个buf中,buf的地址会以参数的形式传递给fstat。
stat 和 lstat 均通过文件名查询状态信息,当文件名是符号链接时,lstat返回的时符号链接本身的信息,而stat 返回的时改链接指向的文件的信息。
#include
#include
#include
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
// 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 标志有一系列相关的宏,定义见 sys/stat.h 中
stat示例代码
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
//int stat(const char *pathname, struct stat *buf);
//获取文件属性
struct stat sb;
stat(argv[1], &sb);
//获取文件类型
if ((sb.st_mode & S_IFMT) == S_IFREG)
{
printf("普通文件\n");
}
else if((sb.st_mode & S_IFMT) ==S_IFDIR)
{
printf("目录文件\n");
}
else if((sb.st_mode & S_IFMT) ==S_IFLNK)
{
printf("连接文件\n");
}
if (S_ISREG(sb.st_mode))
{
printf("普通文件\n");
}
else if(S_ISDIR(sb.st_mode))
{
printf("目录文件\n");
}
else if(S_ISLNK(sb.st_mode))
{
printf("连接文件\n");
}
//判断文件权限
if(sb.st_mode & S_IROTH)
{
printf("---R----");
}
if(sb.st_mode & S_IWOTH)
{
printf("---W----");
}
if(sb.st_mode & S_IXOTH)
{
printf("---X----");
}
printf("\n");
return 0;
}
通过man 2 stat 可以找到代码示例。
4.2 access 函数
#include
int access(const char *pathname, int mode);
功能:测试指定文件是否具有某种属性
参数:
pathname:文件名
mode:文件权限,4种权限
R_OK: 是否有读权限
W_OK: 是否有写权限
X_OK: 是否有执行权限
F_OK: 测试文件是否存在
返回值:
0: 有某种权限,或者文件存在
-1:没有,或文件不存在
示例
#include
#include
int main(int argc, char **argv)
{
// 检查参数个数
if (2 != argc)
{
printf("usage: ./a.out filename\n");
return 1;
}
//判断文件是否存在
if (access(argv[1], F_OK) == 0)
{
printf("文件存在...\n");
}
else
{
printf("文件不存在....\n");
}
//判断文件是否有读的权限
if (access(argv[1], R_OK) == 0)
{
printf("可以读\n");
}
else
{
printf("不可以读\n");
}
return 0;
}
4.3 chmod函数
#include
int chmod(const char *pathname, mode_t mode);
功能:修改文件权限
参数:
filename:文件名
mode:权限(8进制数)
返回值:
成功:0
失败:-1
4.4 chown 函数
#include
int chown(const char *pathname, uid_t owner, gid_t group);
功能:修改文件所有者和所属组
参数:
pathname:文件或目录名
owner:文件所有者id,通过查看 /etc/passwd 得到所有者id
group:文件所属组id,通过查看 /etc/group 得到用户组id
返回值:
成功:0
失败:-1
4.5 truncate
#include
#include
int truncate(const char *path, off_t length);
功能:修改文件大小
参数:
path:文件文件名字
length:指定的文件大小
a)比原来小, 删掉后边的部分
b)比原来大, 向后拓展
返回值:
成功:0
失败:-1
4.6 link函数
#include
int link(const char *oldpath, const char *newpath);
功能:创建一个硬链接
参数:
oldpath:源文件名字
newpath:硬链接名字
返回值:
成功:0
失败:-1
4.7 symlink
#include
int symlink(const char *target, const char *linkpath);
功能:创建一个软链接
参数:
target:源文件名字
linkpath:软链接名字
返回值:
成功:0
失败:-1
4.8 readlink函数
#include
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:读软连接对应的文件名,不是读内容(该函数只能读软链接文件)
参数:
pathname:软连接名
buf:存放软件对应的文件名
bufsiz :缓冲区大小(第二个参数存放的最大字节数)
返回值:
成功:>0,读到buf中的字符个数
失败:-1
4.9 unlink函数
#include
int unlink(const char *pathname);
功能:删除一个文件(软硬链接文件)
参数:
pathname:删除的文件名字
返回值:
成功:0
失败:-1
4.10 rename函数
#include
int rename(const char *oldpath, const char *newpath);
功能:把oldpath的文件名改为newpath
参数:
oldpath:旧文件名
newpath:新文件名
返回值:
成功:0
失败:-1
五.文件描述符操作
5.1 dup 和 dup2
dup
#include
int dup(int oldfd);
功能:
通过 oldfd 复制出一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符,最终 oldfd 和新的文件描述符都指向同一个文件。
参数:
oldfd : 需要复制的文件描述符 oldfd
返回值:
成功:新文件描述符
失败: -1
示例
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd = -1;
int newfd = -1;
// 打开一个文件
fd = open("new.log", O_RDWR | O_CREAT, 0644);
if(-1 == fd){
perror("open");
return 1;
}
// 复制文件描述符
newfd = dup(fd);
if(-1 == newfd){
perror("dup");
return 1;
}
printf("fd:%d newfd:%d\n",fd,newfd);
// 关闭文件
close(fd);
close(newfd);
return 0;
}
dup2
#include
int dup2(int oldfd, int newfd);
功能:
通过 oldfd 复制出一个新的文件描述符 newfd,如果成功,newfd 和函数返回值是同一个返回值,最终 oldfd 和新的文件描述符 newfd 都指向同一个文件。
参数:
oldfd : 需要复制的文件描述符
newfd : 新的文件描述符,这个描述符可以人为指定一个合法数字(0 - 1023),如果指定的数字已经被占用(和某个文件有关联),此函数会自动关闭 close() 断开这个数字和某个文件的关联,再来使用这个合法数字。
返回值:
成功:返回 newfd
失败:返回 -1
示例
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd = -1;
int newfd = -1;
// 打开一个文件
fd = open("new.log", O_RDWR | O_CREAT, 0644);
if(-1 == fd){
perror("open");
return 1;
}
// 复制文件描述符
newfd = 10;
newfd = dup2(fd,newfd);
if(-1 == newfd){
perror("dup");
return 1;
}
printf("fd:%d newfd:%d\n",fd,newfd);
// 关闭文件
close(fd);
close(newfd);
return 0;
}
5.2 fcnlt函数
#include
#include
int fcntl(int fd, int cmd, ... /* arg */);
功能:改变已打开的文件性质,fcntl针对描述符提供控制。
参数:
fd:操作的文件描述符
cmd:操作方式
arg:针对cmd的值,fcntl能够接受第三个参数int arg。
返回值:
成功:返回某个其他值
失败:-1
fcntl代码示例: 复制一个现有的描述符(cmd=F_DUPFD)
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd = -1;
int newfd = -1;
// 打开一个文件
fd = open("new.log", O_RDWR | O_CREAT, 0644);
if(-1 == fd){
perror("open");
return 1;
}
// 复制文件描述符
newfd = fcntl(fd,F_DUPFD,10);
printf("fd:%d newfd:%d\n",fd,newfd);
// 关闭文件
close(fd);
close(newfd);
return 0;
}
示例 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd = -1;
int ret = -1;
// 打开一个文件
fd = open("new.log", O_RDWR | O_CREAT, 0644);
if(-1 == fd){
perror("open");
return 1;
}
// 获取文件状态标志
ret = fcntl(fd,F_GETFL);
if(-1 == ret){
perror("fcntl");
return 1;
}
if(ret & O_NONBLOCK){
printf("文件为非阻塞\n");
}
else{
printf("文件为阻塞");
}
// 设置文件状态标志
// 设置为非阻塞
ret |= O_NONBLOCK;
ret = fcntl(fd,F_SETFL,ret);
if (-1 == ret)
{
perror("fcntl");
return 1;
}
// 获取文件状态标志
ret = fcntl(fd,F_GETFL);
if(-1 == ret){
perror("fcntl");
return 1;
}
if(ret & O_NONBLOCK){
printf("文件为非阻塞\n");
}
else{
printf("文件为阻塞");
}
// 关闭文件
close(fd);
return 0;
}
六 目录操作汇总
6.1 getcwd
#include
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录
参数:
buf : 缓冲区,存储当前的工作目录
size : 缓冲区大小
返回值:
成功:buf中保存当前进程工作目录位置
失败:NULL
6.2 chdir函数
#include
int chdir(const char *path);
功能:修改当前进程(应用程序)的路径
参数:
path:切换的路径
返回值:
成功:0
失败:-1
6.3 opendir函数
#include
#include
DIR *opendir(const char *name);
功能:打开一个目录
参数:
name:目录名
返回值:
成功:返回指向该目录结构体指针
失败:NULL
6.4 closedir函数
#include
#include
int closedir(DIR *dirp);
功能:关闭目录
参数:
dirp:opendir返回的指针
返回值:
成功:0
失败:-1
6.5 readdir函数
#include
struct dirent *readdir(DIR *dirp);
功能:读取目录
参数:
dirp:opendir的返回值
返回值:
成功:目录结构体指针
失败: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_LNK | 软链接 |
DT_FIFO | 管道 |
DT_REG | 普通文件 |
DT_SOCK | 套接字 |
DT_UNKNOWN | 未知 |