目录和文件
获取文件属性
获取文件属性有如下的系统调用,下面逐个来分析。
stat:通过文件路径获取属性,面对符号链接文件时获取的是所指向的目标文件的属性
从上图中可以看到stat函数接收一个文件的路径字符串(你要获取哪个文件的属性),还有一个stat类型的结构体指针的缓冲区,然后我们所需要的该文件的属性全都会保存在这个stat类型结构体的缓冲区buf中,我们要查看的话通过使用这个缓冲区即可查到。
下面来具体看一下stat结构体的定义:
我们接下来来简单的使用一下这个系统调用,这个程序用来查看某个文件的大小:
可以看见正常输出,同理stat结构体内定义了的属性都是可以直接使用的。
fstat: 通过文件描述符获取属性
lstat: 面对符号链接文件时获取的是符号链接文件的属性
文件访问权限
文件访问权限的内容全部都在stat结构体中的 st_mode这个属性中,st_mode是一个十六位的位图,用于表示文件类型、文件访问权限及特殊权限位。
在man手册中我们可以看到对于文件权限这一块,Linux给我们提供了对应的宏来实现对文件的判断:
举一个例子:
除了上述这种宏的形式外,文件权限还有一个对应的位图,但这里不展开论述(我的Ubuntu系统中手册没有对应的说明,也有可能是我没找到,但是宏定义的形式已经够用啦)。
umask
我们之前说过,如果在终端上创建文件,假如没特意设定其权限值的话,那么将会产生一个默认的权限,这就是因为有umask的存在才导致的,其求权限公式是:
其中0666是系统默认先赋予的(前提是我们没指定),然后将umask的值按位取反后,二者相与最后得到这个新文件的最终权限。
使用umask可以得到当前系统的umask值:
使用umask 后跟四个位的数字可以更改这个值,但重启终端之后又会恢复原样。
umask这种机制的存在,就是为了防止产生权限过于松散的文件。
这个命令实际上也是一个系统调用:
这就不再演示,看文档就能明白。
文件权限的更改/管理:chmod,fchmod
这两个也是系统调用,并非是我们终端上所使用的命令嗷:
也比较简单,不再赘述。
粘住位
粘住位也叫 t 位,其一开始的作用是给一个可执行的二进制的命令设置一个当前 t 位,就是把某一个命令的使用痕迹给保留下来,为了在下一次装载这个模块的时候调用比较快。
比如有的命令常用,那么就在内存当中保存它的使用痕迹,下次调用就比较快。
但是随着技术革新现在这一点不需要了,现在常用这个 t 位来对某一个目录进行设置:
可以看见tmp目录的权限的最后一位就是 t 位。
这就意味着各个用户对该目录以及该目录下的文件进行操作时就会有点特殊化了。
这里了解一下即可。
文件系统:FAT、UFS(二者都是Unix系统早期的文件系统,后者开源前者不开源)
文件系统:就是用来解决文件或者数据的存储格式和管理等问题。
关于这个老师也只是讲了概述性质的内容,感兴趣可以自己去找点资料看,这里不再赘述。
硬链接与符号链接
硬链接与符号链接的关系以及二者是什么就不赘述了,主要提一下和这两个东西相关的系统调用:
另外注意:硬链接与目录项是同义词,且建立硬链接有限制:不能给分区建立,不能给目录建立。
符号链接优点:可跨分区,可以给目录建立。
时间相关更改命令:utime
可以看出utime系统调用修改的是文件最后一一次读写的时间。
上面的第二个参数就是一个结构体参数utimbuf,其定义在上图的下方,可以看到我们通过该结构体可以对access time(最后读的时间)和modification time(最后写的时间)进行修改(比较少用啦)。
后面我们还有个专题专门来讲解时间这部分。
目录的创建和销毁
更改当前工作路径
分析目录/读取目录内容
这一节的内容我们需要使用到递归,可以用两种方式实现,一种是直接使用glob函数:
我们来写个小例子详细解释一下这个函数的使用,这个例子程序的作用是查看某个目录下面以a开头的.conf文件有多少:
还有一种是使用一大堆函数来堆叠使用达到相同的效果,opendir、closedir、readdir、rewinddir、seekdir、telldir:
那么我们依然可以写一个小例子程序来进行一个练习,这个程序用来描述/etc文件目录下共有多少文件:
目录解析实例(一)
实现du功能:
du指令的功能就是以字节为单位来显示当前路径下所有文件所占的K数,比如上面第一个例子就表示fileSystem目录就占36KB大小的数据。
为什么要用到递归呢?
如上图所示,若要展示的文件目录下还有子目录也一并会打印出来,那么肯定是要使用递归来查询所有的内容。
那么接下来我们来实现这个功能:
系统数据文件和信息
/etc/passwd
打开该文件:
这个文件详细记录了系统的用户信息、组信息以及一些其它的系统数据等等,具体可以参考APUE这本书,这里不再赘述,只介绍跟这个有关的几个函数:
getpwuid() 和 getpwnam()
这两个函数都是通过给定的参数比如用户名或者用户id返回其对应的用户信息,用户信息会放在一个passwd的结构体中:
也就是可以获取上述的信息。
例子程序:
/etc/group
一样,先打开看一下这个文件:
这个顾名思义就是和用户组相关的信息都在这个文件里面,和上面说的类似,也有两个函数:
getgrgid() 和 getgrgrnam()
和之前类似,结构体group的定义如下:
例子程序就不再赘述。
/etc/shadow
这个文件叫阴影文件,里面存放了一些口令信息,是非常重要且隐私的文件。
因为这个内容比较隐私,所以就不放图了。
但和之前的内容一样,会提到几个函数:
getspnam() 和 getspent() 以及crypt()
结构体spwd的定义如下:
crypt是加密函数,其man手册描述如下:
其中phrase是原串也就是原文,setting就是加密的方式,然后这个函数返回一个被加密了的字符串。
一个简单的加密程序:
shadow文件注意一定要使用root用户来进行操作嗷,这里我没用root权限所以执行不了,不再赘述。
时间戳
之前提到过,stat系统调用下有个关于time_t的时间类型(这好像是红帽系列的Linux是time_t类型,我用的Ubuntu,所以有点不不太一样好像):
关于这一块,我们需要掌握下面几个函数:
time()、gmtime()、localtime()、mktime()、strftime()。
time()
其作用是以秒为单位获取时间,其用法如下:
这个stamp可以作参数也可以直接作返回值,因为效果是一样的,从man手册中可以看出来。
gmtime() 和 localtime()
结构体tm的定义如下:
gmtime很明显就是将一个time_t类型的时间戳转换成tm类型的结构体,同理localtime也是一样不再赘述。
mktime()
这个函数实际上是上面两个函数的逆向过程:
将结构体tm又逆向回一个time_t类型。
strftime()
这个函数的作用是格式化时间和日期,其作用是将一个tm结构体(第四个参数)当中提出来我所需要的结构体字段(第三个参数),然后把这个字段放到由第一个参数和第二个参数所构成的缓冲区中(s是字符串,max就是最大字节数)。
第三个参数格式在man手册中有对应的操作,下图中的%a等就是第三个参数所需要的格式字符串:
简单的使用:
时间专题实例
程序一,实现类似下面内容的输出,每一秒输出一次带行号:
程序如下:
再来一个示例程序,该程序用来求从现在开始一百天后是哪一天:
进程环境
注意目前还是讲的单进程内容。
main函数
main函数本身就是一个进程或者说线程,在后面会再提及。
进程的终止(正常终止和异常终止)
正常终止:
从main函数返回,如return 0;
调用exit;
调用_exit或者_Exit;
最后一个线程从其启动例程返回;
最后一个线程调用pthread_exit;
return 0和 exit(0)有什么区别?
返回 return 0 表示是给当前这个进程的父进程看的,这是一个约定俗成的写法,没有什么特别的说法,而exit则是一个函数(钩子函数):
可以看见exit函数可以引发进程的正常终止,与之相关的还有一个函数是atexit()钩子函数(onexit函数与其类似,但atexit用的比较多):
当进程正常终止时这个atexit()函数会被调用。
我们写一个小例子来感受一下:
而_exit和_Exit则是系统调用,也就是说上面的exit函数是依赖于这二者的:
那么与exit有什么区别呢?
exit函数被调用时会直接将整个程序的执行步骤全部执行,比如钩子函数该调的调用,缓冲区该刷新的刷新,那么此时如果是程序出了问题的情况我们还是调用exit函数退出的话,就很可能会将本来的小错误给扩大化(因为影响了后面钩子函数啊、缓冲区中的内容,造成了一步错步步错的效果),所以此时肯定不能使用exit,为了阻止错误进一步扩散,我们可以使用_exit或者_Exit,这两个函数就不会去执行后面的步骤而是直接退出,或者使用abort信号也行。
剩下部分的解析都涉及信号的内容,这些会在后面说到,这里不再赘述。
异常终止:
调用abort;
接到一个信号并终止;
最后一个线程对其取消请求做出响应;
异常终止和正常终止的情况非常重要,必须要熟记。
命令行参数的分析
这一块内容涉及两个函数:getopt() 和 getopt_long(),一个接收命令行短格式一个接收长格式。
接下里我们使用 getopt() 函数来实现一个小程序,可以根据命令行输入 -m 表示获得现在是几月, -h 表示现在是几点, -y表示现在是几年等等:
运行结果:
环境变量
main函数在最开始的时候其实有三个参数,其中第三个参数就是环境变量。
环境变量的本质其实就是:KEY = VALUE;
查看环境变量,使用export命令:
可以看到我们使用的很多命令在环境变量里面都是有的,这是我们能够在shell窗口下很便捷的使用各种命令的原因(因为如果不是这样的话我们就得写很长的命令串)。
比如 ls 命令就在PATH环境变量里,使用ls命令时shell就会去PATH里面寻找 ls 命令的二进制可执行文件并执行。
PATH环境变量所保存的二进制可执行文件就叫Shell 的外部命令处理,也就是说这类文件是存储在磁盘上的,我们能找到它并对它进行调用。内部命令处理就是OS自带的,比如进程调度啊、管道啊之类的。
在这块会用到一个值叫 environ ,它相当于一个全局变量,被用来保存所有环境变量的内容。
它存放的形式非常像main函数中的 argv[] 参数数组,是一个二维数组,其中每一行是一个字符串,我们写个小例子来看一下:
关于环境变量我们需要知道的几个函数:getenv()、setenv()、putenv();
C程序的存储空间布局
可以使用 pmap 命令来查看一个进程在当前系统下的内存分布情况:
当我们将位于各区内的代码都写在一个c文件中并运行时,通过 pmap 指令就能知道其各个变量会被存放在哪个区了,这是一个很好的学习方法。
使用 ps axf 可以以一个树状结构来查看当前系统中的所有进程以及进程父子关系:
函数跳转
这里不是说通常意义上的调用函数之后就跳转到另一个函数了,这里强调的是在不改变现有数据结构的情况下实现函数的跳转。
比如递归这种操作,递归的本质就是压栈,那么若此时一个函数位于栈顶想调用栈底的函数怎么办呢?goto语句也只能实现在函数内部的跳转,我们需要类似于goto这样的机制来实现在各个函数间进行跳转,所以又有几个函数需要学习:
setjmp() 和 longjmp();
setjmp函数是用来设置跳转点的,longjmp函数是用来进行从某个位置跳回到某个跳转点的。
这两个函数可以进行安全的跨函数跳转。
来试一下:
可以看见现在是正常的函数之间的调用。
现在加入跳转函数,先来看一下跳转函数的man手册定义:
如果是在设置跳转点,那么返回值就是0,如果是从别处跳回来,那么返回值就为非0。
接下来我们将在上面的程序中,让函数 d 跳走,然后在函数 a 当中设置一个跳转点:
可以看见直接跳转到了函数 a,然后程序直接执行结束了就。
资源的获取及控制
查看系统资源的命令还记得吗?
ulimit -a:
这里依然是提供两个函数,一个是getrlimit()用来获取资源总量,另外一个是setrlimit()用来设置资源总量。
可以从上图看到有个硬限制和软限制,软限制表示其只能在硬限制范围内活动,高不能高过硬限制;普通用户对自己某种资源的硬限制只能降低不能抬高,而对于root用户对于自己所用资源的软限制可以升高可以降低但高也不能高过自己的硬限制,对于自己的硬限制则可以升高可以降低。