Linux-C/C++--初探linux应用编程概念
对于大多数首次接触 Linux 应用编程的读者来说,可能对应用编程(也可称为系统编程)这个概念并不 太了解,所以在正式学习 Linux 应用编程之前,笔者有必要向大家介绍这些简单基本的概念,从整体上认识 到应用编程为何物?与驱动编程、裸机编程有何不同? 了解本章所介绍的内容是掌握应用编程的先决条件,所以本章主要内容便是对 Linux 应用编程进行一 个简单地介绍,让读者对此有一个基本的认识。 本章将会讨论如下主题内容。
何为系统调用;
何为库函数;
应用程序的 main()函数;
应用程序开发环境的介绍。
一、系统调用
系统调用(system call)其实是 Linux 内核提供给应用层的应用编程接口(API),是 Linux 应用层进入 内核的入口。不止 Linux 系统,所有的操作系统都会向应用层提供系统调用,应用程序通过系统调用来使用 操作系统提供的各种服务。
通过系统调用,Linux 应用程序可以请求内核以自己的名义执行某些事情,譬如打开磁盘中的文件、读 写文件、关闭文件以及控制其它硬件外设。
通过系统调用 API,应用层可以实现与内核的交互,其关系可通过下图简单描述:

内核提供了一系列的服务、资源、支持一系列功能,应用程序通过调用系统调用 API 函数来使用内核 提供的服务、资源以及各种各样的功能,如果大家接触过其它操作系统编程,想必对此并不陌生,譬如 Windows 应用编程,操作系统内核一般都会向应用程序提供应用编程接口 API,否则我们将我无法使用操 作系统。
应用编程与裸机编程、驱动编程有什么区别?
在学习应用编程之前,相信大家都有过软件开发经验,譬如 51、STM32 等单片机软件开发、以及嵌入 式 Linux 硬件平台下的驱动开发等,51、STM32 这类单片机的软件开发通常是裸机程序开发,并不会涉及 到操作系统的概念,那应用编程与裸机编程以及驱动开发有什么区别呢?
就拿嵌入式 Linux 硬件平台下的软件开发来说,我们大可将编程分为三种,分别为裸机编程、Linux 驱 动编程以及 Linux 应用编程。首先对于裸机编程这个概念来说很好理解,一般把没有操作系统支持的编程环 境称为裸机编程环境,譬如单片机上的编程开发,编写直接在硬件上运行的程序,没有操作系统支持;狭义 上 Linux 驱动编程指的是基于内核驱动框架开发驱动程序,驱动开发工程师通过调用 Linux 内核提供的接口 完成设备驱动的注册,驱动程序负责底层硬件操作相关逻辑,如果学习过 Linux 驱动开发的读者,想必对此 并不陌生;而 Linux 应用编程(系统编程)则指的是基于 Linux 操作系统的应用编程,在应用程序中通过调 用系统调用 API 完成应用程序的功能和逻辑,应用程序运行于操作系统之上。通常在操作系统下有两种不 同的状态:内核态和用户态,应用程序运行在用户态、而内核则运行在内核态。
关于应用编程这个概念,以上给大家解释得很清楚了,笔者以实现点亮一个 LED 功能为例,给大家简 单地说明三者之间的区别,LED 裸机程序如下所示:
static void led_on(void)
{/* 点亮 LED 硬件操作代码 */
}static void led_off(void)
{/* 熄灭 LED 硬件操作代码 */
}int main(void)
{/* 用户逻辑 */for ( ; ; ) {led_on(); //点亮 LEDdelay(); //延时led_off(); //熄灭 LEDdelay(); //延时}
}
可以看到在裸机程序当中,LED 硬件操作代码与用户逻辑代码全部都是在同一个源文件(同一个工程) 中实现的,硬件操作代码与用户逻辑代码没有隔离,没有操作系统支持,代码编译之后直接在硬件平台运 行,俗称“裸跑”。我们再来看一个 Linux 系统下的 LED 驱动程序示例代码,如下所示:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
static void led_on(void)
{/* 点亮 LED 硬件操作代码 */
}static void led_off(void)
{/* 熄灭 LED 硬件操作代码 */
}static int led_open(struct inode *inode, struct file *filp)
{/* 打开设备时需要做的事情 */
}static ssize_t led_write(struct file *filp, const char __user *buf,size_t size, loff_t *offt)
{int flag;/* 获取应用层 write 的数据,存放在 flag 变量 */if (copy_from_user(&flag, buf, size))return -EFAULT;/* 判断用户写入的数据,如果是 0 则熄灭 LED,如果是非 0 则点亮 LED */if (flag)led_on();elseled_off();return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{/* 关闭设备时需要做的事情 */
}static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,.release = led_release,
};static int led_probe(struct platform_device *pdev)
{/* 驱动加载时需要做的事情 */
}static int led_remove(struct platform_device *pdev)
{/* 驱动卸载时需要做的事情 */
}static const struct of_device_id led_of_match[] = {{.compatible = "alientek,led", },{ /* sentinel */ },
};MODULE_DEVICE_TABLE(of, led_of_match);
static struct platform_driver led_driver = {.driver = {.owner = THIS_MODULE,.name = "led",.of_match_table = led_of_match,},.probe = led_probe,.remove = led_remove,
};
module_platform_driver(led_driver);
MODULE_DESCRIPTION("LED Driver");
MODULE_LICENSE("GPL");
以上并不是一个完整的 LED 驱动代码,如果没有接触过 Linux 驱动开发的读者,看不懂也没有关系, 并无大碍,此驱动程序使用了最基本的字符设备驱动框架编写而成,非常简单;led_fops 对象中提供了 open、 write、release 方法,当应用程序调用 open 系统调用打开此 LED 设备时会执行到 led_open 函数,当调用 close 系统调用关闭 LED 设备时会执行到 led_release 函数,而调用 write 系统调用时会执行到 led_write 函数,此 驱动程序的设定是当应用层调用 write 写入 0 时熄灭 LED,write 写入非 0 时点亮 LED。
驱动程序属于内核的一部分,当操作系统启动的时候会加载驱动程序,可以看到 LED 驱动程序中仅仅 实现了点亮/熄灭 LED 硬件操作相关逻辑代码,应用程序可通过 write 这个系统调用 API 函数控制 LED 亮 灭;接下来我们看看 Linux 系统下的 LED 应用程序示例代码,如下所示:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{int fd;int data;fd = open("/dev/led", O_WRONLY);//打开 LED 设备(假定 LED 的设备文件为/dev/led)if (0 > fd)return -1;for ( ; ; ) {data = 1;write(fd, &data, sizeof(data)); //写 1 点亮 LEDsleep(1); //延时 1 秒data = 0;write(fd, &data, sizeof(data)); //写 0 熄灭 LEDsleep(1); //延时 1 秒}close(fd);return 0;
}
此应用程序也非常简单,仅只需实现用户逻辑代码即可,循环点亮、熄灭 LED,并不需要实现硬件操 作相关,示例代码中调用了 open、write、close 这三个系统调用 API 接口,open 和 close 分别用于打开/关闭 LED 设备,write 写入数据传给 LED 驱动,传入 0 熄灭 LED,传入非 0 点亮 LED。
二、库函数
前面给大家介绍了系统调用,系统调用是内核直接向应用层提供的应用编程接口,譬如 open、write、 read、close 等,关于这些系统调用后面会给大家进行详细介绍。编写应用程序除了使用系统调用之外,我们 还可以使用库函数,本小节来聊一聊库函数。
在Linux环境下,C语言程序的开发通常需要使用各种库函数来简化编程任务。Linux系统为C语言程序提供了丰富的标准库函数,涵盖了输入输出、字符串处理、内存管理、文件操作、时间和日期、数学运算等多个方面。
以下是一些常用的C语言库函数的介绍:
2.1、标准输入输出库(stdio.h
)
该库包含了进行输入输出操作的函数。
printf
:用于格式化输出。 int printf(const char *format, ...);
scanf
:从标准输入读取数据,按照指定的格式。 int scanf(const char *format, ...);
fopen
:打开一个文件。
FILE *fopen(const char *filename, const char *mode);
fclose
:关闭打开的文件。
int fclose(FILE *stream);
fread
:从文件中读取数据。
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
fprintf
:向文件输出格式化数据。
int fprintf(FILE *stream, const char *format, ...);
2.2、字符串处理库(string.h
)
strlen
:返回字符串的长度。 size_t strlen(const char *str);
strcpy
:将一个字符串复制到另一个字符串。
char *strcpy(char *dest, const char *src);
strcat
:将两个字符串连接起来。
char *strcat(char *dest, const char *src);
strcmp
:比较两个字符串。
int strcmp(const char *str1, const char *str2);
strtok
:分割字符串为多个子字符串。
char *strtok(char *str, const char *delim);
memcpy
:从源地址拷贝指定字节到目标地址。
void *memcpy(void *dest, const void *src, size_t n);
memset
:将指定值的字节填充到内存块。
void *memset(void *str, int c, size_t n);
2.3、内存管理库(stdlib.h
)
malloc
:分配指定大小的内存块。 void *malloc(size_t size);
calloc
:分配内存块并初始化为零
void *calloc(size_t num, size_t size);
free
:释放之前分配的内存块。
void free(void *ptr);
realloc
:重新调整已分配内存的大小。 void *realloc(void *ptr, size_t new_size);
exit
:终止程序并返回指定状态。
void exit(int status);
atoi
:将字符串转换为整数。
int atoi(const char *str);
rand
:生成随机数。
int rand(void);
srand
:设置随机数种子。
void srand(unsigned int seed);
2.4、 数学库(math.h
)
sqrt
:计算平方根。 double sqrt(double x);
pow
:计算幂。
double pow(double base, double exponent);
sin
、cos
、tan
:三角函数。
double sin(double x);
double cos(double x);
double tan(double x);
log
:计算自然对数。
double log(double x);
exp
:计算e的x次方。
double exp(double x);
ceil
:返回大于或等于参数的最小整数。
double ceil(double x);
floor
:返回小于或等于参数的最大整数。
double floor(double x);
2.5、 时间和日期库(time.h
)
该库包含了处理日期和时间的函数。
time
:返回当前时间的秒数。
time_t time(time_t *t);
localtime
:将时间转换为本地时间。
struct tm *localtime(const time_t *timep);
strftime
:格式化日期和时间。
size_t strftime(char *str, size_t max, const char *format, const struct tm *tm);
difftime
:计算两个时间点的差异(单位为秒)。
double difftime(time_t end, time_t beginning);
三、标准 C 语言函数库
在 Linux 系 统 下 , 使 用 的 C 语 言 库 为 GNU C 语 言 函 数 库 ( 也 叫 作 glibc , 其 网 址 为 http://www.gnu.org/software/libc/),作为 Linux 下的标准 C 语言函数库。
进入到 http://www.gnu.org/software/libc/网址,如下所示:


glibc 源码的获取方式很简单,直接直接从 git 仓库下载,也可以通过 ftp 下载,如果大家有兴趣、或者 想要了解某一个库函数它的具体实现,那么就可以获取到它源码来进行分析,好了,这里就不再多说了!
确定 Linux 系统的 glibc 版本
前面提到过了,C 语言库是以动态库文件的形式提供的,通常存放在/lib 目录,它的命名方式通常是 libc.so.6,不过这个是一个软链接文件,它会链接到真正的库文件。 进入到 Ubuntu 系统的/lib 目录下,笔者使用的 Ubuntu 版本为 16.04,在我的/lib 目录下并没有发现 libc.so.6 这个文件,其实是在/lib/x86_64-linux-gnu 目录下,进入到该目录:
可以看到 libc.so.6 链接到了 libc-2.23.so 库文件,2.23 表示的就是这个 glibc 库的版本号为 2.23。除此之 外,我们还可以直接运行该共享库来获取到它的信息,如下所示:

从打印信息可以看到,笔者所使用的 Ubuntu 系统对应的 glibc 版本号为 2.23。
四、main函数
对学习过 C 语言编程的读者来说,譬如单片机编程、Windows 应用编程等,main 函数想必大家再熟悉 不过了,很多编程开发都是以 main 函数作为程序的入口函数,同样在 Linux 应用程序中,main 函数也是作 为应用程序的入口函数存在,main 函数的形参一般会有两种写法,如果执行应用程序无需传参,则可以写 成如下形式:
int main(void)
{/* 代码 */
}
如果在执行应用程序的时候需要向应用程序传递参数,则写法如下:
int main(int argc, char **argv)
{/* 代码 */
}
argc 形参表示传入参数的个数,包括应用程序自身路径和程序名,譬如运行当前目录下的 hello 可执行 文件,并且传入参数,如下所示:
./hello 112233
那么此时参数个数为 2,并且这些参数都是作为字符串的形式传递给 main 函数:
argv[0]等于"./hello"
argv[1]等于"112233"
有传参时 main 函数的写法并不只有这一种,只是这种写法最常用,对于其它的写法,后面学习过程中 如果遇到了再给大家进行讲解,这里暂时先不去管。
五、开发环境
对于编程开发,大家可能都比较关心开发环境的问题,譬如本书将使用什么 IDE 编写应用程序之类的 问题,本小节将对开发环境的问题进行一个简单介绍。
在 Linux 操作系统下,也有很多比较好用的 IDE 软件,可以帮助我们更为轻松的进行软件开发,譬如 Eclipse、vscode 等,如果你会使用 Eclipse,可以在 Ubuntu 系统下安装 Eclipse 进行 Linux 应用开发
小编使用的是VSCode,vscode 是一个代码编辑器,提供了很多好用的插件,譬如语法检测、高亮显示、智能补全等,大家可以根据自己的选择安装插件。
相关文章:

Linux-C/C++--初探linux应用编程概念
对于大多数首次接触 Linux 应用编程的读者来说,可能对应用编程(也可称为系统编程)这个概念并不 太了解,所以在正式学习 Linux 应用编程之前,笔者有必要向大家介绍这些简单基本的概念,从整体上认识 到应用编…...
用sklearn运行分类模型,选择AUC最高的模型保存模型权重并绘制AUCROC曲线(以逻辑回归、随机森林、梯度提升、MLP为例)
诸神缄默不语-个人CSDN博文目录 文章目录 1. 导入包2. 初始化分类模型3. 训练、测试模型,绘图,保存指标 1. 导入包 from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClass…...

动手学大数据-3社区开源实践
目录 数据库概览: MaxComput: HAWQ: Hologres: TiDB: Spark: ClickHouse: Apache Calcite 概览 Calcite RBO HepPlanner 优化规则(Rule) 内置有100优化规则 …...

使用Pydantic驾驭大模型
本文介绍Pydantic 库,首先介绍其概念及优势,然后通过基本示例展示如何进行数据验证。后面通过多个示例解释如何在LangChain中通过Pydantic进行数据验证,保证与大模型进行交互过程中数据准确性,并显示清晰的数验证错误信息。 Pydan…...

【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发之常见布局
目录 1 -> 自适应布局 1.1 -> 线性布局 1.1.1 -> 线性布局的排列 1.1.2 -> 自适应拉伸 1.1.3 -> 自适应缩放 1.1.4 -> 定位能力 1.1.5 -> 自适应延伸 1.2 -> 层叠布局 1.2.1 -> 对齐方式 1.2.2 -> Z序控制 1.3 -> 弹性布局 1.3.1…...

【论文投稿】Python 网络爬虫:探秘网页数据抓取的奇妙世界
目录 前言 一、Python—— 网络爬虫的绝佳拍档 二、网络爬虫基础:揭开神秘面纱 (一)工作原理:步步为营的数据狩猎 (二)分类:各显神通的爬虫家族 三、Python 网络爬虫核心库深度剖析 &…...
队列的基本用法
以下是关于 C 语言中队列的详细知识,包括队列的生成、相关函数使用以及其他重要概念: 一、队列的概念 队列是一种线性数据结构,它遵循先进先出(First In First Out,FIFO)的原则,就像日常生活中…...

网络安全VS数据安全
关于网络安全和数据安全,我们常听到如下两种不同声音: 观点一:网络安全是数据安全的基础,把当年做网络安全的那一套用数据安全再做一遍。 观点二:数据安全如今普遍以为是网络安全的延伸,实际情况是忽略数据…...

Linux(NFS服务)
赛题拓扑: 题目: NFS: 共享/webdata/目录。用于存储AppSrv主机的WEB数据。仅允许AppSrv主机访问该共享。 [rootstoragesrv ~]# yum install nfs-utils -y [rootstoragesrv ~]# mkdir /webdata [rootstoragesrv ~]# chmod -R ow /webdata …...

python编程-OpenCV(图像读写-图像处理-图像滤波-角点检测-边缘检测)边缘检测
OpenCV中边缘检测四种常用算子: (1)Sobel算子 Sobel算子是一种基于梯度的边缘检测算法。它通过对图像进行卷积操作来计算图像的梯度,并将梯度的大小作为边缘的强度。它使用两个3x3的卷积核,分别用于计…...

SSM课设-学生管理系统
【课设者】SSM课设-学生管理系统 技术栈: 后端: SpringSpringMVCMybatisMySQLJSP 前端: HtmlCssJavaScriptEasyUIAjax 功能: 学生端: 登陆 学生信息管理 个人信息管理 老师端: 多了教师信息管理 管理员端: 多了班级信息管理 多了年级信息管理 多了系统用户管理...

【Pytorch实用教程】TCN(Temporal Convolutional Network,时序卷积网络)简介
文章目录 TCN的基本特点TCN的优点TCN的应用场景典型的TCN架构总结TCN(Temporal Convolutional Network,时序卷积网络)是一种用于处理序列数据的深度学习模型,尤其适用于时间序列预测、语音识别、自然语言处理等任务。它利用卷积神经网络(CNN)来处理时序数据,相比于传统的…...

网络安全 | 什么是正向代理和反向代理?
关注:CodingTechWork 引言 在现代网络架构中,代理服务器扮演着重要的角色。它们在客户端和服务器之间充当中介,帮助管理、保护和优化数据流。根据代理的工作方向和用途,代理服务器可分为正向代理和反向代理。本文将深入探讨这两种…...

3 前端(中):JavaScript
文章目录 前言:JavaScript简介一、ECMAscript(JavaScript基本语法)1 JavaScript与html结合方式(快速入门)2 基本知识(1)JavaScript注释(和Java注释一样)(2&am…...

VIT论文阅读与理解
transform网络结构 vision transform网络结构 图1:模型概述。我们将图像分割成固定大小的补丁,线性嵌入每个补丁,添加位置嵌入,并将结果向量序列馈送到标准Transformer编码器。为了执行分类,我们使用标准方法向序列中添…...

JavaScript笔记APIs篇01——DOM获取与属性操作
黑马程序员视频地址:黑马程序员前端JavaScript入门到精通全套视频教程https://www.bilibili.com/video/BV1Y84y1L7Nn?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes&p78https://www.bilibili.com/video/BV1Y84y1L7Nn?…...

SQL表间关联查询详解
简介 本文主要讲解SQL语句中常用的表间关联查询方式,包括:左连接(left join)、右连接(right join)、全连接(full join)、内连接(inner join)、交叉连接&…...
select函数
系统调用 select()可用于执行 I/O 多路复用操作,调用 select()会一直阻塞,直到某一个或多个文件描述符成为就绪态(可以读或写)。其函数原型如下所示: #include <sys/select.h> int select(int nfds, fd_set *re…...
建造者模式(或者称为生成器(构建器)模式)
一、什么是建造者模式? 将复杂对象的构建与表示进行分离,使得统一的构建过程,可以创建出不同的对象表现模式 就是将复杂对象里面的成员变量,设置不同的值,使得生成出来的对象拥有不同的属性值; 二、特点…...
【深度学习】Huber Loss详解
文章目录 1. Huber Loss 原理详解2. Pytorch 代码详解3.与 MSELoss、MAELoss 区别及各自优缺点3.1 MSELoss 均方误差损失3.2 MAELoss 平均绝对误差损失3.3 Huber Loss 4. 总结4.1 优化平滑4.2 梯度较好4.3 为什么说 MSE 是平滑的 1. Huber Loss 原理详解 Huber Loss 是一种结合…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...