一、本章讲什么
1.1 获取系统文件的数据
(1)什么是系统文件
就是Linux系统自己会用到的文件,分为两类。
1)文本文件
(a)里面的内容都是文字编码,vi打开后,我们能够识别的数据。
(b)放的都是Linux系统要用到各种配置信息
Linux系统在启动和运行时,会用到里面的数据。
我们自己写的程序,有的时候也需要用到里数据,但是我们基本只是读数据,大多数
情况只有Linux系统才会去改里面的数据,本章会介绍我们自己的程序,如何来调用API
来获取里面的数据。
(c)比如后面要讲的/etc/passwd文件
里面放的是用户的账户信息。
用户登录系统时,输入用户名和密码后,Linux会使用你输入的用户名和密码,然后到这个
文件中匹配注册的用户名和密码,只有匹配成功后才能登录,否者你是登录不了的。
(d)文本形式的系统文件,大多放在了/etc这个目录下
本站要介绍的系统文件,都是/etc/下的文件。
2)纯二进制文件
(a)比如各种编译好的库、以及可执行文件,里面放是能够被cpu执行的机器指令。
· 库的话,都放在了各种名叫lib的目录下,比如/lib,lib就是库的意思。
其实有好多lib目录,比如/lib、/usr/lib等,有关它们的区别,本门课
暂不介绍,后面讲《Linux基础高级》时会详说。
· 各种可执行文件的话
比如ls、mkdir等这些命令(可执行程序),都放在了各种名叫bin的目录下,
比如/bin,bin就是binary二进制的意思。
bin目录也有好些,比如/bin,/usr/bin,同样的,有关它们的区别,本门课
暂不介绍,后面讲《Linux基础高级》时会详说。
(b)二进制文件,我们vi后是看不懂的
因为里面放的不是文字编码,所以文本编辑器无法正确翻译为文字图形,所以我们无法看懂。
3)系统文件的特点
(a)系统文件的所属用户都是root,所属组也基本都是root。
演示:
(b)普通用户操作系统文件时,只能以其它用户的身份去操作,而其它用户的权限往往只有r,
所以普通不能写系统文件,只能读里面的数据,只要你不能写,就不会对Linux系统构成威胁。
有些非常重要的系统文件,甚至都不允许普通用户读,比如后面要介绍的/etc/shadow
文件。
(c)对于普通用户来说,一般情况下,只有读系统文件的需要
如果你要修改里面的内容的话,必须要使用sudo,临时获取root身份,才能拥有
root(管理员用户)才有写权限,只有这样才能修改系统文件。
(d)用户自己的程序,需要获取系统文件数据时怎么办
可以自己调用open、read等文件io函数操作这些文件,同样的一般只能读,不能写,
如果你要写,必须以root身份运行程序,然后你才能修改文件,不过一般情况下我们
只有读取数据的需求。
为了方便操作,系统提供了专门的函数,调用这些函数可以很方便的操作文件中的数据,
比我们自己调用open、read更方便,这些函数其实也是靠封装open、read等文件io函数来
实现的。
(2)本章会讲些什么系统文件
1)其实Linux的系统文件有很多,比如
(a)/etc/passwd:存放用户账户信息的文件
(b)/ext/shadow:存放密码,用户密码其实单独存放的
(c)/etc/group:组信息
(d)/etc/setvices:各种网络服务器的信息
(e)/etc/protocols:各种协议的协议号
(f)/etc/networks:网络配置信息
....
本章重点只介绍a b c这三个系统文件,其它的后面涉及到了,在具体介绍。
2)为什么介绍/etc/passwd、/ext/shadow、/etc/group这三个系统文件
(a)很有要了解
每次登陆系统时,都需要输入用户名和密码,因此我们有必要了解下Linux是如何管理
账户信息的。
实际上其它的软件,比如人事管理系统、银行管理系统、其它OS,在管理用户的账户、
密码时,都采用了类似的管理机制,仅站在知识面扩展的角度来说,很有必要了解下。
(b)完善我们第二章的my_ls程序
my_ls在显示文件属性时,文件的属主还是ID形式。
演示:
我们需要将ID换为名字,这就必须涉及到/etc/passed、/ext/shadow、/etc/group这三个文件。
1.2 获取系统时间
(1)什么是获取系统时间
说白了就是获取:年 月 日 时 分 秒。
(2)获取时间的API
为了方便应用程序获取时间,我们可以调用相应的API。
比如我的运行于Linux系统的C程序,需要用到系统时间时,就可以调用这些API来获取时间,
这些API有:
1) time :Linux的系统API
2) gmtime、localtime、mktime、ctime、asctime、strftime :c库API
库API需要系统API time的支持,后面会介绍到。
其实所有语言的库,都有获取时间的库API,不过这些库API,同样都是基于系统API实现的。
gmtime、localtime
mktime、ctime、 ...... ......
asctime、strftime
C库时间API C++库时间API Java库时间API ...........
| | |
| | |
|____________|____________| ................
|
|
time(OS API)
|
|
Linux OS
二、口令文件:/etc/passwd
2.1 什么是口令文件?
存放用户账户信息的文件,就是口令文件。
2.2 ls 查看下 /etc/passwd
演示
-rw-r--r-- 1 root root 2270 Apr 5 03:31 /etc/passwd
对于这个文件,只有文件所属用户root才有写权限,组员用户以及其它用户,只有读权限。
所以当普通用户打开这个文件时,是以其它用户的身份来打开文件的,所以对应的权限只允许r,
不允许写。
当然这个文件是没有x权限的,因为文本文件放的是文字编码,不是机器指令,不需要被cpu执行。
对于普通用户而言,不需要向这个文件写入任何数据,顶多就是读取里面的数据。
不过当我们调用某些命令的时候,这些个命令会去修改该文件。比如调用useradd添加新的用户,
系统执行这个命令时,系统会把新用户的账户信息,写到这个文件中保存。
文件内容:
root:x:0:0:root:/root:/bin/bash 管理员用户的账户信息
_
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin |
bin:x:2:2:bin:/bin:/usr/sbin/nologin |
sys:x:3:3:sys:/dev:/usr/sbin/nologin |
........ |
........ >Linux系统相关用户的账户信息
usbmux:x:120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false |
........ |
........ _|
zxf:x:1000:1000:zxf,,,:/home/zxf:/bin/bash 我自己这个普通用户的账户信息,这个普通用户是安装ubuntu时创建的
newUser:x:1001:1001::/home/newUser: 我使用useradd命令创建的另外一个普通用户的账户信息
2.2.1 账户所包含的信息:
以:zxf:x:1000:1000:zxf,,,:/home/zxf:/bin/bash 为例
分为7个字段,字段间使用:分隔
用户名:密码:用户ID:用户所在组的组ID:注释:用户主目录的路径:shell程序的路径
(1)用户名
比如root、zxf、newUser
(2)密码
为了安全起见,真实的密码并不放在这里,而是放在了/etc/shadow中,这里只是使用
一个X来代表。
X表示有密码,如果没有X(字段是空的),表示这个用户没有密码。
(3)用户ID
root的用户ID为0,在Linux下,root管理员用户的ID都是0
zxf的用户ID为1000
newUser用户ID为1001
用户ID都是由系统自动分配的。
(4)组ID
默认情况下,每个用户都有一个自己的组,组里面就自己一个组员,组长就是自己,
自己的用户ID就是组ID。新建用户后,每个用户所在组就是自己这个组,所以你才会
发现对于绝大多数用户来说,它的组ID也是自己的ID。其实,执行相应的命令,可以
将我的用户加入其它用户的组,也可以其它用户也可以加入我的组,成为我的组员。
由于有关组这个东西,在我们实际开发的过程中,我们基本用不到,因此我们这里就
不讲如何通过命令来修改用户的所在组。
(5)注释
账户注册者的个人信息,如果信息很多的话,信息之间使用,分隔。
注册者的信息有哪些呢?
比如注册者的名字、电话、办公地址、邮箱等等。
一般的人嫌麻烦,在注册账户的时,都不会填写这些内容,所以注释字段基本都是空的
,比如我自己新注册的newUser账户,就没有注释信息。
newUser:x:1001:1001::/home/newUser:
(6)用户主目录的路径
系统启动起来后,用户登录系统时,会用到主目录,所以这里有记录主目录的路径,
用户登陆后,系统便会从这里得到该用户的主目录路径。
千万不要去修改主目录的路径,修改之后很可能会导致你下一次无法登录,如
果你好奇心重,就想改改看,那你一定要先做好ubuntu的备份。
不仅主目录路径不能改,其它信息你也不能改。
(7)shell程序的路径
什么是shell程序?
shell程序,是一个命令的解释程序,说白了就是解析我们从终端输入的各种命令的。
在/bin下还有一个shell程序叫dash(/bin/dash),但是/etc/passwd文件中
给的路径是/bin/bash,那么登陆后,启动的就是bash,而不是dash。
因为dash和bash都是二进制的可执行程序,因此都放在了bin目录下。
2.2.2 getpwuid、getpwnam
这两个函数的作用是,获取passwd文件中的账户数据,其实,我们也可以调用open、read等文件io
函数来读取passwd文件的数据,但是Linux系统提供了更加便捷的API,通过这些API,可以更加方便
的读取,比我们自己调用open、read来的更便捷。
getpwuid、getpwnam这个两个函数是c库函数,这两个函数也是靠封装open、read等函数来实现的。
(1)函数原型
#include
#include
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
1)功能
getpwuid:使用用户ID(uid),到/etc/passwd中搜索,并获取对应账户信息。
getpwnam:使用用户名(name),到/etc/passwd中搜索,并获取对应账户信息。
调用这两个函数时,函数会开辟一个struct passwd结构体变量,然后把从文件中读到的
账户数据,保存到结构体变量。
zxf:x:1000:1000:zxf,,,:/home/zxf:/bin/bash
struct passwd
{
char *pw_name; /* 用户名,字符串形式 */
char *pw_passwd; /* 是否有密码 */
uid_t pw_uid; /* user ID ,用户ID*/
gid_t pw_gid; /* group ID ,组ID*/
char *pw_gecos; /* 注释 */
char *pw_dir; /* 主目录路径 */
char *pw_shell; /* shell程序路径 */
};
2)返回值
(a)成功:返回struct passwd结构体变量的指针。
(b)失败:返回NULL,errno被设置。
三、阴影文件:/etc/shadow
1.2.1 里面放的是什么
放的是加密后的密码。
1.2.2 为什么密码要单独存放,而且还要加密
为了让密码更安全。
(1)密码不能明文存放
注册用户时,用户密码会被加密,而且使用的是不可逆加密算法,所谓不可逆算法,就是不能
通过密文,反过来推算出原文。
常用的不可逆算法是“摘要加密算法”,我们在讲《计算机体系结构》软件篇——计算机信息安
全时介绍过,不了解的可以看这部分内容。
为什么加密?
如果明文存放,别人把你的密码文件偷到了,一打开,不就直接知道你的密码了吗。
当我们登录时输入密码原文,密码加密后会被拿去和注册时登记的加密密码进行比对,如果
相等就能登录成功。
(2)密码单独放在一个文件中(/etc/shadow),而且普通用户无法查看
防止你猜密码。
比如我知道你的加密算法是什么,只不过我不知道你的密码原文,怎么办呢?
我就根据你的生日、街道等各种的组合猜你的密码,我再对这些猜的密码进行同样算法的加密
,然后把加密后的密码和你注册的加密密码进行比对,如果无限制组合猜下去的话,肯定能找到
你的密码。
所以说最安全的方式就是,不仅要对密码加密,而且还不能让你看到我的加密后的密码。
而/etc/passwd这个文件是可以被任何用户查看的,确实也需要被普通用户查看,所以密码
肯定不能保存在这里面,只能把密码单独的保存在另一个文件/etc/shadow中,而且普通用户
还无法查看这个文件。
当然如果passwd中,该账户的密码字段为空,而不是x,表示没有密码,那么在
/etc/shadow中,也就没有该用户的密码了。
为什么普通用户不能查看该文件?
/etc/shadow文件的文件权限是:
-rw-r----- 1 root shadow 1467 Apr 7 18:47 /etc/shadow
普通用户打开该文件时,只能以其它用对应的权限打开,但是---就没有给你任何权限。
演示:普通打开
不过好在,在安装ubuntu时,我的普通用户zxf和root做了关联,我可以使用sudo
临时的变身为root用户,而root操作该文件时,对应的权限允许r,而且还允许w。
当然还有一个办法,可以把我的zxf用户,加入shadow,以shadow组中组员身份操作
shadow时,可以有r权限。
(3)文件中的内容
root:$6$5liwWndK$.o3Ixdv18/vCJhjEh10ypmexBrkL2ZMji3hzjmGAZ/W6GkHMR
rdwMHAwLRhC3Mb9ydQCRkkALObRknCYIYo0Q1:17615:0:99999:7:::
........
zxf:$6$qmxD4ykF$Sa6Rag5jyietGlL/gM7Er0rosAeVrVIst0p3sX.y9Hi0Mij
pITvl6NkKk.n76uo3RUKP9eso7Pv2URNOSslBH/:17615:0:99999:7:::
newUser:$6$AuLu6Skf$QtD4niZmcGXc4nK9Vck8iPM2X3MoE3NBkkemJc
FaKA4ZX7cGp/M/9av6vbPz7YMeSnjcYvCTSuuobn/ijTdD41:17629:0:99999:7:::
1)分成了8个字段,相互间被“:”隔开。
(a)字段1:用户名
(b)字段2:加密后的密码
(c)其它字段
上次修改密码时的日期
多少时间后,可以再次修改密码
账户有效期
距离到期还有多久
等等
2)$6$qmxD4ykF$Sa6Rag5jyietGlL/gM7Er0rosAeVrVIst0p3sX.y9Hi0MijpITvl6NkKk
.n76uo3RUKP9eso7Pv2URNOSslBH/
$6$:加密盐巴。
对密码原文加密时,加密算法会把“盐巴”加进去,最终生成加密后的密码。
密码原文是加工原料,加了“盐”就得到了菜(加密后的密码)。
(4)Linux也提供了的相应的API,用于获取/etc/shadow中密码信息
比如struct spwd *getspnam(const char *name);
根据用户名获取文件中该用户的密码信息,这个函数与的工作原理getpwuid、getpwnam
函数是一样的。
不过对于一般的开发来说,根本用涉及不到,所以我们这里就不介绍了,
四、组文件:/etc/group
4.1.1 放的是什么
我们之前说过,多个用户在一起可以组成一组,其中的某个用户担任组长,组长用户ID就是组长ID,组长用
户的名字就是整个组的名字。
/etc/group里面放的就是各种用户组相关的信息。
这个文件,普通用户也只能读,不能写。
4.1.2 文件内容
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
.........
zxf:x:1000:
newUser:x:1001:
(1)分成4个字段
1)字段1:组的名字,就是组长用户的用户名
2)字段2:组的密码,就是组长用户的密码,x表示有密码,字段为空的话,表示没有密码
3)字段3:组ID,就是组长用户的ID
4)字段4:组员有哪些
如果字段为空,表示组员就一个,就组长自己。
我们前面介绍过,默认情况下,每个用户自己一个组,自己担任组长,在没有别的用户加入组之前
,组员就自己一个人,自己既是将军也是兵。
我们刚创建一个新文件时,你在什么用户下创建的文件,这个文件的所属用户默认就是当前用户,
所属组就是当前用户自己的那一个组。
当然我们可以使用chown来修改文件的所属组。
4.1.3 getgrgid、getgrnam函数
这两个函数同样是库函数,工作原理和getpwduid、getpwnam完全一样。
(1)函数原型
#include
#include
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
(2)函数功能
1)getgrnam函数:利用组名搜索组文件,获取对应组的信息。
2)getgrgid函数:利用组ID搜索组文件,获取对应组的信息。
将获取的内容写到函数开辟的struct group结构体变量中,然后将指针返回给应用程序使用。
zxf:x:1000:
struct group
{
char *gr_name; /* 组名 */
char *gr_passwd; /* 是否有组密码 */
gid_t gr_gid; /* 组ID */
char **gr_mem; /* 指向组成员用户名的的指针数组 */
};
(3)函数返回值
调用成功,则返回指向struct group结构体变量的指针,失败则返回NULL,errno被设置。
五、其它系统文件
比如:
/etc/services:记录了各种网络服务器提供的服务。
/etc/protocols:记录了各种的协议。
/etc/networks:记录网络信息
同样也有类似get***的函数,通过这样的函数,可以获取对应系统文件的信息,后面涉及到了,再来有
针对性的介绍。
有关调用函数获取系统文件信息,在实际开发中用的不多,这里介绍更多是扩展性的,希望你知道有
这么回事,对于大家来说理解即可。
六、获取系统时间
什么是系统时间:就是年月日 时分秒,有OS时,这个时间就是系统提供的,因此成为系统时间。
比如我的应用程序需要显示当前日期和时间,我就可以通过调用相应的API来获取。
区分日期和时间。
日期:年 月 日
时间:时 分 秒
准确来讲,具体的时 分 秒应该指的是时刻,两个时刻之间的差值才是时间,不过平时说习惯了,往往
把时刻也说成是时间,甚至将日期和时间都合称为时间,后面我们说到时间时,指的就是日期和时间合在
一起的情况。
6.1 Linux的计时方式
Linux系统记录的时间,是从公元1970年1月1日00:00:00开始,到现在的总秒数,每过1秒,总秒数就会+1。
这个总秒数 + 1970年1月1日00:00:00这个起点时间,即可计算得到当的前时间。
通过调用Linux系统提供系统 API——time,即可获取这个总秒数,我们可以自己去将这个总秒数转换
成年月日、时分秒,但是由于涉及到闰年闰月的问题,我们一般不会自己去转换,因为很多人连什么是
润年闰月都不清楚,自己写代码去转换不划算。
因此Linux下的C库还提供了gmtime、localtime、mktime、ctime、asctime、strftime等C库函
数,调用这些函数就可以将time函数返回的总秒数,自动的转换为我们要的当前时间。
前面说过,所有语言的库都有获取时间的库API,这些库函数都是基于系统API来实现的,如果是
windows的库,就是基于windows系统API实现的,如果是Linux的库,就是Linux的系统API实现的。
gmtime、localtime
mktime、ctime、
asctime、strftime
C库时间API C++库时间API Java库时间API ...........
| | |
| | |
|____________|____________| ................
|
|
time(OS API)
|
|
Linux OS
同样不要记这些函数,理解即可,通过理解C库的时间API,了解库函数的时间API,才是重点。
以后,如果在你写的c程序中用到了这些函数,查阅笔记和man手册即可。
如果你写的是c++和java函数的话,那就直接调用c++和java的获取时间的库函数即可。
6.2 time
函数原型
#include
time_t time(time_t *t);
1)功能:返回总秒数。
3)参数 t:存放总秒数的缓存的地址。
time_t其实就是int类型,只不过被typedef重命名了。
调用time时,我们需要定一个int变量(缓存)来存放总秒数。
疑问:使用int的变量放总秒数,空间大小够吗?
答:够,如果说你的总秒数,大到int型变量都放不了的话,起码需要好几百年的时间,才能累计如此
之多的总秒数,在我们有生之年你是看不到总秒数把int变量撑爆的情况,你要是能看到,估计都成大仙了。
2)返回值:函数调用成功返回总秒数,失败则返回(time_t)-1 ,errno被设置。
讲到这里可以看出,获取总秒数的方式有两种。
(a)通过返回值获取
time_t tim = time(NULL);
不使用参数时,参数指定为NULL。
(b)参数
time_t tim;
time(&tim);
6.3 gmtime、localtime、mktime、ctime、asctime、strftime
6.3.1 系统API:time和库API:gmtime、localtime、mktime、ctime、asctime、strftime的调用关系
6.3.2 ctime
(1)函数原型
#include
char *ctime(const time_t *timep);
1)功能
将time返回的总秒数,转为固定的格式时间,不过这个时间是国际时间,并不是本地时间(我们的本地时间是北京时间)。
2)参数
保存有总秒数的缓存的地址,这个总秒数,我们需要调用time来得到。
3)返回值
成功:转换后的时间字符串的指针。
失败:返回NULL,errno被设置
6.3.3 gmtime、localtime、mktime
(1)gmtime
1)函数原型
#include
struct tm *gmtime(const time_t *timep);
(a)功能
将time返回的总秒数,转为国际时间的年 月 日 时 分 秒。
然后开辟一个struct tm结构体变量,将年月日时分秒放到struct tm结构体变量中。
struct tm
{
int tm_sec; /* 秒 */
int tm_min; /* 分 */
int tm_hour; /* 时 */
int tm_mday; /* 月天 */
int tm_mon; /* 月份 */
int tm_year; /* 年 */
int tm_wday; /* 周天 */
int tm_yday; /* 年天 */
int tm_isdst; /* 夏时令设置 */
};
(b)参数
存放有总秒数的缓存的地址。
(c)返回值
成功:返回struct tm结构体变量的地址,应用程序就可以使用里面存放的年 月 日 时 分 秒。
失败:返回NULL,errno被设置。
(2)localtime
1)函数原型
#include
struct tm *localtime(const time_t *timep);
功能与gmtime完全一样,只不过是转为本地时间的年月日时分秒,我们的本地时间是北京时间。
(3)mktime
1)函数原型
#include
time_t mktime(struct tm *tm);
(a)功能
将struct tm变量中的年月日时分秒,反过来转为总秒数。
(b)返回值
计算得到的总秒数。
6.3.4 asctime、strftime
(1)asctime
1)函数原型
#include
char *asctime(const struct tm *tm);
(a)函数功能
负责将struct tm中的年月日时分秒,组合为固定格式的时间。
(b)返回值
转换后时间字符换的指针。
(2)strftime
1)函数原型
#include
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
(a)功能
与asctime功能一样,只不过strftime能够组合为我们自己指定的时间格式。
为了组合为我们自定义的时间格式,我们需要为函数其指定格式。
(b)返回值
返回转换后时间字符串的字符个数。
(c)参数
· s:缓存地址,这个缓存用于存放转换后的字符串。
· max:缓存的大小
· tm:放有年月日时分秒的结构体变量的地址。
· format:自定义时间格式
与printf("%d %s", a, buf);指定打印格式的操作方式是一样的。
格式怎么用?
比如:
strftime(strtim_buf, sizeof(strtim_buf), "%Y:%m:%d %H:%M:%S\n", tm);
%Y:%m:%d %H:%M:%S指定的时间格式,是中国人惯用的格式 ———— 年:月:日 时:分:秒。
格式表如下:
%a:缩写周日名 TUE
%A:全周日名 Tuesday
%b:缩写月名 Jan
%B:月全名 January
%c:固定格式的日期和时间 Tue Jan 14 19:40:30 1992
%d:月日 26
%H:24小时制 23
%I:小时(上下午12小时制) 11
%j:年日 089
%m:月份 08
%M:分 55
%p:AM/PM(上下午指示) PM
%s:秒 30
%w:(周天到周6用0~6 表示) 0
%x:固定格式日期 01/14/92
%X:固定格式时间 19:40:30
%y:不带公园的年 18
%Y:带公元的年 2018
%z:时区名 MST、DST、WET、......
有关这个表,不要记,你也记不住,理解了,用到时查man手册即可。