2024-02-21-物联网系统编程(2-系统调用)
2.系统调用
2.1 系统编程概述
操作系统的职责:操作系统用来管理所有的资源,并将不同的设备和不同的程序关联起来
Linux系统编程:在有操作系统打的环境下编程,并使用操作系统提供的系统调用及库函数,对系统资源进行访问
系统编程就是为了让用户更方便的操作硬件设备,并且对硬件设备起到保护作用。我们所写的程序,本质就是对硬件设备的操作。
2.2 系统调用概述


系统调用本质上是对硬件设备进行操作,但是linux操作系统在硬件之上设置了内核,这样只有内核才可以直接对硬件设备进行操作。
如果想操作内核,需要使用内核的系统调用,手段有以下三种:
- shell,用户通过shell命令,经由shell解释器与操作内核的系统调用
- 库函数,用户通过应用层库函数接口,对内核的系统调用进行操作
- 应用层系统调用,直接对内核系统调用进行操作
系统调用是操作系统提供给用户程序的一组"特殊"的函数接口
系统调用按照功能逻辑分为:
- 进程控制
- 进程间通信
- 文件系统控制
- 系统控制
- 内存管理
- 网络管理
- socket控制
- 用户管理
系统调用的返回值:
- 返回值为0 ,成功
- 返回值为负数,错误,错误信息存放在errno中,用户可以通过perror函数打印出错信息
系统调用遵循的规范:在linux中,应用程序编程接口(API)遵循POSIX标准。
2.3 系统调用I/O函数
2.3.1 文件描述符
系统调用中操作I/O函数,都是针对文件描述符的。
文件描述符是非负整数,打开现存的文件或者新建文件时,系统(内核)会返回一个文件描述符。
当一个程序运行或者一个进程开启时,系统会自动创建三个文件描述符。
文件描述符 | 标准IO | 说明 |
---|---|---|
0 | stdin | 标准输入 |
1 | stdout | 标准输出 |
2 | stderr | 标准输出错误 |
2.3.2 open函数
打开一个文件
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> // 当文件存在时,使用 int open(const char *pathname, int flag); // 当文件不存在时使用 int open(const char *pathname,int flags, mode_t mode); 参数: pathname: 文件路径及文件名; flags:open函数的行为标志; mode:文件权限(可读可写可执行)的设置; 返回值: 成功,返回打开文件的描述符 失败, 返回-1,可以利用perror查看原因
flags的取值和含义
取值 | 含义 |
---|---|
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以只写方式打开 |
O_RDWR | 以可读可写方式打开 |
flags除了上述取值外,还可以与下列值位或
取值 | 含义 |
---|---|
O_CREAT | 文件不存在时创建文件;使用此选项需要使用mode说明文件权限 |
O_EXCL | 如果同时指定O_CREAT,且文件已经存在,则出错 |
O_TRUNC | 如果文件存在,则清空文件内容 |
O_APPEND | 写文件时,数据添加到文件末尾 |
O_NOBLOCK | 非阻塞标志位(文件类型为FIFO、字符文件、块文件) |
mode ,只有当flag含有O_CREAT时,才能使用,取值如下(可以用八进制数代替):
取值 | 八进制数 | 含义 |
---|---|---|
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 | 其他组用户可执行 |
文件IO和标准IO权限对比
标准IO | 文件IO | 权限含义 |
---|---|---|
r | O_RDONLY | 只读方式打开,文件不存在则报错 |
r+ | O_RDWR | 读写方式打开文件,文件不存在则报错 |
w | O_WRONLY | O_CREAT | O_TRUNC,0644 | 只读方式打开,文件不存在则创建,存在则清空 |
w+ | O_RDWR | O_CREAT | O_TRUNC, 0644 | 读读方式打开,文件不存在则创建,存在则清空 |
a | O_WEONLY | O_CREAT | O_APPEND, 0644 | 只写方式打开,文件不存在则创建,存在则追加 |
a+ | O_RDWR | O_CREAT | O_APPEND, 0644 | 读写方式打开,文件不存在则创建,存在则追加 |
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> int main(int argc, char const *argv[]) { int fd; // 使用open函数打开一个文件 fd = open("file.txt",O_RDONLY | O_CREAT,0664); printf("fd = %d",fd); return 0; }
输出结果,会创建一个新文件file.txt
fd = 3
函数调用出错后输出错误信息
通过全局变量errno #include <errno.h> errno是一个全局变量,当函数调用失败后,可以通过errno获取错误码 通过一个函数perror #include <stdio.h> 功能:输出函数调用失败后的错误信息 参数: s : 打印错误信息的提示消息 返回值:无
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> int main(int argc, char const *argv[]) { int fd; // 使用open函数打开一个文件 fd = open("file.txt", O_RDONLY); printf("error = %d\n", errno); perror("fail to open"); // 使用perror打印错误信息 return 0; }
输出结果
error = 2 // 文件不存在的错误码 fail to open: No such file or directory // 文件不存在,直接打印出出错信息
2.3.3 close函数
关闭一个文件
#include <unistd.h> int close(int fd) 功能:关闭一个文件描述符 参数: fd:指定文件的文件描述符,即open函数的返回值 返回值: 成功:0 失败:1
#include <stdio.h> #include <sys/fcntl.h> int main(int argc, char const *argv[]) { int fd; fd = open("file.txt", O_RDONLY); if (fd == -1) { perror("fail to open"); return -1; } else { printf("fd = %d\n", fd); // 当不对文件操作的时候,就会关闭文件描述符 // 使用close函数关闭文件描述符 // 一旦文件描述符关闭,就不能通过原有的文件描述符对文件进行操作 close(fd); } return 0; }
输出结果
fd = 3
注意:
-
程序运行的时候,最多创建1024个文件描述符,值范围是 0 -1023
-
文件描述符从小到大依次递增
-
如果中途有文件描述符被关闭,则会再创建;新建的文件描述符会使用前面空缺的文件描述符,前面不空缺,则依次递增
如已有文件描述符 1 2 3 4 5 7 这是 4 被关闭,只剩下 1 2 3 5 7,新创建两个文件描述符,第一个就是 4,第二个为 8;
2.3.4 write 函数
把执行数目的数据写入文件
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); 功能:向文件写入数据 参数: fd:指定的文件描述符 buf:要写入的数据 count:要写入的数据的长度 返回值: 成功:实际写入的字节数 失败:-1
向终端写入内容
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> int main(int argc, char const *argv[]) { // 向终端写入数据,对1这个文件描述符进行操作,标准输出 if(write(1,"hello world!",12) == -1){ perror("fail to write"); return -1; } return 0; }
输出结果
hello world!
向文件写入内容
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> int main(int argc, char const *argv[]) { // 向文件写数据 int fd; // 以只写的方式打开文件,文件不存在则创建,文件存在则清空 fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("fail to open"); return -1; } if (write(fd, "hello world!\n", 12) == -1) { perror("fail to write"); return -1; } close(fd); return 0; }
输出结果,创建了文件file.txt
file.txt
hello world!
2.3.5 read函数
把指定数目的数据读取到内存
把指定数目的数据读到内存 #include <unistd, h> ssize_t read(int fd, void *addr, size_t count); 参数: fd:攻件描述符 addr:内存首地址 count;读取的字节个数 返回值: 成功:返回实际读取到的字节个数 失败:返回-1,可以利用 perror获取原因 注意:如果读取到末尾,返回0
从终端读取数据
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> int main(int argc, char const *argv[]) { // 从终端读取数据,使用文件描述符0 char str[32] = ""; if (read(0, str, 6) == -1) // 此处定义了保存多少个字节 // 此处定义的数据大于输入数据,则输出数据位原数据 + 一个换行符 { perror("fail to read"); return -1; } printf("str = [%s]\n",str); return 0; }
输出结果
hello world str = [hello ] //保存了6个字符
从文件读取数据
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> #define N 64 int main(int argc, char const *argv[]) { // 从文件读取数据 int fd; if ((fd = open("text.txt", O_RDONLY | O_CREAT, 0664)) == -1) { perror("fail to read"); return -1; } // 读取文件内容 char buf[N] = ""; if ((read(fd, buf, 32)) == -1) { perror("fail to read"); return -1; } printf("buf = [%s]", buf); close(fd); return 0; }
输出结果
buf = [HELLO WORLD]
2.3.6 remove
删除文件
#include<stdio.h> int remove(const char *pathname) 参数: 文件的路名+文件名pathname : 返回值: 成功返回 0。 失败返回-1,可以利用 perror 去查看原因
#include <stdio.h> int main(int argc, char const *argv[]) { // 使用remove函数删除文件 if (remove("./file.txt") == -1) { perror("fail to remove"); return -1; } printf("delete done\n"); return 0; }
输出结果
delete done
2.4系统调用与库函数对比
库函数有两类函数组成:
- 不需要调用系统调用
- 需要调用系统调用
2.4.1 不需要调用系统调用
不需要切换到内核空间即可完成函数的全部功能,并将结果反馈给应用程序,如strcpy,bzero等字符串操作函数
2.4.2 需要调用系统调用
需要切换到内核空间,这类函数通过封装系统调用去实现相应功能,如printf、fread等
2.4.3 库函数与系统调用的关系
并不是所有的系统调用都被封装成了库函数,系统的很多功能需要通过系统调用才能控制

系统调用需要时间,程序中频繁使用系统调用会降低程序的运行效率。
当运行内核代码时,CPU工作在内核态,在系统调用发生前,需要保存用户态的栈和内存环境,然后转入内核态工作。
系统调用结束后,又要切换到用户态,这种环境的切换很消耗时间。
库函数的优势:设置不同类型的缓冲区,减少直接调用IO系统的次数,节省时间。大多数库函数的本质也是系统调用,只不过库函数有缓冲区,能够节省时间。
本文来自博客园,作者:Yasuo_Hasaki,转载请注明原文链接:https://www.cnblogs.com/hasaki-yasuo/p/18027213
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步