学习任务:
1、 文件属性与目录:Linux 文件类型、stat、chmod、链接文件、目录文件
2、 字符串处理:字符串输入/输出、strlen、strcat、strcpy、memset、atoi()、atol()、atoll()
3、 系统信息:proc 虚拟文件系统(重点)、uname、sleep、malloc 和 free
4、 使用的工具:虚拟机、开发板
- 理解文件属性与目录的概念
- 学会Linux下字符串的处理
- 学会获取Linux系统信息
- 重点 proc 文件系统的内容
1.1 Linux 文件类型
普通文件、目录、字符设备文件、块设备文件、符号链接文件、管道文件、套接字文件
普通文件是最常见的文件类型;
目录也是一种文件类型;
设备文件对应于硬件设备;
符号链接文件类似于Windows的快捷方式;
管道文件用于进程间通信;
套接字文件用于网络通信
1.2 stat函数
Linux下可以使用stat命令查看文件的属性,其实这个命令内部就是通过调用stat()函数来获取文件属性的,stat函数是Linux中的系统调用,用于获取文件相关的信息(可通过"man 2 stat"命令查看)
每个文件都有一个唯一的 inode 节点编号,inode 是文件系统中用于存储文件元数据的数据结构。它包含了文件的各种属性信息,如文件的大小、创建时间、修改时间、访问权限、所有者、所属组等,inode 还包含了指向文件数据块的指针,用于在磁盘上定位和读取文件的内容。
文件系统通过 inode 来管理文件。当用户访问一个文件时,文件系统首先根据文件名找到对应的 inode,然后通过 inode 中的信息来读取文件的内容。
获取文件的inode节点编号以及文件大小,并将它们打印出来
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h> int main(void)
{ struct stat file_stat; int ret; /* 获取文件属性 */ ret = stat("./test_file", &file_stat); if (-1 == ret) { perror("stat error"); exit(-1); } /* 打印文件大小和inode编号 */ printf("file size: %ld bytes\n" "inode number: %ld\n", file_stat.st_size, file_stat.st_ino); exit(0);
}
为何两次编译出的inode一样?
1.3 chmod 修改文件权限
文件访问权限
在Linux 系统下,可以使用chmod命令修改文件权限,该命令内部实现方法其实是调用了chmod函数,chmod 函数是一个系统调用,函数原型如下所示(可通过"man 2 chmod"命令查看):
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
首先,使用该函数需要包含头文件<sys/stat.h>
函数参数及返回值如下所示:
- pathname:需要进行权限修改的文件路径,若该参数所指为符号链接,实际改变权限的文件是符号链接所指向的文件,而不是符号链接文件本身
- mode:该参数用于描述文件权限,与open函数的第三个参数一样,这里不再重述,可以直接使用八进制数据来描述,也可以使用相应的权限宏(单个或通过位或运算符" | "组合)
- 返回值:成功返回0;失败返回-1,并设置errno
文件权限对于文件来说是非常重要的属性,是不能随随便便被任何用户所修改的,要想更改文件权限,要么是超级用户(root)进程、要么进程有效用户ID与文件的用户ID(文件所有者)相匹
1.4 链接文件
在Linux 系统中有两种链接文件,分为软链接(也叫符号链接)文件和硬链接文件,软链接文件也就是前面的Linux系统下的七种文件类型之一,其作用类似于Windows下的快捷方式。那么硬链接文件又是什么呢?
首先,从使用角度来讲,两者没有任何区别,都与正常的文件访问方式一样,支持读、写以及执行。那它们的区别在哪呢?
使用ln 命令可以为一个文件创建软链接文件或硬链接文件,用法如下:
硬链接:ln 源文件 链接文件
软链接:ln -s 源文件 链接文件
遇到问题:
不允许将硬链接指向目录怎么解决?
- 硬链接不能用于目录,因为目录结构复杂,容易形成循环
使用ln命令创建的硬链接文件与源文件testhard都拥有相同的inode号,既然inode 相同,也就意味着它们指向了物理硬盘的同一个区块,仅仅只是文件名字不同而已,创建出来的硬链接文件与源文件对文件系统来说是完全平等的关系。
删除了硬链接文件或源文件其中之一,那文件所对应的inode以及文件内容在磁盘中的数据块会被文件系统回收吗?不会,因为 inode 数据结结构中会记录文件的链接数,这个链接数指的就是硬链接数,前面学的struct stat 结构体中的st_nlink 成员变量就记录了文件的链接数。
当为文件每创建一个硬链接,inode节点上的链接数就会加一,每删除一个硬链接,inode节点上的链接数就会减一,直到为0,inode 节点和对应的数据块才会被文件系统所回收,也就意味着文件已经从文件系统中被删除了。
为啥别的文件夹也是2 ????
软链接文件与源文件有着不同的inode号,如图 5.7.3所示,所以也就是意味着它们之间有着不同的数据块,但是软链接文件的数据块中存储的是源文件的路径名,链接文件可以通过这个路径找到被链接的源文件,它们之间类似于一种“主从”关系,当源文件被删除之后,软链接文件依然存在,但此时它指向的是一个无效的文件路径,这种链接文件被称为悬空链接,inode节点中记录的链接数并未将软链接计算在内
对于硬链接来说,存在一些限制情况:
- 不能对目录创建硬链接(超级用户可以创建,但必须在底层文件系统支持的情况下)
- 硬链接通常要求链接文件和源文件位于同一文件系统中
而软链接文件的使用并没有上述限制条件,优点如下所示:
- 可以对目录创建软链接;
- 可以跨越不同文件系统;
- 可以对不存在的文件创建软链接
1.5 目录文件
目录(文件夹)在Linux 系统也是一种文件
对于目录来说,其存储形式则是由inode节点和目录块所构成,目录块当中记录了有哪些文件组织在这个目录下,记录它们的文件名以及对应的inode编号
目录在文件系统中的存储方式与常规文件类似,常规文件包括了inode节点以及文件内
容数据存储块(block),目录块当中有多个目录项(或叫目录条目),每一个目录项(或目录条目)都会对应到该目录下的某一个文件,目录项当中记录了该文件的文件名以及它的inode节点编号,所以通过目录的目录块便可以遍历找到该目录下的所有文件以及所对应的inode节点。
所以对此总结如下:
- 普通文件由inode节点和数据块构成
- 目录由inode节点和目录块构成
创建目录mkdir
删除目录rmdir
///
2 字符串处理
2.1 字符串输入/输出
字符串输出:
puts() 函数 :puts()函数用来向标准输出设备(屏幕、显示器)输出字符串并自行换行
puts("Hello World!");
putchar() 函数:把参数c指定的字符(一个无符号字符)输出到标准输出设备,其输出可以是一个字符,可以是介于0~127之间的一个十进制整型数(包含0和127,输出其对应的ASCII码字符),也可以是用char类型定义好的一个字符型变量
fputc() 函数 : fputc()与 putchar()类似,也用于输出参数 c 指定的字符(一个无符号字符),与 putchar()区别在于,putchar()只能输出到标准输出设备,而fputc()可把字符输出到指定的文件中,既可以是标准输出、标准错误设备,也可以是一个普通文件
fputs() 函数 :同理,fputs()与 puts()类似,也用于输出一条字符串,与 puts()区别在于,puts()只能输出到标准输出设备,而fputs()可把字符串输出到指定的文件中,既可以是标准输出、标准错误设备,也可以是一个普通文件
字符串输入:
**gets()**函数用于从标准输入设备(譬如键盘)中获取用户输入的字符串
用户从键盘输入的字符串数据首先会存放在一个输入缓冲区中,gets()函数会从输入缓冲区中读取字符串存储到字符指针变量s所指向的内存空间,当从输入缓冲区中读走字符后,相应的字符便不存在于缓冲区,输入的字符串中就算是有空格也可以直接输入,字符串输入完成之后按回车即可,gets()函数不检查缓冲区溢出
例子:
#include <stdio.h>
#include <stdlib.h>int main() {char str[5];printf("请输入一个字符串:");gets(str);printf("你输入的字符串是:%s\n", str);return 0;
}
如果用户输入一个长度超过 4 个字符(因为还需要一个位置存储字符串结束符 \0)的字符串,比如 “abcde”,gets()函数会继续将字符存储到str数组中,这就会导致缓冲区溢出。缓冲区溢出可能会覆盖相邻的内存区域,引发程序崩溃、数据损坏或安全漏洞(如缓冲区溢出攻击)
为了避免这种情况,在 C11 标准中,gets()函数已经被弃用,建议使用更安全的函数,如fgets(),fgets()函数会限制读取的字符数量
fgets()函数最多读取 sizeof(str) - 1 个字符(为\0预留位置)
**getchar()**函数:用于从标准输入设备中读取一个字符(一个无符号字符)
fgets() :fgets()与 gets()一样用于获取输入的字符串
fgetc():与getchar()一样,用于读取一个输入字符
2.2 字符串长度 strlen()
使用该函数需要包含头文件<string.h>
需要进行长度计算的字符串,字符串必须包含结束字符’ \0 ‘,字符串结束字符’ \0 '不计算在内
sizeof 和 strlen 的区别
在程序当中,我们通常也会使用sizeof来计算长度,那strlen和sizeof有什么区别呢?
- sizeof是C语言内置的操作符关键字,而strlen是C语言库函数;
- sizeof仅用于计算数据类型的大小或者变量的大小,而strlen只能以结尾为’ \0 '的字符串作为参数;
- 编译器在编译时就计算出了sizeof的结果,而strlen必须在运行时才能计算出来;
- sizeof计算数据类型或变量会占用内存的大小,strlen计算字符串实际长度
2.3 字符串拼接strcat()
原型:
#include <string.h>
char *strcat(char *dest, const char *src);
char str1[100] = "Linux app strcat test, ";
char str2[] = "Hello World!";
strcat(str1, str2);
strcat()函数会把 src 所指向的字符串追加到dest所指向的字符串末尾,所以必须要保证dest有足够的存储空间来容纳两个字符串,否则会导致溢出错误;dest末尾的’ \0 ‘结束字符会被覆盖,src末尾的结束字符’ \0 ‘会一起被复制过去,最终的字符串只有一个’ \0 '。
strncat()
strncat可以指定源字符串追加到目标字符串的字符数量
如果源字符串src包含n个或更多个字符,则strncat()将n+1个字节追加到dest目标字符串(src中的n个字符加上结束字符’ \0 ')
strncat(str1, str2, 5);
2.4 字符串拷贝strcpy()
char *strcpy(char *dest, const char *src);
strcpy()会把 src(必须包含结束字符’ \0 ‘)指向的字符串复制(包括字符串结束字符’ \0 ')到dest,所以必须保证dest指向的内存空间足够大,能够容纳下src字符串,否则会导致溢出错误。
strncpy()与 strcpy()的区别在于,strncpy()可以指定从源字符串 src 复制到目标字符串dest的字符数量
把src 所指向的字符串复制到dest,最多复制n个字符。当n小于或等于src字符串长度(不包括结束字符的长度)时,则复制过去的字符串中没有包含结束字符’ \0 ‘;当n大于src字符串长度时,则会将src字符串的结束字符’ \0 '也一并拷贝过去,必须保证 dest 指向的内存空间足够大,能够容纳下拷贝过来的字符串,否则会导致溢出错误。
覆盖式拷贝
2.5 内存填充memset()
用于将某一块内存的数据全部设置为指定的值
void *memset(void *s, int c, size_t n);
参数c虽然是以int类型传递,但memset()函数在填充内存块时是使用该值的无符号字符形式,也就是函数内部会将该值转换为unsigned char类型的数据,以字节为单位进行数据填充
2.5 字符串转整形数据 atoi、atol、atoll 函数
atoi()、atol()、atoll()三个函数可用于将字符串分别转换为int、long int以及long long类型的数据
strtol、strtoll 函数可分别将字符串转为 long int 类型数据和 long long ing 类型数据,与 atol()、atoll()之间的区别在于,strtol()、strtoll()可以实现将多种不同进制数(譬如二进制表示的数字字符串、八进制表示的数字字符串、十六进制表示的数数字符串)表示的字符串转换为整形数据
3 系统信息
3.1 proc 虚拟文件系统(重点)
proc文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口,用户和应用程序可以通过proc文件系统得到系统信息和进程相关信息,对proc文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的是,proc文件系统是动态创建的,文件本身并不存在于磁盘当中、只存在于内存当中,与devfs一样,都被称为虚拟文件系统。
最初构建proc文件系统是为了提供有关系统中进程相关的信息,但是由于这个文件系统非常有用,因此内核中的很多信息也开始使用它来报告,或启用动态运行时配置。内核构建proc虚拟文件系统,它会将内核运行时的一些关键数据信息以文件的方式呈现在proc文件系统下的一些特定文件中,这样相当于将一些不可见的内核中的数据结构以可视化的方式呈现给应用层。
proc文件系统挂载在系统的/proc目录下,对于内核开发者(譬如驱动开发工程师)来说,proc文件系统给了开发者一种调试内核的方法:通过查看/proc/xxx文件来获取到内核特定数据结构的值,在添加了新功能前后进行对比,就可以判断此功能所产生的影响是否合理。
/proc目录下有很多以数字命名的文件夹,譬如100038、2299、98560,这些数字对应的其实就是一个一个的进程 PID 号,每一个进程在内核中都会存在一个编号,通过此编号来区分不同的进程,这个编号就是PID号
还有很多的虚拟文件:
- cmdline:内核启动参数;
- cpuinfo:CPU相关信息;
- iomem:IO设备的内存使用情况;
- interrupts:显示被占用的中断号和占用者相关的信息;
- ioports:IO 端口的使用情况;
- kcore:系统物理内存映像,不可读取;
- loadavg:系统平均负载;
- meminfo:物理内存和交换分区使用情况;
- modules:加载的模块列表;
- mounts:挂载的文件系统列表;
- partitions:系统识别的分区表;
- swaps:交换分区的利用情况;
- version:内核版本信息;
- uptime:系统运行时间;
proc 文件系统的使用就是去读取/proc目录下的这些文件,获取文件中记录的信息,可以直接使用cat命令读取,也可以在应用程序中调用open()打开、然后再使用read()函数读取
3.2 系统标识uname
系统调用uname()用于获取有关当前操作系统内核的名称和信息
3.3 休眠 sleep
sleep(unsigned int seconds) ,seconds:休眠时长,以秒为单位
sleep()是一个秒级别休眠函数,程序在休眠过程中,是可以被其它信号所打断的
微秒级休眠: usleep
高精度休眠: nanosleep
3.4 申请堆内存malloc 、free
在操作系统下,内存资源是由操作系统进行管理、分配的,当应用程序想要内存时(这里指的是堆内存),可以向操作系统申请内存,然后使用内存;当不再需要时,将申请的内存释放、归还给操作系统;在许多的应用程序当中,往往都会有这种需求,譬如为一些数据结构动态分配/释放内存空间
在堆上分配内存:malloc和free
Linux C 程序当中一般使用malloc()函数为程序分配一段堆内存,而使用free()函数来释放这段内存
void * malloc(size_t size)
size:需要分配的内存大小,以字节为单位
返回值为void *类型,如果申请分配内存成功,将返回一个指向该段内存的指针,void *并不
是说没有返回值或者返回空指针,而是返回的指针类型未知,所以在调用malloc()时通常需要进行强制类型转换,将void *指针类型转换成我们希望的类型;如果分配内存失败(譬如系统堆内存不足)将返回NULL,如果参数size为0,返回值也是NULL
malloc()在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的,所以通常需要程序员对malloc()分配的堆内存进行初始化操作
调用free()还是不调用free()
Linux系统中,当一个进程终止时,内核会自动关闭它没有关闭的所有文件
(该进程打开的文件,但是在进程终止时未调用close()关闭它)
同样,对于内存来说,也是如此!当进程终止时,内核会将其占用的所有内存都返还给操作系统,这包括在堆内存中由malloc()函数所分配的内存空间。基于内存的这一自动释放机制,很多应用程序通常会省略对free()函数的调用。 这在程序中分配了多块内存的情况下可能会特别有用,因为加入多次对free()的调用不但会消耗品大量的CPU时间,而且可能会使代码趋于复杂。
虽然依靠终止进程来自动释放内存对大多数程序来说是可以接受的,但最好能够在程序中显式调用free()释放内存,首先其一,显式调用free()能使程序具有更好的可读性和可维护性;其二,对于很多程序来说,申请的内存并不是在程序的生命周期中一直需要,大多数情况下,都是根据代码需求动态申请、释放的,如果申请的内存对程序来说已经不再需要了,那么就已经把它释放、归还给操作系统,如果持续占用,将会导致内存泄漏,也就是人们常说的“你的程序在吃内存”!