为了能到远方,脚下的每一步都不能少.|

Yasuo_Hasaki

园龄:1年1个月粉丝:2关注:0

2024-02-21-物联网系统编程(2-系统调用)

2.系统调用

2.1 系统编程概述

操作系统的职责:操作系统用来管理所有的资源,并将不同的设备和不同的程序关联起来

Linux系统编程:在有操作系统打的环境下编程,并使用操作系统提供的系统调用及库函数,对系统资源进行访问

系统编程就是为了让用户更方便的操作硬件设备,并且对硬件设备起到保护作用。我们所写的程序,本质就是对硬件设备的操作。

2.2 系统调用概述

image-20210822152235927 image-20240221172709674

系统调用本质上是对硬件设备进行操作,但是linux操作系统在硬件之上设置了内核,这样只有内核才可以直接对硬件设备进行操作。

如果想操作内核,需要使用内核的系统调用,手段有以下三种:

  1. shell,用户通过shell命令,经由shell解释器与操作内核的系统调用
  2. 库函数,用户通过应用层库函数接口,对内核的系统调用进行操作
  3. 应用层系统调用,直接对内核系统调用进行操作

系统调用是操作系统提供给用户程序的一组"特殊"的函数接口

系统调用按照功能逻辑分为:

  1. 进程控制
  2. 进程间通信
  3. 文件系统控制
  4. 系统控制
  5. 内存管理
  6. 网络管理
  7. socket控制
  8. 用户管理

系统调用的返回值:

  • 返回值为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

注意

  1. 程序运行的时候,最多创建1024个文件描述符,值范围是 0 -1023

  2. 文件描述符从小到大依次递增

  3. 如果中途有文件描述符被关闭,则会再创建;新建的文件描述符会使用前面空缺的文件描述符,前面不空缺,则依次递增

    如已有文件描述符 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系统调用与库函数对比

库函数有两类函数组成:

  1. 不需要调用系统调用
  2. 需要调用系统调用

2.4.1 不需要调用系统调用

不需要切换到内核空间即可完成函数的全部功能,并将结果反馈给应用程序,如strcpy,bzero等字符串操作函数

2.4.2 需要调用系统调用

需要切换到内核空间,这类函数通过封装系统调用去实现相应功能,如printf、fread等

2.4.3 库函数与系统调用的关系

并不是所有的系统调用都被封装成了库函数,系统的很多功能需要通过系统调用才能控制

image-20240222140533433

系统调用需要时间,程序中频繁使用系统调用会降低程序的运行效率。

当运行内核代码时,CPU工作在内核态,在系统调用发生前,需要保存用户态的栈和内存环境,然后转入内核态工作。

系统调用结束后,又要切换到用户态,这种环境的切换很消耗时间。

库函数的优势:设置不同类型的缓冲区,减少直接调用IO系统的次数,节省时间。大多数库函数的本质也是系统调用,只不过库函数有缓冲区,能够节省时间。

posted @   Yasuo_Hasaki  阅读(5)  评论(1编辑  收藏  举报
//雪花飘落效果
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起