嵌入式C语言(十四)

在现代操作系统架构中,内核空间和用户空间之间增加了一个中间层,这就是系统调用层。

系统调用层主要有如下作用。

为用户空间程序提供一层硬件抽象接口。这能够让应用程序编程者从学习硬件设备底层编程中解放出来。例如,当需要读写一个文件时,应用程序编写者不用去关心磁盘类型和介质,以及文件存储在磁盘哪个扇区等底层硬件信息。

保证系统稳定和安全。应用程序要访问内核必须通过系统调用层,那么内核可以在系统调用层对应用程序的访问权限、用户类型和其他一些规则进行过滤,这样可以避免应用程序不正确地访问内核。

可移植性。可以让应用程序在不修改源代码的情况下,在不同的操作系统或者不同的硬件体系结构的系统中重新编译并且运行。

1 系统调用和POSIX标准

有的读者可能对应用编程接口(API)和系统调用之间的关系有点糊涂了。

一般来说,应用程序调用用户空间实现的应用编程接口来编程,而不是直接调用系统调用。

一个 API接口函数可以由一个系统调用实现,也可以由多个系统调用来实现,甚至完全不使用任何系统调用。

因此,一个API接口没有必要对应一个特定的系统调用。

在UNIX系统设计的早期就出现了操作系统的API接口层。

在UNIX的世界里,最通用的系统调用层接口是POSIX(Portable Operating System Interface of UNIX)标准。POSIX的诞生和UNIX的发展密不可分。

UNIX系统诞生于20世纪70年代的贝尔实验室,很多商业厂商基于UNIX发展自己的UNIX系统,但是标准不统一。后来IEEE制定了POSIX标准,但是需要注意的是,POSIX标准针对的是API而不是系统调用。

判断一个系统是否与POSIX兼容时,要看它是否提供一组合适的应用编程接口,而不是看它的系统调用是如何定义和实现的。

Linux操作系统的API接口通常是以C标准库的方式提供的,比如Linux中的libc库。

C库提供了POSIX的绝大部分的API的实现,同时也为内核提供的每个系统调用封装了相应的函数,并且系统调用和 C 库封装的函数名称通常是相同的。

例如,open 系统调用在 C库的函数也是open函数。

另外几个API函数可能调用封装了不同功能的同一个系统调用,例如,libc库函数中实现的malloc()、calloc()和free()等函数,这几个函数用来分配和释放虚拟内存(堆上的虚拟内存),它们都是利用brk系统调用来实现的。

大家都知道malloc是c中常用的内存操作函数,malloc动态的申请一块指定大小的内存,方便存放数据。而brk/sbrk则是实现malloc的底层函数,其中brk是系统调用。brk和sbrk主要的工作是实现虚拟内存到内存的映射。

每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这一块分配的。如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的下界移动,sbrk函数在内核的管理下将虚拟地址空间映射到内存,供malloc函数使用。

sbrk不是系统调用,是C库函数。系统调用通常提供一种小功能,而库函数通常提供比较复杂的功能。sbrk/brk是从堆中分配空间,本质是移动一个位置,向后移就是分配空间,向前移就是释放空间,sbrk用相对的整数值确定位置,如果这个整数是正数,会从当前位置向后移若干字节,如果为负数就向前若干字节。在任何情况下,返回值永远是移动之前的位置。

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

brk是将数据段(.data)的最高地址指针_edata往高地址推;

mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层就是由brk,mmap,munmap这些系统调用实现的。

2 系统调用表

Linux系统为每一个系统调用赋予一个系统调用号。

当应用程序执行一个系统调用时,应用程序就可以知道执行和调用到哪个系统调用了,从而不会造成混乱。

系统调用号一旦分配之后就不会有任何变更,否则已经编译好的应用程序就不能运行了。

对于ARM32系统来说,其系统调用号定义在arch/arm/include/uapi/asm/unistd.h头文件中。

<arch/arm/include/uapi/asm/unistd.h>
/** This file contains the system call numbers.*/
#define __NR_restart_syscall    (__NR_SYSCALL_BASE+ 0)
#define __NR_exit      (__NR_SYSCALL_BASE+ 1)
#define __NR_fork      (__NR_SYSCALL_BASE+ 2)
#define __NR_read      (__NR_SYSCALL_BASE+ 3)
#define __NR_write      (__NR_SYSCALL_BASE+ 4)
#define __NR_open      (__NR_SYSCALL_BASE+ 5)
#define __NR_close      (__NR_SYSCALL_BASE+ 6)
/* 7 was sys_waitpid */
#define __NR_creat      (__NR_SYSCALL_BASE+ 8)
#define __NR_link      (__NR_SYSCALL_BASE+ 9)
#define __NR_unlink     (__NR_SYSCALL_BASE+ 10)
#define __NR_execve     (__NR_SYSCALL_BASE+ 11)
#define __NR_chdir      (__NR_SYSCALL_BASE+ 12)
#define __NR_time      (__NR_SYSCALL_BASE+ 13)
#define __NR_mknod      (__NR_SYSCALL_BASE+ 14)
#define __NR_chmod      (__NR_SYSCALL_BASE+ 15)
#define __NR_lchown     (__NR_SYSCALL_BASE+ 16)

例如,open这个系统调用被赋予的号码是5,因此在所有的ARM32系统中,这个open系统调用号是不能被更改的。

open系统调用最终的实现在如下函数中。

<fs/open.c>
SYSCALL_DEFINE3(open, const char __user *, filename,int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}

SYSCALL_DEFINE是一个宏,其实现是在include/linux/syscalls.h头文件中。

<include/linux/syscalls.h>
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1,_##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2,_##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3,_##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4,_##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5,_##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6,_##name, __VA_ARGS__)
#define SYSCALL_DEFINEx(x, sname, ...)        \
SYSCALL_METADATA(sname, x, __VA_ARGS__)      \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

该宏最后扩展完会变成sys_open()函数。

asmlinkage long sys_open(const char __user *filename,int flags, umode_t mode);

3 用程序访问系统调用

应用程序编写者通常不会直接访问系统调用,而是通过C标准库函数来访问系统调用。

如果给Linux系统新添加了一个系统调用,那么可以通过直接调用syscall()函数来访问新添加的系统调用。

#include <unistd.h>
#include <sys/syscall.h> 
/* 系统调用定义 */
long syscall(long number, ...);

syscall()函数可以直接调用一个系统调用,第一个参数是系统调用号码,比如上面提到的open系统调用号码是5;

“…”是可变参数,用来传递参数到内核。

以上述的open系统调用为例,在应用程序中可以用如下代码直接调用。

#define NR_OPEN 5
syscall(NR_OPEN, filename, flags, mode);

4 新增系统调用

读者可能疑惑,既然Linux系统为我们提供了几百个系统调用,当我们在实际项目中遇到问题时,是否可以新增一个系统调用呢?

在Linux系统中新增一个系统调用是很容易的事情,但是我们不提倡新增系统调用,因为新增一个系统调用意味着你的应用程序可能缺乏了移植性。

Linux 系统的系统调用必须由 Linux 社区来决定,并且和 glibc 社区同步,也就是需要Linux和glibc同步进行修改。

因此,新增一个系统调用需要在社区里充分讨论和沟通,这个过程会非常漫长。

其实Linux内核里提供了很多机制来让用户程序和内核进行信息交互,读者应该充分思考是否可以使用如下方法来实现,而不是考虑新增一个系统调用。

设备节点。实现一个设备节点之后,就可以对该设备进行read()和write()等操作,甚至可以通过ioctl()接口来自定义一些操作。

sysfs接口。sysfs接口也是一种推荐的用户程序和内核直接的通信方式,这种方式很灵活,也是Linux内核推荐的做法。还有proc。

参考资料

  • 《奔跑吧Linux内核》
  • https://www.bilibili.com/read/cv20460912/
  • https://www.cnblogs.com/antidogmatist/p/16966897.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/808424.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux操作系统的学习

Linux系统的目录结构 / 是所有目录的顶点目录结构像一颗倒挂的树 Linux常用命令 常见命令 序号命令对应英文作用1lslist查看当前目录下的内容2pwdprint work directory查看当前所在目录3cd [目录名]change directory切换目录4touch [文件名]touch如果文件不存在&#xff0c;新…

TRX节点部署

安装说明 安装版本&#xff1a;4.7.4 安装目录&#xff1a;/data/docker-compose/trx 配置文件路径&#xff1a;/data/docker-compose/trx/config.conf 节点data数据&#xff1a;/data/docker-compose/trx/output-directory 安装方式&#xff1a;Dockerfiledockerdocker-compos…

数字化采购管理系统有什么作用?

数字化采购管理系统是一种以计算机技术为基础&#xff0c;用于管理和优化采购过程的信息系统&#xff0c;它的作用主要体现在以下几个方面&#xff1a; 1、优化采购流程&#xff1a;数字化采购管理系统可以帮助企业建立和优化采购流程&#xff0c;通过设定采购流程的各个环节和…

6.11物联网RK3399项目开发实录-驱动开发之定时器的使用(wulianjishu666)

嵌入式实战开发例程【珍贵收藏&#xff0c;开发必备】&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1tkDBNH9R3iAaHOG1Zj9q1Q?pwdt41u 定时器使用 前言 RK3399有 12 个 Timers (timer0-timer11)&#xff0c;有 12 个 Secure Timers(stimer0~stimer11) 和 2 个 …

鸿蒙实战开发-如何实现标准化数据定义与描述的功能。

介绍 本示例主要使用ohos.data.uniformTypeDescriptor 展示了标准化数据定义与描述的功能&#xff0c;在新增预置媒体文件后&#xff0c;对媒体文件的utd标准类型获取、utd类型归属类型查询、获取文件对应的utd类型的默认图标、支持自定义数据类型等功能。 实现过程中还使用到…

编译安装nginx

nginx版本 安装组件 yum -y install openssl-devel pcre-devel zlib-devel ./configure --with-http_ssl_module && make && make installln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx

【C++算法模板】背包九讲(上):01背包、完全背包、多重背包

文章目录 1&#xff09;01背包1&#xff1a;二维数组2&#xff1a;一维数组 2&#xff09;完全背包1&#xff1a;朴素做法2&#xff1a;公式优化3&#xff1a;再优化一维数组 3&#xff09;多重背包1&#xff1a;朴素做法2&#xff1a;二进制优化3&#xff1a;单调队列优化 1&a…

AcWing-直方图中最大的矩形

131. 直方图中最大的矩形 - AcWing题库 所需知识&#xff1a;单调栈 思路&#xff1a;要求最大矩形&#xff0c;所以需要使矩形的高与长的乘积最大即可&#xff0c;依次从左到右将每一列当作中心列&#xff0c;向两边扩散&#xff0c;直到两边的高都小于该列的高&#xff0c;…

Vmware虚拟机Centos7固定IP地址

1、点击编辑-虚拟网络编辑器 2、点击更改设置、修改虚拟网络配置器并确认保存&#xff08;见图&#xff09; 这个子网IP和子网掩码的前三位需要一样网关的前三位需要和子网ip一致。 3、打开设置“网络和Internet”&#xff0c;点击“更改适配器选项”&#xff0c;点击适配器VM…

PP-LCNet:一种轻量级CPU卷积神经网络

PP-LCNet: A Lightweight CPU Convolutional Neural Network 最近看了一个新的分享&#xff0c;在图像分类的任务上表现良好&#xff0c;具有很高的实践意义。 论文&#xff1a; https://arxiv.org/pdf/2109.15099.pdf项目&#xff1a; https://github.com/PaddlePaddle/Padd…

JUC并发编程2(高并发,AQS)

JUC AQS核心 当有线程想获取锁时&#xff0c;其中一个线程使用CAS的将state变为1&#xff0c;将加锁线程设为自己。当其他线程来竞争锁时会&#xff0c;判断state是不是0&#xff0c;不是自己就把自己放入阻塞队列种&#xff08;这个阻塞队列是用双向链表实现&#xff09;&am…

探索ChatGPT-Plus:AI 助手全套开源解决方案

探索ChatGPT-Plus&#xff1a;AI 助手全套开源解决方案 ChatGPT-plus是一种新型的对话生成模型&#xff0c;它是在OpenAI的ChatGPT基础上进行了改进和优化的版本。ChatGPT-plus的出现引起了广泛关注&#xff0c;因为它在对话生成方面展现出了更加出色的表现和能力。在本文中&am…

蒙特卡洛方法【强化学习】

强化学习笔记 主要基于b站西湖大学赵世钰老师的【强化学习的数学原理】课程&#xff0c;个人觉得赵老师的课件深入浅出&#xff0c;很适合入门. 第一章 强化学习基本概念 第二章 贝尔曼方程 第三章 贝尔曼最优方程 第四章 值迭代和策略迭代 第五章 强化学习实践—GridWorld 第…

flutter嵌入原生view

一、iOS端(Swift实现) 1. 新建原生view(NativeView.swift) &#x1f3f7;️ 需要继承FlutterPlatformView &#xff0c;实现view()方法 import Foundation import Flutterclass NativeView: NSObject, FlutterPlatformView {private var _view: UIViewinit(frame: CGRect,view…

从文件夹(包含子文件夹)找到的包含特定关键词的 Word 文档复制到一个新的文件夹中

import os import glob import shutildef find_and_copy_docs(root_dir, keyword, target_dir):"""在指定的目录及其子目录下查找包含特定关键词的 Word 文档&#xff0c;并将它们复制到目标文件夹。参数:root_dir: 要搜索的根目录路径。keyword: 需要匹配的关键…

make/makefile学习

文章目录 1、makefile函数1.1、字符串替换函数&#xff1a;subst1.2、模式字符串替换函数&#xff1a;patsubst1.3、去空格函数&#xff1a;strip1.4、查找字符串函数&#xff1a;findstring 2、、:、&#xff1f;区别 1、makefile函数 1.1、字符串替换函数&#xff1a;subst …

TS中,PropType导入报错is a type and must be imported using a type-only import...

报错信息&#xff1a; PropType is a type and must be imported using a type-only import when verbatimModuleSyntax is enabled.ts(1484) (alias) type PropType<T> PropConstructor<T> | PropConstructor<T>[] import PropType问题分析&#xff1a; …

《QT实用小工具·二十》存款/贷款计算器

1、概述 源码放在文章末尾 该项目实现了用于存款和贷款的计算器的功能&#xff0c;如下图所示&#xff1a; 项目部分代码如下&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget>namespace Ui { class Widget; }class Widget : public QWidget {Q_OBJ…

构造函数,原型对象,对象实例 以及原型链的关系

当我们使用构造函数new的方式创建实例对象&#xff0c;此时构造函数的.prototype属性就是实例对象的原型对象&#xff0c;实例对象可以通过.__proto__来访问到原型对象&#xff0c;同时实例对象会继承原型对象的属性方法。 构造函数和原型对象的关系其实是被包含的关系&#x…

【电子通识】普通电阻、敏感电阻、可调电阻的种类和特点

电阻的作用 在【分立元件】理解电阻 中我们知道电阻是在电路中对电流产生阻碍作用的元件。电阻是电子产品中最基本、最常用的电子元件之一。 有各产品的电路板中基本都有电阻器&#xff0c;通常起限流、滤波或分压等作用。实际上&#xff0c;电阻器的种类很多&#xff0c;根据其…