嵌入式Linux应用层开发 I/O操作

GCC

GCC全称GNU Compiler Collection,是GNU项目的一部分,主要是一套编译器工具集,支持多种编程语言。

GCC主要作用是将源代码编译成机器语言,生成可执行文件或库文件。它也提供了一些优化选项,可以在编译过程中优化代码,提高程序运行的效率。

glibc

glibc对于Linux系统和基于Linux的应用程序至关重要,因为它提供了与操作系统交互的基本接口和运行时环境。应用程序通过调用glibc提供的函数来执行文件操作、内存管理、进程控制等操作。

POSIX

可移植操作系统接口,POSIX标准的主要目的是促进应用软件与多种类型的操作系统之间的兼容性。

预处理命令

在C语言编译过程中,预处理是其中的第一个阶段,它的主要目的是处理源代码文件中的预处理指令,将它们转换成编译器可以识别的形式。

预处理主要包含宏替换、文件包含、条件编译、注释移除等几种任务。
预处理的输出通常是经过预处理后的源代码文件,它会保存成一个临时文件,并作为编译器的输入。预处理器处理后的文件通常会比原始源文件大,因为它会展开宏和包含其它文件的内容。

gcc -E hello.c -o hello.i
  • -E:Expand的缩写,该参数指定gcc执行预处理操作。

编译

编译阶段,编译器会将经过预处理的源代码文件转换成汇编代码。
在这个阶段,编译器会将源代码翻译成机器能够理解的中间代码,包括词法分析、语法分析、语义分析和优化过程。

编译器会检查代码的语法和语义,生成对应的汇编代码。编译阶段是整个编译过程中最复杂和耗时的阶段之一,它对源代码进行了深入的分析和转换,确保了程序的正确性和性能。

gcc -S hello.i -o hello.s

汇编

将编译器生成的汇编代码转换为目标机器的机器语言代码,也就是目标代码。这个阶段由汇编器完成,其主要任务是将汇编指令翻译成目标机器的二进制形式。

最终,汇编器会将翻译和处理后的目标代码输出到目标文件,用于后续的链接和生成可执行程序或共享库文件。

gcc -c main.s -o main.o

链接

链接阶段,由链接器完成。
链接器将各个目标文件以及可能用到的库文件进行链接,生成最终的可执行程序。

在这个阶段,链接器会解析目标文件中的符号引用,并将它们与符号定义进行匹配,以解决符号的地址关联问题。

C语言的链接方式共有三种:静态链接、动态链接和混合链接。

静态链接是在编译时将所有需要的库文件整合到最终的可执行文件中。

动态链接是在运行时将所需的库文件加载到内存中,并与可执行文件进行链接。

我们也可以将自己编写的部分代码处理为动态库。
执行下面的指令将hello.o编译为动态链接库libhello.so。

gcc -fPIC -shared -o libhello.so hello.o
  • fPIC:这个选项告诉编译器为“位置无关码(Position Independent Code)”生成输出。在创建共享库时使用这个选项是非常重要的,因为它允许共享库被加载到内存中的任何位置,而不影响其执行。这是因为位置无关码使用相对地址而非绝对地址进行数据访问和函数调用,使得库在被不同程序加载时能够灵活地映射到不同的地址空间。
  • shared:这个选项指示GCC生成一个共享库而不是一个可执行文件。共享库可以被多个程序同时使用,节省了内存和磁盘空间。
  • libhello.so:Linux下的共享库名称以lib开头,扩展名为.so(表示共享对象)

Linux的默认动态链接库文件夹是/lib和/usr/lib。

Makefile

objects := hello.o \
		   main.o
		   
main: $(objects)
	gcc $(objects) -o main

#声明伪目标
.PHONY : clean
clean:
	rm main $(objects)

常见系统调用

open()系统调用用于打开一个标准的文件描述符。

系统调用_exit()
_exit()是由POSIX标准定义的系统调用,用于立即终止一个进程,定义在unistd.h中。这个调用确保进程立即退出,不执行任何清理操作

_exit()在子进程终止时特别有用,这可以防止子进程的终止影响到父进程(比如,防止子进程以外地刷新了父进程未写入的输出缓冲区。)

库函数exit()由C标准库提供的,定义在stdlib.h中。

文件描述符

当我们打开或创建一个文件(或套接字)时,操作系统会提供一个文件描述符,这是一个非负整数,我们可以通过它来进行读写操作。

文件描述符本身只是操作系统为应用程序操作底层资源(如文本、套接字等)所提供的一个引用或句柄。

文件描述符0、1、2是有特殊含义的。

  • 0:标准输入的文件描述符
  • 1:标准输出的文件描述符
  • 2:标准错误的文件描述符

文件描述符关联的数据结构

struct file
每个文件描述符都关联到内核一个struct file类型的结构体数据。

struct file{
	... f_count; //引用计数,管理文件对象的生命周期
	f_pos_lock; //保护文件位置的互斥锁
	f_pos;		//当前文件读写位置
	f_path;		//记录文件路径
	*f_inode;	//指向与文件相关联的inode对象的指针,该对象用于维护文件元数据,如文件类型、访问权限等。
	*f_op;		//指向文件操作函数表的指针,定义了文件支持的操作,如度、写、锁定等操作。
	*private_data;	//私有数据
}

所有的文件都有唯一的inode编号。

f_inode记录了文件的元数据。

struct path

struct path{
	*mnt;
	*dentry;
}
  • struct vfsmounnt:虚拟文件系统挂载点的表示,存储有关挂载文件系统的信息。
  • struct dentery:目录项结构体,代表了文件系统中的一个目录项。通常对应一个文件或目录的名字,通过这个属性,可以定位文件位置。
struct inode{
	i_mode;//文件类型和权限,指定了文件是普通文件、目录、字符设备、块设备等,以及它的访问权限(读、写、执行)。
	i_uid; //文件的用户ID,决定了文件的拥有者
	i_gid; //文件的组ID,决定了文件的拥有者组
	i_ino; //inode编号,是文件系统中文件的唯一标识
	i_size; //文件大小
}

打开的文件表数据结构

//用来维护一个进程所有打开文件信息的
struct files_struct{
	*fdt; //指向当前使用的文件描述符表
	next_fd; //存储下一个可用的最小文件描述符编号
}

文件描述符是一个非负整数,其值实际上就是关联的struct file在fd指向的数组或fd_array中的下标

总结

每个进程都有一个文件描述符表:
在这里插入图片描述
文件描述符表在底层通过数组来实现的。
文件描述符实际上是这个数组的偏移量。

执行代码int fd = open(“test.txt”, O_RDONLY); //描述符表会增加一项

在这里插入图片描述
在这里插入图片描述
当我们执行open()等系统调用时,内核会创建一个新的struct file,这个数据结构记录了文件的元数据(文件类型、权限等)、文件路径、支持的操作等,然后分配文件描述符,将struct file维护在文件描述符中,最后将文件描述符返回给应用程序。
我们可以通过后者对文件执行它所支持的各种函数操作,这些函数的函数指针都维护在struct file_operations数据结构中。文件描述符实质上是底层数据结构struct file的一个引用或者句柄,它为用户提供了操作底层文件的入口。

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值