Linux应用编程概念、文件IO、标准IO
学习任务:
1、 学习Linux 应用开发概念,什么是系统调用,什么是库函数
2、 学习文件IO:包括 read、write、open、close、lseek
3、 深入文件IO:错误处理、exit 等
4、 学习标准IO:FILE 指针、标准输入、标准输出、标准错误、fopen()、格式化 I/O
5、 使用的工具:ubuntu虚拟机、开发板
了解Linux应用编程概念
学习文件IO函数等基础知识
学习标准IO函数等基础知识
///
补充:静态库 /动态库
程序中调用的库有两种 静态库和动态库,不管是哪种库文件本质是还是源文件,只不过是二进制格式只有计算机能够识别
项目中使用库一般有两个目的,一个是为了使程序更加简洁不需要在项目中维护太多的源文件,另一方面是为了源代码保密
拿到了库文件(动态库、静态库)之后要想使用还必须有这些库中提供的API函数的声明,也就是头文件,把这些都添加到项目中
ar rcs 静态库的名字(libxxx.a) 原文件(.o)
gcc -shared 与位置无关的目标文件(.o) -o 动态库(libxxx.so)
实战再加强
1.1 Linux 应用开发概念
在 Linux 应用开发中,主要是创建各种应用程序来满足不同的需求,如网络应用、文件处理应用等。开发人员利用 Linux 系统提供的各种资源和接口来构建这些应用程序。
1.2 系统调用(System Call)
定义:系统调用是操作系统提供给用户程序(应用程序)的一组接口,它允许应用程序请求操作系统内核的服务。这些服务包括对硬件设备的访问、文件系统操作、进程管理等。
例如,当应用程序想要读取一个文件时,它不能直接访问磁盘硬件,而是通过系统调用(如read系统调用)向内核发出请求,内核再执行相应的硬件操作并将数据返回给应用程序。
特点: 运行于内核态,系统调用会导致用户程序从用户态切换到内核态。在用户态下,应用程序只能访问自己的内存空间等受限资源;而内核态下可以访问系统的所有资源,如硬件设备、内核数据结构等。
安全性:通过系统调用接口,操作系统可以对应用程序的操作进行安全检查和权限验证。例如,一个没有足够权限的应用程序不能直接访问某些受保护的文件或硬件设备。
提供基本功能:系统调用提供了最基本的操作系统功能,如创建进程(fork系统调用)、进程间通信(如pipe系统调用)、网络通信(如socket系统调用)等。
举例:在 C 语言中,open系统调用用于打开一个文件。其基本语法为int open(const char *pathname, int flags);
这里pathname
是要打开的文件路径,flags
指定打开文件的方式(如只读、只写、读写等)。当应用程序调用open
时,实际上是向内核发送一个请求,内核根据请求在文件系统中查找文件,并根据权限等因素决定是否打开文件,然后返回一个文件描述符给应用程序。
1.3 库函数(Library Function)
定义:库函数是建立在系统调用之上的函数库,它对系统调用进行了封装和扩展。这些函数库提供了更方便、更高级的编程接口,使得应用程序开发更加容易。
例如,C 标准库中的stdio.h库中的fopen函数,它在内部可能会调用系统调用open来实现文件的打开操作,但fopen提供了更易用的接口,如可以直接使用字符串形式的文件名(不需要像open那样进行一些底层的参数设置),并且可以自动处理一些错误情况。
可移植性:很多库函数是跨平台的,例如 C 标准库函数。这使得应用程序可以在不同的操作系统(只要支持该库)上进行编译和运行,而不需要针对每个操作系统的系统调用进行重写。
功能丰富:库函数提供了比系统调用更丰富的功能。除了基本的文件操作、内存管理等功能外,还可能包括数学计算(如sin、cos等函数)、字符串处理(如strcpy、strcat等函数)等各种功能
用户态执行:库函数主要在用户态执行,不需要频繁地进行用户态到内核态的切换(除非在库函数内部调用了系统调用),因此在一定程度上提高了执行效率。
举例
在 C++ 中,iostream库中的cout对象用于输出信息到标准输出设备(通常是控制台)。它是一个库函数,在内部会处理很多复杂的操作,如缓冲区管理、格式化输出等,这些操作可能会基于底层的系统调用(如写入到标准输出文件描述符的系统调用),但从应用程序开发者的角度来看,使用cout比直接使用系统调用更加方便、直观。
2.1 文件IO
标准IO的相关函数:fopen/fread/fwrite/fseek/fflush/fclose。
系统调用IO的相关函数:open/read/write/lseek/fsync/close。
二者区别:①系统调用IO函数每次操作都会进入内核。②标准IO函数引入了用户Buffer,先访问一次内核将数据存入Buffer,然后进行读写操作,不会频繁访问内核。但其底层仍然使用系统调用IO函数。
标准IO的内部,会分配一个用户空间的buffer,读写操作先经过这个buffer。在有必要时,才会调用底下的系统调用IO向内核发起操作。 所以:标准IO效率更高;但是要访问驱动程序时就不能使用标准IO,而是使用系统调用IO。
///
3.1 I/O中的错误处理
errno 变量:
在 Linux 的文件 I/O 操作中,errno是一个非常关键的全局变量。它被定义在<errno.h>头文件中。当系统调用(如文件 I/O 相关的open、read、write、close、lseek等)发生错误时,操作系统内核会将一个表示特定错误的整数值赋给errno。
不同的错误码对应不同的情况。例如:
EACCES(13):表示权限不足。这可能发生在试图以没有足够权限的方式打开文件时,比如以写的方式打开一个只读文件,或者用户没有执行某些操作(如访问特定目录)的权限。
ENOENT(2):表示文件或目录不存在。当调用open函数试图打开一个不存在的文件,并且没有使用O_CREAT标志时,就可能得到这个错误码。
EINVAL(22):表示无效的参数。例如,如果在open函数中传递了一个无效的标志组合,就可能导致这个错误码被设置。
在程序中使用errno时,需要注意的是,它的值只有在函数调用返回错误(通常是返回 - 1)时才有意义。而且,由于errno是全局变量,在多线程环境下可能会存在竞争条件,需要谨慎处理。
perror 函数:
perror函数的主要作用是将错误信息输出到标准错误输出(stderr)。它会首先输出传入的字符串参数,然后输出一个冒号和一个空格,接着输出与errno对应的错误信息。
例如,如果有以下代码片段:
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
int main() {
int fd = open("nonexistent_file", O_RDONLY);
if (fd == -1) {
perror("open");
}
return 0;
}
当open函数调用失败(因为文件不存在)时,perror函数会输出类似 “open: No such file or directory” 的内容。这里 “open” 是传入perror的字符串,后面是对应errno(ENOENT)的错误信息。
在文件 I/O 中的应用场景:在每个文件 I/O 操作之后,都应该检查返回值是否为 - 1,如果是,则调用perror函数来输出错误信息。这有助于在程序开发和调试过程中快速定位问题。例如,在write操作中,如果返回 - 1,可以使用perror来确定是磁盘空间不足(ENOSPC)还是其他权限或设备相关的问题。
exit 函数在文件 I/O 中的应用
功能:exit函数用于立即终止当前进程的执行。它会执行一些清理操作,如刷新标准 I/O 缓冲区,然后将控制权返回给操作系统。
当在文件 I/O 操作中发生严重错误,导致程序无法继续正常运行时,使用exit函数是一种合适的方式来停止程序。例如,如果一个程序依赖于某个配置文件的正确读取,而在打开或读取这个配置文件时发生了不可恢复的错误,继续执行程序可能没有意义,此时可以调用exit。
///
嵌入式Linux应用开发基础知识
Linux打工仔