基于I.MX6ULL.MINI开发板
- 标准I/O库
- 链接
- 目录
- 删除文件
- 正则表达式
- 系统标识
- 时间
- 堆内存
- 信号
- 标准信号
- 进程
- 进程组
- 进程间通信
- 线程
- 互斥锁
- 线程安全
本文章是入门篇的概念,有点零散,后续需要补充复习
**inode(索引节点)**是 Linux 和 Unix 文件系统(如ext4、xfs)中的数据结构,用于存储文件的元信息(metadata),而不存储文件内容或文件名。
inode 存储的信息
文件类型(普通文件、目录、符号链接等)
文件权限(rwx)
所有者(UID)和组(GID)
文件大小
创建、修改、访问时间(ctime、mtime、atime)
链接计数(有多少硬链接指向这个文件)
数据块指针(指向文件数据所在的磁盘块)
inode 不包含的信息
文件名:文件名存储在目录(dirent 结构)中,并通过 inode 号与 inode 关联。
在测试函数strerror
时
用命令gcc -o test test.c
编译下面的代码时报错
[/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/14/…/…/…/x86_64-linux-gnu/Scrt1.o: in function ’_start‘: (.text+0x1b): undefined reference to `main’collect2: error: ld returned 1 exit status
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>int main(void)
{int fd;/* 打开文件 */fd = open("./test_file", O_RDONLY);if (-1 == fd) {printf("Error: %s\n", strerror(errno));return -1;}close(fd);return 0;
}
原因是没有使用共享库
使用命令gcc -shared -g -o test test.c
就能编译成功了
命令du -h 文件名
,可以查看文件实际占用存储块的大小
函数menset
,给数组赋值
void *memset(void *str, int c, size_t n)
同一个进程中多次调用 open 函数打开同一个文件,各数据结构之间的关系如下图所示
不同进程中分别使用 open 函数打开同一个文件,其数据结构关系图如下所示:
同一个进程中通过 dup(dup2)函数对文件描述符进行复制,其数据结构关系如下图所示:
标准I/O库
函数FILE *fopen(const char *path, const char *mode);
参数mode取值如下
定义一个用于存放数据的 buf,起始地址以 4096 字节进行对齐
static char buf[8192] __attribute((aligned (4096)));
__attribute 是 gcc 支持的一种机制(也可以写成__attribute __) ,可用于设置函数属性、变量属性以及类型属性等
fd = open("./test_file", O_WRONLY | O_CREAT | O_TRUNC, 0664);
0664 是文件权限(mode),用于指定新创建文件的访问权限。在 open() 调用中,它只有在 O_CREAT 标志存在时才生效。
查看文件类型方法
命令stat
或ls
stat
可以看到很详细的信息
ls -l
通过符号表示文件的类型
Linux 系统中,可将硬件设备分为字符设备和块设备,所以就有了字符设备文件和块设备文件两种文件类型。虽然有设备文件,但是设备文件并不对应磁盘上的一个文件,也就是说设备文件并不存在于磁盘中,而是由文件系统虚拟出来的,一般是由内存来维护,当系统关机时,设备文件都会消失;字符设备文件一般存放在Linux 系统/dev/目录下,所以 /dev 也称为虚拟文件系统 devfs 。
Ubuntu 发行版对 ls 命令做了别名处理,执行 ls命令的时候携带了一些选项,而这些选项会访问文件的一些信息,所以导致出现"权限不够"问题,这也说明,只拥有读权限、是没法访问目录下的文件的;为了确保使用的是ls 命令本身,执行时需要给出路径的完整路径/bin/ls
链接
硬链接:ln 源文件 链接文件
软链接:ln -s 源文件 链接文件
硬链接:int link(const char *oldpath, const char *newpath);
软链接:int symlink(const char *target, const char *linkpath);
取出软链接文件中存储的路径信息:ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
目录
创建目录:int mkdir(const char *pathname, mode_t mode);
删除目录:int rmdir(const char *pathname);
打开目录:DIR *opendir(const char *name);
读取目录:struct dirent *readdir(DIR *dirp);
和void rewinddir(DIR *dirp);
关闭目录:int closedir(DIR *dirp);
获取进程当前的工作目录:char *getcwd(char *buf, size_t size);
修改当前的工作目录:int chdir(const char *path);
和int fchdir(int fd);
删除文件
系统调用:uint unlink(const char *pathname);
C库函数:int remove(const char *pathname);
正则表达式
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <regex.h>
#include <string.h>
int main(int argc, char *argv[])
{regmatch_t pmatch = {0};regex_t reg;char errbuf[64];int ret;char *sptr;int length;int nmatch; //最多匹配出的结果/*作用:检查命令行参数数量是否正确(应有 3 个参数)。argv[1]:正则表达式argv[2]:待匹配字符串argv[3]:最大匹配次数如果参数不符合要求,程序退出。*/if (4 != argc) {/*********************************** 执行程序时需要传入两个参数:* arg1: 正则表达式* arg2: 待测试的字符串* arg3: 最多匹配出多少个结果**********************************/fprintf(stderr, "usage: %s <regex> <string> <nmatch>\n", argv[0]);exit(0);}/* 编译正则表达式regcomp(®, argv[1], REG_EXTENDED) 编译正则表达式如果编译失败,使用 regerror() 输出错误信息,并退出。*/if(ret = regcomp(®, argv[1], REG_EXTENDED)) {regerror(ret, ®, errbuf, sizeof(errbuf));fprintf(stderr, "regcomp error: %s\n", errbuf);exit(0);}/* 赋值操作sptr 指向待匹配的字符串length 记录 sptr 长度nmatch 由 argv[3] 转换为整数,代表最多匹配多少次*/sptr = argv[2]; //待测试的字符串length = strlen(argv[2]);//获取字符串长度nmatch = atoi(argv[3]); //获取最大匹配数/* 匹配正则表达式循环 nmatch 次,每次尝试匹配正则regexec(®, sptr, 1, &pmatch, 0) 执行正则匹配pmatch.rm_so:匹配子串的起始位置pmatch.rm_eo:匹配子串的结束位置如果 regexec() 失败(无匹配),打印错误并跳转到 out 释放正则资源。*/for (int j = 0; j < nmatch; j++) {char temp_str[100];/* 调用 regexec 匹配正则表达式 */if(ret = regexec(®, sptr, 1, &pmatch, 0)) {regerror(ret, ®, errbuf, sizeof(errbuf));fprintf(stderr, "regexec error: %s\n", errbuf);goto out;}if(-1 != pmatch.rm_so) {//pmatch.rm_so == pmatch.rm_eo 时,匹配的是 空字符串,需要跳过 1 个字符,防止死循环。if (pmatch.rm_so == pmatch.rm_eo) {//空字符串sptr += 1;length -= 1;printf("\n"); //打印出空字符串if (0 >= length)//如果已经移动到字符串末尾、则退出break;continue; //从 for 循环开始执行}//memcpy() 提取 sptr 中匹配的子串,并存入 temp_str//printf("%s\n", temp_str); 打印匹配结果memset(temp_str, 0x00, sizeof(temp_str));//清零缓冲区memcpy(temp_str, sptr + pmatch.rm_so,pmatch.rm_eo - pmatch.rm_so);//将匹配出来的子字符串拷贝到缓冲区printf("%s\n", temp_str); //打印字符串//跳过当前匹配的部分,继续匹配剩余字符串//避免死循环(如果 length <= 0,停止匹配)sptr += pmatch.rm_eo;length -= pmatch.rm_eo;if (0 >= length)break;}}/* 释放正则表达式 */
out:regfree(®);exit(0);}
./regex_program "a[0-9]+" "abc a12 b34 a56 a78 xyz" 3 //代码执行
//匹配 a 后跟随 至少一个数字
//只匹配 最多 3 次(nmatch=3)
/*
结果
a12
a56
a78
*/
系统标识
struct utsname {char sysname[]; /* 当前操作系统的名称 */char nodename[]; /* 网络上的名称(主机名) */char release[]; /* 操作系统内核版本 */char version[]; /* 操作系统发行版本 */char machine[]; /* 硬件架构类型 */#ifdef _GNU_SOURCEchar domainname[];/* 当前域名 */#endif
};
struct sysinfo {long uptime; /* 自系统启动之后所经过的时间(以秒为单位) */unsigned long loads[3]; /* 1, 5, and 15 minute load averages */unsigned long totalram; /* 总的可用内存大小 */unsigned long freeram; /* 还未被使用的内存大小 */unsigned long sharedram; /* Amount of shared memory */unsigned long bufferram; /* Memory used by buffers */unsigned long totalswap; /* Total swap space size */unsigned long freeswap; /* swap space still available */unsigned short procs; /* 系统当前进程数量 */unsigned long totalhigh; /* Total high memory size */unsigned long freehigh; /* Available high memory size */unsigned int mem_unit; /* 内存单元大小(以字节为单位) */char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */
};
时间
GMT 时间
GMT(Greenwich Mean Time)中文全称是格林威治标准时间,这个时间系统的概念在 1884 年被确立,由英国伦敦的格林威治皇家天文台计算并维护,并在之后的几十年向欧陆其它国家扩散。
由于从 19 实际开始,因为世界各国往来频繁,而欧洲大陆、美洲大陆以及亚洲大陆都有各自的时区,所以为了避免时间混乱,1884 年,各国代表在美国华盛顿召开国际大会,通过协议选出英国伦敦的格林威治作为全球时间的中心点,决定以通过格林威治的子午线作为划分东西两半球的经线零度线(本初子午线、零度经线),由此格林威治标准时间因而诞生!
所以 GMT 时间就是英国格林威治当地时间,也就是零时区(中时区)所在时间,譬如 GMT 12:00 就是指英国伦敦的格林威治皇家天文台当地的中午 12:00,与我国的标准时间北京时间(东八区)相差 8 个小时,即早八个小时,所以 GMT 12:00 对应的北京时间是 20:00。
UTC 时间
UTC(Coordinated Universal Time)指的是世界协调时间(又称世界标准时间、世界统一时间),是经过平均太阳时(以格林威治时间 GMT 为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间,计算过程相当严谨精密,因此若以「世界标准时间」的角度来说,UTC 比 GMT 来得更加精准
查看UTC时间:date -u
jiffies 是内核中定义的一个全局变量,内核使用 jiffies 来记录系统从启动以来的系统节拍数,所以这个变量用来记录以系统节拍时间为单位的时间长度,Linux 内核在编译配置时定义了一个节拍时间,使用节拍率(一秒钟多少个节拍数)来表示
堆内存
申请堆内存有malloc()
和calloc()
#define MALLOC_MEM_SIZE (1 * 1024 * 1024)
int main(int argc, char *argv[])
{//声明一个字符指针 base,并初始化为 NULLchar *base = NULL;/* 申请堆内存 *///malloc(MALLOC_MEM_SIZE) 从堆上申请 MALLOC_MEM_SIZE 字节的内存。//malloc() 返回 void* 指针,需要强制转换为 char*base = (char *)malloc(MALLOC_MEM_SIZE);if (NULL == base) {printf("malloc error\n");exit(-1);}/* 初始化申请到的堆内存 */memset(base, 0x0, MALLOC_MEM_SIZE);/* 使用内存 *//* ...... *//* 释放内存 */free(base);return 0;
}
**calloc()**在堆中动态地分配 nmemb 个长度为 size 的连续空间,并将每一个字节都初始化为 0
calloc()与 malloc()的一个重要区别是:calloc()在动态分配完内存后,自动初始化该内存空间为零,而malloc()不初始化,里边数据是未知的垃圾数据。
一下的两种写法是等价的
// calloc()分配内存空间并初始化
char *buf1 = (char *)calloc(10, 2);// malloc()分配内存空间并用 memset()初始化
char *buf2 = (char *)malloc(10 * 2);
memset(buf2, 0, 20);
信号
标准信号
核心转储文件(Core Dump)是什么?
核心转储(Core Dump)是 程序崩溃时,操作系统 自动保存的内存快照。它包含了进程在崩溃时的 内存数据、CPU 寄存器、堆栈信息、打开的文件描述符 等信息,帮助开发者 调试程序崩溃的原因。
核心转储文件通常命名为 core 或 core.pid(pid 为进程 ID),默认生成在程序的工作目录。
核心转储文件的作用
1.调试崩溃原因
查看程序崩溃时的调用栈、变量值、内存状态等信息。
2.分析段错误(Segmentation Fault, SIGSEGV)
访问非法地址时,操作系统会触发 SIGSEGV,并生成 core dump。
3.检查死锁、资源泄漏
通过分析核心转储文件,检查线程死锁、文件描述符泄漏等问题。
4.重现难以复现的 Bug
记录程序崩溃时的完整状态,方便后续分析。
忽略信号(Ignore Signal)
进程接收到信号后,可以选择不处理,相当于信号被丢弃。
捕获信号(Catch Signal)
进程可以定义自定义信号处理函数,在收到信号时执行特定操作,而不是采用默认行为。
进程
进程组
setpgid(0,pgid) == setpgid(getpgrp(),pgid)
setpgid(pid,0) == setpgid(pid,pid)
setpgrp() == setpgid(0.0) == etpgid(getpgrp(),getpgrp())
./testapp &
中的 & 用于 将命令放入后台运行,使其在 后台执行,而不阻塞当前终端。
输出示例:[1] 12345
[1]: 作业编号(Job ID),可以用 jobs 命令查看。
12345:进程 ID(PID),可以用 kill 12345 终止该进程。
进程间通信
UNIX 是一种 多用户、多任务 的 操作系统,最早由 AT&T 贝尔实验室 在 1969 年开发。它广泛应用于服务器、工作站和嵌入式系统,以 稳定性、高效性和安全性 著称。
管道: 把一个进程连接到另一个进程的数据流称为管道,管道(Pipe)是一种进程间通信(IPC,Inter-Process Communication) 机制,允许一个进程的输出作为另一个进程的输入,常用于数据流式处理。
线程
线程处于分离状态(Detached State)意味着该线程的 资源在结束时会自动释放,而 不需要其他线程(如主线程)显式 join() 等待它结束。
互斥锁
Mutex(互斥锁) 是一种线程同步机制,用于防止多个线程同时访问共享资源,避免数据竞争和不一致。
执行流(Execution Flow)指的是 程序运行时指令的执行顺序,即 代码如何被逐步执行。它决定了程序如何处理 函数调用、条件判断、循环和多线程等,一个线程就是一条执行流
线程安全
信号处理函数的执行流(Signal Handling Execution Flow)
在 UNIX/Linux 系统中,信号(Signal)是一种 异步事件,可以在 程序执行的任何时刻打断正常的执行流,然后跳转到 信号处理函数(Signal Handler) 执行特定操作。
//pthread_create() 用于创建一个新线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);