一、本章讲什么
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 <sys/types.h> #include <pwd.h> 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 <sys/types.h> #include <grp.h> 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.h> 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 <time.h> char *ctime(const time_t *timep); 1)功能 将time返回的总秒数,转为固定的格式时间,不过这个时间是国际时间,并不是本地时间(我们的本地时间是北京时间)。 2)参数 保存有总秒数的缓存的地址,这个总秒数,我们需要调用time来得到。 3)返回值 成功:转换后的时间字符串的指针。 失败:返回NULL,errno被设置
6.3.3 gmtime、localtime、mktime
(1)gmtime 1)函数原型 #include <time.h> 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 <time.h> struct tm *localtime(const time_t *timep); 功能与gmtime完全一样,只不过是转为本地时间的年月日时分秒,我们的本地时间是北京时间。 (3)mktime 1)函数原型 #include <time.h> time_t mktime(struct tm *tm); (a)功能 将struct tm变量中的年月日时分秒,反过来转为总秒数。 (b)返回值 计算得到的总秒数。
6.3.4 asctime、strftime
(1)asctime 1)函数原型 #include <time.h> char *asctime(const struct tm *tm); (a)函数功能 负责将struct tm中的年月日时分秒,组合为固定格式的时间。 (b)返回值 转换后时间字符换的指针。 (2)strftime 1)函数原型 #include <time.h> 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手册即可。