系统调用
系统调用概述
如果想操作内核(kernel),需要调用内核的系统调用(system calls)
系统调用有三种方式:
-
shell, 用户通过shell命令,有shell解释器操作内核的系统调用
-
库函数,用户通过 应用层库函数 的接口,比如fread对内核的系统调用进行操作
在 Linux 中,**应用程序编程接口(API)**遵循 POSIX 标准。 POSIX 标准基于当时现有的 UNIX 实践和经验,描述了操作系统的系统调用编程接口(实际上就是 API),用于保证应用程序可以在源代码一级上在多种操作系统上移植运行。
-
应用层系统调用,他可以直接对内核的系统调用进行操作
分类:系统调用按照功能逻辑大致可分为
-
进程控制、进程间通信、文件系统控制、系统控制、
-
内存管理、网络管理、socket控制、用户管理。
返回值:
- 返回值为**0 ** 表明成功。
- 返回值为负数表明错误,
错误信息
错误信息存放在全局变量errno中,用户可用perror函数打印出错信息。
查看 全局变量 errno
errno是一个全局变量,当函数调用失败后,用于获取错误码。
#include <errno.h>int fd;
fd = open("file.txt",O_RDONLY);
if (fd = -1)
{printf("errno=%d\n",errno);errno=2
}
对照错误信息码:
cat /usr/include/asm-generic/errno-base.h# 得到错误码信息:
define EPERM 1 /* Operation not permitted */
define ENOENT 2 /* No such file or directory */
....
输出 函数perror
输出函数调用的错误信息·
#include <stdio.h>void perror(const char *s); perror("fail to open");
参数: s 需要打印的错误提示消息
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main()
{// open 函数打开或者创建一个文件int fd;fd = open("demo.txt",O_RDONLY | O_CREAT,0664);// 保存文件描述符 从小到大依次创建的顺序 3,4,5..printf("fd = %d\n",fd);if(fd == ‐1){perror("fail to open"); return ‐1;}return 0;
}
系统调用I/O函数
系统调用中操作 I/O 的函数,都是针对文件描述符的. 通过文件描述符可以直接对相应的文件进行操作。
# include <unistd.h>
文件描述符
创建规则
文件描述符是非负整数。打开现存文件或新建文件时,系统(内核)会返回一个文件描述符。文件描述符用来指定已打开的文件,文件描述符对文件起到标识作用。如果要操作文件,就是对文件描述符的操作。
如果有文件描述符被关闭了,则再创建的文件描述符会先补齐之前的,然后依次递增创建。3,4,5…
一个程序运行的时候最多可以创建2014个文件描述符,0~1023
特殊文件描述符
当一个程序运行或者一个进程开启时,系统会自动创建三个文件描述符。
#define STDIN_FILENO 0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符
特殊文件描述符 | 含义 |
---|---|
0 | 标准输入 stdin |
1 | 标准输出 stdout |
2 | 标准报错 stderr |
复制 dup dup2
dup 和 dup2 都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。 复制文件描述符
dup 和 dup2 经常用来重定向进程的 stdin、stdout 和 stderr。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup 函数
#include <unistd.h>int dup(int oldfd);
功能:复制 oldfd 文件描述符,并分配一个新的文件描述符。新的文件描述符是调用进程文件描述符表中最小可用的文件描述符。
参数:要复制的文件描述符 oldfd。
返回值:
-
成功:新文件描述符。 (自动分配的)
-
失败:返回-1,错误代码存于 errno 中。
示例
int fd1;
int fd2;
fd2 = dup(1); // fd2=3 指向标准输出
printf("new:fd2 = %d\n",fd2); // fd2=3
fd1 = open("test.txt", O_RDWR | O_CREAT, 0664);
close(1);
int fd3 = dup(fd1); // fd3=1 和fd1=4一起指向 test.txt
printf("hello world\n");
printf("fd = %d\n", fd3); //这些都被重定向 test.txt
close(1);
int fd4 = dup(fd2); // fd4此时=1 和 fd2 都指向标准输出
printf("nihao beijing\n");
printf("fd = %d\n", fd4);
dup2 函数 重定向
#include <unistd.h>int dup2(int oldfd, int newfd);//首先关闭1文件描述符,然后将fd1复制给1,意味着1和fd1都标识test.txt文件, 返回值跟1是一样的
fd1 = open("test.txt", O_CREAT | O_WRONLY, 0664);
fd2 = dup2(fd1, 1);
功能:复制一份打开的文件描述符 oldfd,并分配新的文件描述符 newfd。如果 newfd 是一个已经打开的文件描述符,则首先关闭该文件(自动close newfd),然后再复制。
参数:
- oldfd 要复制的文件描述符
- newfd 分配我们指定的新的文件描述符
返回值:
-
成功:返回 指定的 新的文件描述符 newfd
-
失败:返回-1,错误代码存于 errno 中
示例
//实现输出重定向后,再恢复标准输出
int fd1;
//如果使用dup2,则需要实现给第二个参数对应的变量赋一个初始值
int fd2 = 3;
//将1复制一份为fd2,所以fd2标识的是标准输出
dup2(1, fd2);
printf("fd2 = %d\n", fd2);
fd1 = open("test.txt", O_CREAT | O_RDWR, 0664);
//输出重定向:
// 关闭文件描述符1,将fd1复制一份为1,
// 1此时标识的是test.txt 文件
dup2(fd1, 1);
printf("hello world\n");//重定向
//再次实现标准输出:
// 关闭文件描述符1,将fd2复制一份为1,
// 1此时标识的是标准输出
dup2(fd2, 1);
printf("嘻嘻哈哈哈哈哈哈哈哈哈哈\n"); //标准输出
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:open 函数的行为标志,可取位或
- mode:文件权限(可读、可写、可执行)的设置。
返回值:
- 成功,打开的文件描述符。
- 失败,返回 -1,可以利用 perror
flags 文件IO和标准IO权限对比
标准IO | 文件IO | 权限含义 |
---|---|---|
r | O_RDONLY | 以只读的方式打开文件,如果文件不存在则报错 |
r+ | O_RDWR | 以读写的方式打开文件,如果文件不存在则报错 |
w | O_WRONLY | O_CREAT | O_TRUNC 0664 | 以只写的方式打开文件,如果文件不存在则创建,如果文件存在则清空 |
w+ | O_RDWR | O_CREAT | O_TRUNC, 0664 | 以读写的方式打开文件,如果文件不存在则创建, 如果文件存在则清空 |
a | O_WRONLY | O_CREAT | O_APPEND, 0664 | 以只写的方式打开文件,如果文件不存在则创建, 如果文件存在则追加 |
a+ | O_RDWR | O_CREAT | O_APPEND, 0664 | 以读写的方式打开文件,如果文件不存在则创建, 如果文件存在则追加 |
flags 的取值及其含义:
取值 | 权限含义 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打开 |
O_RDWR | 以可读、可写的方式打开 |
除了取上述值外,还可与下列值位或 | |
O_CREAT | 文件不存在则创建文件,使用此选项时需使用 mode 说明文件的权限 |
O_EXCL | 如果同时指定了 O_CREAT,且文件已经存在,则出错 |
O_TRUNC | 如果文件存在,则清空文件内容 |
O_APPEND | 写文件时,数据添加到文件末尾 |
O_NONBLOCK | 当打开的文件是 FIFO、字符文件、块文件时,此选项为非阻塞标志位 |
mode 的取值及其含义
只要指定了O_CREAT 就需使用 mode 说明文件的权限
取值 | 八进制 | 含义 |
---|---|---|
S_IRWXU | 00700 | 文件所有者的读、写、可执行权限 |
S_IRUSR | 00400 | 文件所有者的读权限 |
S_IWUSR | 00200 | 文件所有者的写权限 |
S_IXUSR | 00100 | 文件所有者的可执行权限 |
S_IRWXG | 00070 | 文件所有者同组用户的读、写、可执行权限 |
S_IRGRP | 00040 | 文件所有者同组用户的读权限 |
S_IWGRP | 00020 | 文件所有者同组用户的写权限 |
S_IXGRP | 00010 | 文件所有者同组用户的可执行权限 |
S_IRWXO | 00007 | 其他组用户的读、写、可执行权限 |
S_IROTH | 00004 | 其他组用户的读权限 |
S_IWOTH | 00002 | 其他组用户的写权限 |
S_IXOTH | 00001 | 其他组用户的可执行权限 |
只要指定了O_CREAT 就需使用 mode 说明文件的权限
close函数
# include <unistd.h>
int close(int fd);
int fd;
fd = open("demo.txt",O_RDONLY);
close(fd);
功能:关闭一个文件描述符。
当不对文件进行任何操作时,就关闭文件描述符 使用close函数关闭文件描述符
参数
- fd:指定文件的文件描述符,open函数的返回值
返回值: 成功:0 。失败:‐1
write函数
# include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向文件写入数据
参数:
- fd:指定的文件描述符
- buf:要写入的数据
- count:要写入的数据的长度
返回值:
-
成功:实际写入的字节数 类型 ssize_t (int 的别名)
-
失败:‐1
示例:
向文件标识符 1 标准输出,从而向终端写入数据
int main(int argc, char const *argv[])
{ //向终端写入数据 //对1这个文件描述符进行操作if(write(1, "hello world\n", 12) == -1){ perror("fail to open"); return -1; } return 0;
}
向文件写入数据
int main(int argc, char const *argv[])
{ //向文件写入数据 int fd; //以只写的方式打开文件,如果文件不存在则创建,如果文件存在则清空 fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664); if(fd == -1) { perror("fail to open"); return -1; } //使用write函数向文件写入数据 ssize_t bytes; if((bytes = write(fd, "hello world\n", 12))== -1) { perror("fail to write"); return -1; } printf("bytes = %ld\n", bytes); write(fd, "nihao beijing", 5); //关闭文件描述符 close(fd); return 0;
}
read 函数
把指定数目的数据读到内存
#include <unistd.h> ssize_t read(int fd, void *addr, size_t count);
参数:
- fd:文件描述符。
- addr:内存首地址。
- count:读取的字节个数。
返回值:类型 ssize_t
- 成功,返回实际读取到的字节个数。
- 失败
- 返回0,读取到文件末尾
- 返回-1,可以利用 perror 去查看原因。
如果输入的字节数大于第三个参数count,返回值与第三个参数一致。如果输入的字节数小于第三个参数count,读取输入的数据加上换行符,返回值就是实际输入的数据+1
ssize_t bytes;char str[32]="";if((bytes = read(0,str,6))==-1){perror("fail to read");return -1;}printf("str = [%s]\n bytes = %ld\n", str, bytrs)
使用0文件描述符从终端读取数据
ssize_t bytes;
char str[32] = "";
if((bytes = read(0, str, 6)) == -1)
{ perror("fail to read"); return ‐1;
}
fread和fwrite 成块读写
fread 和 fwrite 函数可以对数据进行成块读写。
需要注意的是,尽管 fread 和 fwrite 函数并不是说一次想读写多少数据就能全部读写多少数据,毕竟缓存有限,不同的操作系统的缓存大小也可能不一样。
fread为封装好的库函数,而read为系统函数,一般来说,fread效率更高。fread功能更强大,可以的结构体的二进制文件。如果底层的操作,用到文件描述符,用read更好。
size_t fread(void *buf, size_t size, size_t count, FILE *fp);size_t fwrite(const void * buf, size_t size, size_t count, FILE *fp);
fread 函数从文件 fp 中读出 size* count 个字节保存到 buf 中
fwrite 把 buf 中的 size* count 个字节写到文件 fp 中
参数:
- ptr为指向缓冲区保存或读取的数据。
- size为控制记录大小。
- nmemb为记录数。
- 函数返回读取或回写的记录数 count。
返回值:读或写的记录数,
- 成功时返回的记录数等于 count 参数
- 出错或读到文件末尾时返回的记录数小于 count,也可能返回 0。
remove 删除文件
#include <stdio.h>
int remove(const char *pathname);
参数:pathname :文件的路名+文件名。
返回值:
- 成功返回 0。
- 失败返回-1,利用 perror 查看原因。
if(remove("./file.txt") == ‐1)
{perror("fail to remove"); return ‐1;
}