树莓派5B - 零基础应用开发(第二期)
基础知识篇(适用于零基础想学习Linux操作系统的的小白新手)
核心思想
Linux 系统下,一切皆文件,也包括各种硬件设备(一定要有这个概念)。
系统调用
系统调用(system call)其实是 Linux 内核提供给应用层的应用编程接口(API),是 Linux 应用层进入内核的入口。
字符设备驱动
字符设备驱动是 Linux 内核中一种常见的设备驱动类型,主要用于与字符设备交互,如串口、键盘等。这类驱动程序通过实现特定的文件操作(如读取、写入、打开、关闭等)与用户空间程序交互。
字符设备按字节流方式进行数据传输。
字节流(Byte Stream)是一种连续的数据传输方式,数据以字节为单位顺序传输。字节流的本质是按字节传递数据,而不是按块 或其他更大单位, 这意味着接收方按顺序获取每个字节,并且可 以立即处理,不需要等待全 部数据传输完毕。
库函数
库函数也就是 C 语言库函数,C 语言库是应用层使用的一套函数库,在 Linux 下,通常以动态(.so) 库文件的形式提供,存放在根文件系统/lib 目录下,C 语言库函数构建于系统调用之上,也就是说库函数其大部分都是由系统调用封装而来的,但是也有一些库函数不调用任何的系统调用,例如:字符串处理函数 strlen()、strcat()、memcpy()、memset()、strchr()。
.so 文件代表共享库(shared object),即动态链接库。.so 文件可以包含多 种后缀,常见的是 .so 和 .so.数字。这两种后缀有不同的含义,通常涉及库 的版本控制和符号链接
.so 文件:.so 文件是共享库文件的基础形式,代表 “shared object”。它通常 是开发过程中和动态链接时使用的版本,不带具体的版本号。
.so.数字 文件:带有版本号的 .so.数字 文件表示具体版本的共享库。数字后 缀通常用于指代共享库的 API 或 ABI 版本。应用程序在运行时会链接到特 定版本的库,以确保库接口的兼容性。
符号链接与版本控制:在大多数情况下,系统会创建符号链接(symlink), 使没有版本号的 .so 文件指向某个特定版本的库文件。
libc.so -> libc.so.6
libpthread.so -> libpthread.so.0
① 库函数是属于应用层,而系统调用是内核提供给应用层的编程接口,属 于系统内核的一部分
② 库函数运行在用户空间,调用系统调用会由用户空间(用户态)陷入到 内核空间(内核态)
③ 库函数通常是有缓存的,而系统调用是无缓存的,所以在性能、效率上, 库函数通常要优于系统调 用;
④ 可移植性:库函数相比于系统调用具有更好的可移植性
文件描述符
例如调用 open 函数会有一个返回值,在 open 函数执行成功的情况下,会返回一个非负整数,该返回值就是一个文件描述符(file descriptor)文件描述符是一个非负整数;对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。
当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返 回一个文件描述符,用于指代被打开的文件,所有执行 IO 操作的系统调用 都是通过文件描述符来索引到对应的文件。
当调用 read/write 函数进行文件读写时,会将文件描述符传送给 read/write 函数。
在 Linux 系统下,我们可以通过 ulimit 命令来查看进程可打开的最大文件 数 ulimit -n 。该最大值默认情况下是 1024。每一个被 打开的文件在同一 个进程中都有 一个唯一的文件描述符,不会重复,如果 文件被关闭后, 它对应的文件描述 符将会被释放,那么这个文件描述符将 可以再次分配 给其它打开的文件、与对应的文件绑定起来。
每次给打开的文件分配文件描述符都是从最小的没有被使用的文件描述符 (0~1023)开始,当之前打开的文件被关闭之后,那么它对应的文件描述符 会被释放,释放之后也就成为了一个没有被使用的文件描述符了。
0、1、2 这三个文件描述符已经默认被系统占用 了,分别分配给了系统标准输入(0)、标准输出(1)以及标准错误(2)
Linux 系统下,一切皆文件,也包括各种硬件设备
open
打开文件(举着一个例子,后面的read,write,close不详细说明):在 Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件;open 函数用于打开文件,当然除 了打开已经存在的文件之外,还可以创建一个新的文件。
man 手册
man 命令后面跟着两个参数,数字 2 表示系统调用,man 命令除了可以查看系统调用的帮助信息外,还可以查看 Linux 命令(对应数1)以及标准 C 库函数(对应数字 3)所对应的帮助信息;最后一个 参数 open 表示需要查看的系统调用函数名。
lseek
每个打开的文件,系统都会记录它的读写位置偏移量,我们也把这个读写位 置偏移量称为读写偏移 量,记录了文件当前的读写位置,当调用 read()或 write()函数对文件进行读写操作时,就会从当前读写位置偏移量开始进行数据读写。
练习
1.打开一个已经存在的文件(例如 yx_test_file.c),使用只读方式;然后打开一个新建文件(例如 yx_test_file_new.c),使用只写方式,新建文件的权限设置如下: 文件所属者拥有读、写、执行权限; 同组用户与其他用户只有读权限。 从 src_file 文件偏移头部 500 个字节位置开始读取 1Kbyte 字节数据,然后将读取出来的数据写入到 dest_file 文件中,从文件开头处开始写入,1Kbyte 字节大小,操作完成之后使用 close 显式关闭所有文件, 然后退出程序。
2.通过 open 函数判断文件是否存在(例如 0),并将判断结果显示出来。
3.新建一个文件(例如 new_file),新建文件的权限设置为: 文件所属者拥有读、写、执行权限;同组用户与其他用户只有读权限。 使用只写方式打开文件,将文件前 1Kbyte 字节数据填充为 0x00,将下 1Kbyte 字节数据填充为 0xFF, 操作完成之后显式关闭文件,退出程序。
4.打开一个已经存在的文件(例如 test_file),通过 lseek 函数计算该文件的大小,并打印出来
练习源码部分(完整源码)
练习1
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int fd;
int ret;
char buffer[1024];
int i;
/* 打开文件 */
fd = open("./yx_test_file_hole.c", O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
/* 将文件读写位置移动到偏移文件头 4096 个字节(4K)处 */
ret = lseek(fd, 4096, SEEK_SET);
if (-1 == ret)
{
perror("lseek error");
goto err;
}
/* 初始化 buffer 为 0xFF */
memset(buffer, 0xFF, sizeof(buffer));
/* 循环写入 4 次,每次写入 1K */
for (i = 0; i < 4; i++)
{
ret = write(fd, buffer, sizeof(buffer));
if (-1 == ret)
{
perror("write error");
goto err;
}
}
ret = 0;
printf("success to it\n");
err:
/* 关闭文件 */
close(fd);
exit(ret);
}
练习2
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int fd;
int ret;
char buffer[1024];
int i;
/* 打开文件 */
fd = open("./yx_test_file_hole.c", O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
printf("success to it\n");
/* 关闭文件 */
close(fd);
exit(ret);
}
练习3
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char buffer[16];
int fd;
int ret;
/* 打开文件 */
fd = open("./yx_test_file.c", O_RDWR | O_APPEND);
if (-1 == fd)
{
perror("open error");
exit(-1);
}
/* 初始化 buffer 中的数据 */
memset(buffer, 0x55, sizeof(buffer));
/* 写入数据: 写入 4 个字节数据 */
ret = write(fd, buffer, 4);
if (-1 == ret)
{
perror("write error");
goto err;
}
/* 将 buffer 缓冲区中的数据全部清 0 */
memset(buffer, 0x00, sizeof(buffer));
/* 将位置偏移量移动到距离文件末尾 4 个字节处 */
ret = lseek(fd, -4, SEEK_END);
if (-1 == ret)
{
perror("lseek error");
goto err;
}
/* 读取数据 */
ret = read(fd, buffer, 4);
if (-1 == ret)
{
perror("read error");
goto err;
}
printf("0x%x 0x%x 0x%x 0x%x\n", buffer[0], buffer[1],
buffer[2], buffer[3]);
ret = 0;
err:
/* 关闭文件 */
close(fd);
exit(ret);
}
练习4
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
unsigned char buffer1[4], buffer2[4];
int fd1, fd2;
int ret;
int i;
/* 创建新文件 test_file 并打开 */
fd1 = open("./test_file", O_RDWR | O_CREAT | O_EXCL,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (-1 == fd1)
{
perror("open error");
exit(-1);
}
/* 再次打开 test_file 文件 */
fd2 = open("./test_file", O_RDWR);
if (-1 == fd2)
{
perror("open error");
ret = -1;
goto err1;
}
/* buffer 数据初始化 */
buffer1[0] = 0x11;
buffer1[1] = 0x22;
buffer1[2] = 0x33;
buffer1[3] = 0x44;
buffer2[0] = 0xAA;
buffer2[1] = 0xBB;
buffer2[2] = 0xCC;
buffer2[3] = 0xDD;
/* 循环写入数据 */
for (i = 0; i < 4; i++)
{
ret = write(fd1, buffer1, sizeof(buffer1));
if (-1 == ret)
{
perror("write error");
goto err2;
}
ret = write(fd1, buffer2, sizeof(buffer2));
if (-1 == ret)
{
perror("write error");
goto err2;
}
}
/* 将读写位置偏移量移动到文件头 */
ret = lseek(fd1, 0, SEEK_SET);
if (-1 == ret)
{
perror("lseek error");
goto err2;
}
/* 读取数据 */
for (i = 0; i < 8; i++)
{
ret = read(fd1, buffer1, sizeof(buffer1));
if (-1 == ret)
{
perror("read error");
goto err2;
}
printf("%x%x%x%x", buffer1[0], buffer1[1],
buffer1[2], buffer1[3]);
}
for (i = 0; i < 8; i++)
{
ret = read(fd1, buffer2, sizeof(buffer2));
if (-1 == ret)
{
perror("read error");
goto err2;
}
printf("%x%x%x%x", buffer2[0], buffer2[1],
buffer2[2], buffer2[3]);
}
printf("\n");
ret = 0;
err2:
close(fd2);
err1:
/* 关闭文件 */
close(fd1);
exit(ret);
}
如果大家有什么其他好想法,欢迎在讨论区讨论!!!
此系列会持续更新,大家一起加油呀,一起进步呀!!!
谢谢大家的支持!!!点个小赞赞吧!!!