《鸟哥的Linux私房菜:基础学习篇(第四版)》
- 第5章 Linux的文件权限与目录配置
- 第6章 LInux文件与目录管理(正在进行)
《CSAPP》
- 第10章 系统级IO 10.1~10.5(正在进行)
文件
文件名
文件或目录名的最大长度:255字节
文件名需要避免特殊字符(保留字符):? > < ; & ! [ ] | \\ \' "
( ) { } 以
.`开头作为隐藏文件
文件扩展名
仅仅为了了解该文件可能的用途而已
- *.sh : 脚本或批处理文件 (scripts)
- Z, .tar, .tar.gz, .zip, *.tgz: 经过打包的压缩文件
文件种类
- 正规文件
-
:内容是数据
- 文本文件(只含有ASCII或Unicode字符的普通文件、包含若干文本行以\\n
结束)
- 数据格式文件(特定格式的文件)
- 二进制文件(其他普通文件) - 目录
d
:内容是文件名列表,包含一组连接(link)的文件(文件名.
该目录自身的链接、..
该目录的父目录的链接、文件的链接、其他目录的链接),/
代表根目录、~
代表当前用户目录 - 链接文件
l
:类似Windows系统的快捷方式 - 设备与设备文件(device):与系统周边及储存等相关的一些文件,集中在
/dev
目录下
- 区块(block)设备文件b
:可随机存取设备(软盘、磁盘等)
- 字符(character)设备文件c
:一次性读取设备(鼠标、键盘等) - 数据接口文件(sockets)
s
:套接字,与另一个进程进行跨网络通信的文件,常在/run
或/tmp
目录下 - 数据输送档(FIFO,pipe)
p
:解决多个 程序同时存取一个文件所造成的错误问题
多用户
/etc/passwd用户账号列表
/etc/group群组列表
ACL-Access Control Lists
文件权限
针对多用户操作系统,保护文件或目录的内容的安全
- 文件的3种可存取的身份:owner(文件拥有者)、group(群组)、others(其他人)
- 每种身份的3种权限
bit位 | 数字 | 字母 | 来源 | 对文件的控制 | 对目录(抽屉)的控制 |
---|---|---|---|---|---|
2 | 4 |
r |
Read | 读取文件的内容 | 列出目录的内容(抽屉的灯光) |
1 | 2 |
w |
Write | 新增、编辑文件内容 | 创建、删除、重命名、移动文件或目录 |
0 | 1 |
x |
eXecute | 判断文件是否可执行 类似于windows的扩展名 .exe 、.bat 、.com |
能否cd 进入该目录成为工作目录(抽屉的钥匙) |
任意 | 0 |
- |
- | 无权限 | 无权限 |
系统文件
---------
(000)只有root用户才能读写或执行
Linux命令
文件操作命令 | 来源 | 常用参数 | 作用 |
---|---|---|---|
ls | List directory contentS | ls -al |
列出目录内容-a 是列出包括. 开头的文件-l 是列出所有的文件详细的权限和属性1.显示文件类型和权限(十个字符)、 2.链接:有多少文件名连接到一个 i-node 节点、3.拥有者、 4.群组、 5.文件大小(字节)、 6.创建或最近修改日期: □ 当年格式: 月 日 时:分 、□ 非当年格式: 月 日 年 、7.文件名 |
chgrp | CHange GRouP | chgrp [-R] 群组 文件 |
改变文件所属群组-R 递归修改 |
chown | CHange file OWNer and group | chown [-R] 账号[:群组] 文件 |
改变文件拥有者和群组 |
chmod | CHange file MODes or ACL | chmod [-R] 权限 文件 |
改变文件的权限 □ 数字类型:三个数字代表 [0-7]{3} □ 符号类型: [u|g|o|a][+|-|=][r|w|x|-]{1,3} u 、g 、o 代表三种身份、a 代表所有人= 设置、+ 提权、- 降权 |
touch | change file access and modification times | touch 文件 |
创建空文件 |
cat | concatenate and print files | cat 文件 |
将一个文件内容读出来 |
目录操作命令 | 来源 | 常用参数 | 作用 |
---|---|---|---|
cd | Change Directories | cd 目录 |
改变当前工作目录 登录后默认切换到自己帐号的主文件夹 |
pwd | Print Working Directory | pwd [-P] |
显示当前工作目录-P 显示出确实的路径,而非使用链接 link路径 |
mkdir | MaKe DIRectories | mkdir [-m 权限] [-p] 目录 |
创建空目录-p 递归创建父路径-m 直接设置权限,不使用默认权限 |
rmdir | ReMove DIRectories | rmdir 目录 |
删除一个空的目录-p 递归删除父路径 |
rm | unlink--ReMove directory entries | rm [-rf] 目录或文件 |
删除文件或一个目录-r 递归清空-f 不需确认 |
Linux目录规范
FHS,Filesystem Hierarchy Standard
示例 | 不可分享给其他主机 | 可分享给其他主机 |
---|---|---|
不变的(static) | /etc 配置文件 |
/usr 软件安装 |
可变动的(variable) | /var/run 程序相关 |
/var/mail 使用者邮件信箱 |
/
:根目录,与开机系统有关(保持根目录越小越好)
根目录 | 作用 |
---|---|
/boot |
开机配置文件、核心文件、开机菜单等等 |
/bin | LInux的root用户常用命令 单人维护模式能够执行的命令 |
/sbin | LInux的开机、修复、还原系统所需要的指令 |
/lib | 开机时会用到的函数库, 以及在/bin 或/sbin 下面的指令会调用的函数库而已 |
/lib64 |
支持 64 位的函数库等 |
/run |
开机运行产生的各项信息 |
/dev | 所有系统设备文件 |
/media |
可移除的设备:软盘、光盘、DVD |
/mnt |
要临时挂载某些额外的设备 |
/proc |
虚拟文件系统:在内存当中,系统核心、进程信息、周边设备状态及网络状态等 |
/sys |
虚拟文件系统:记录 核心与系统硬件信息较相关的信息 |
/home |
系统默认的使用者主文件夹 |
/root |
系统管理员(root)的主文件夹,单人维 护模式可以挂载该目录 |
/etc | 配置文件 |
/tmp |
暂时放置文件的地方,建议开机时清空 |
/opt |
第三方协力软件 |
/srv |
“service”的缩写,一些网络服务启动之后的数据目录 |
/lost+found |
使用标准的ext2/ext3/ext4文件系统,(xfs取消) 当文件系统发生错误时将一些遗失的片段放置到这个目录下 |
/usr
:Unix Software Resource,安装时会占用较大硬盘容量的目录、系统预装软件、建议即使挂载成为只读,系统还是可以正常运行
- 类似Windows 系统的“C:\\Windows\\ (当中的一部份) + C:\\Program files\\”
- CentOS 7.x 版本将
/sbin
,/bin
,/lib
移动到/usr
目录下,在救援模式挂载/usr
软件目录 | 作用 |
---|---|
/usr/bin/ |
一般用户能够使用指令集合、/bin 链接到此要求在此目录 下不应该有子目录 |
/usr/sbin/ |
系统管理员常用指令集/sbin 链接到此 |
/usr/lib/ |
/lib 链接到此 |
/usr/lib64/ |
/lib64 链接到此 |
/usr/local/ |
系统管理员在本机自行安装自己下载的软件 |
/usr/share/ |
共享文件 |
/usr/include/ |
c/c++ 等程序语言的文件开始(header)与包含档(include)放置处 |
/usr/libexec/ |
某些不被一般使用者惯用的可执行文件或脚本(script)等 |
/usr/src/ |
一般源代码建议放置到这里 |
/var
:VARiable,在系统运行后才会渐渐占用硬盘
目录 | 作用 |
---|---|
/var/cache/ |
程序运行中会产生的暂存盘 |
/var/lib/ |
程序运行中,需要使用到的数据文件放置的目录 |
/var/lock/ |
某些设备或者是文件资源一次只能被一个应用程序所使用 |
/var/log/ |
系统登录文件 |
/var/mail/ |
放置个人电子邮件信箱的目录 链接到/var/spool/mail/ |
/var/run/ |
某些程序或者是服务启动后,会将他们的PID放置在这个目录下 链接到 /run |
/var/spool/ |
目录通常放置一些排队等待其他程序使用的数据,被使用后通常都会被删除 |
Unix I/O函数
头文件 | 作用 | 内容 |
---|---|---|
sys/types.h |
操作系统的数据类型 | clock_t: dev_t:设备类型 mode_t:文件访问权限位类型 pid_t:进程ID size_t:(ulong)设置占据内存的字节数 ssize_t:(long)返回字节长度结果 off_t:(long)偏移量 |
sys/stat.h |
操作系统的文件元信息 | 文件访问权限位宏 struct stat |
fcntl.h |
控制操作描述符 | 函数:open、 |
unistd.h |
Linux/Unix系统调用函数 | 函数:close、lseek、read、write |
系统调用
- system call 控制权从程序到系统,切换上下文,开销比较大(2~4万个时钟周期)
- 每次系统调用时都应该检查返回值
访问文件方式
宏名 | 如何访问文件 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 可读写 |
宏名 | 打开文件后怎么处理、文件不存在怎么处理 |
---|---|
O_CREAT | 若存在则覆盖(不存在就创建) |
O_TRUNC | 若存在则覆盖(不存在不创建) |
O_APPEND | 若存在则追加结尾(不存在不创建) |
访问权限位
在sys/stat.h中定义:
- 宏名:
S_I[R|W|X][USR|GRP|OTH]
(USR
拥有者、GRP
群组、OTH
其他人)
mode_t umask(mode_t mask);
- 每个进程都用umask函数可以设置一个umask的值、最终打开文件时的文件访问权为:mode&~umask、通常为
umask(0)
打开文件
- 应用程序要求内核打开相关文件,将filename转为文件描述符,来声明使用某个IO设备。
- 并返回一个非负整数(描述符:在当前进程中当前未被打开的最小描述符,一般从3开始)
- linux shell在每个进程开始时自动打开三个文件(0-标准输入STDIN_FILENO、1-标准输出STDOUT_FILENO、2-标准错误STDERR_FILENO)。
- 使用limit命令:查看descriptors值-允许同事打开文件数量限制
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//int flags:访问文件方式
//mode_t mode:文件访问权限位
int open(char *filename,int flags,mode_t mode);
关闭文件
- 应用程序要求内核关闭文件、由内核释放文件打开时创建的数据结构、描述符变为当前未被打开的描述符、当进程结束时也可自动调用关闭所有文件并释放内存资源、当前文件位置k=0
- 关闭一个已关闭的描述符会出错(多线程时)
#include <unistd.h>
int close(int fd);
改变当前文件位置k
- 大多数文件都有文件位置k:在打开文件,内核中始终保持一个文件位置k(从文件开头起始的字节偏移量、已读写字节数、初始值为0)
- 但像终端和套接字没有文件位置k:无法通过改变当前文件位置,读取之前接收和未来接收的数据
#include<sys/types.h>
#include<unistd.h>
//int fd:已打开的文件描述符
//off_t offset:相对偏移量(long)
//int whence:参考系(SEEK_SET 文件开头、SEEK_CUR 文件当前位置、SEEK_END文件结尾)
//返回值:新的文件位置(偏移量)
off_t lseek(int fd, off_t offset, int whence);
读写文件
- 从m个字节的文件当前位置k,复制n个字节到内存,并将文件当前位置更新为k+n
- 从文件的当前位置k开始,把内存的n个字节复制,并将文件当前位置更新为k+n
#include <unistd.h>
//int fd:已打开的文件描述符
//void *buf:保存数据的内存位置
//size_t n:(ulong)一次读取或写入的字节数
ssize_t read(int fd,void *buf,size_t n);
ssize_t write(int fd,void *buf,size_t n);
返回值ssize_t:(long,计算字节数,值可为负)
- 当k≥m时,即触发
EOF条件
(并没有EOF符号)返回0 - 若读取出错返回-1
- 若正常返回实际读取的字节数(≤n)
- 其中,当返回<n时为不足值:在文件上读写遇到EOF、在终端上读写输入遇到回车(文本行)、在套接字上读写(底层缓冲约束1000~1500字节、网络延迟等)
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd,i;
ssize_t wlen, rlen;
char buf[10]={""};
//创建并写入文件
fd = open("myfile", O_WRONLY | O_CREAT, 0644);
wlen = write(fd, "hello_world", 11);
printf("write length:%zd\\n", wlen);
close(fd);//重置指针位置
//打开并读取文件
fd = open("myfile", O_RDONLY | O_CREAT, 0644);
for (i = 0; i < 5;i++){
rlen = read(fd, buf, 5);
printf("buf:%s,length=%zd\\n", buf, rlen);
memset(buf, 0, 10);
}
close(fd);
return 0;
}
输出
write length:11
buf:hello,length=5
buf:_worl,length=5
buf:d,length=1
buf:,length=0
buf:,length=0
使用命令可以跟踪调用函数过程
strace -e trace=write,read ./xxx.c
奥哈拉伦博士对Unix I/O函数的封装:RIO包
RIO无缓冲的输入输出函数(化零为整)
合适用途:socket收发通讯帧时,解决1.5KB以上的较长帧会被系统底层缓冲约束自动拆分成若干帧
- 针对终端输入遇到回车或套接字底层缓冲约束1000~1500字节,导致使read或write返回值不足n时
- 会自动调用read或write自动拼接usrbuf内,直到返回n(终端、套接字)或直到EOF(套接字)停止调用
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
rio_readn示例过程
RIO带缓冲的输入函数(化整为零)
合适用途:读取一个超大文件时,调用一次read,将大量数据放到内部缓冲区中,供rio_readnb分多次取出少量数据(减少io开销)
#define RIO_BUFSIZE 8192
typedef struct{
int rio_fd;//文件描述符
int rio_cnt;//内部缓冲区未读取字节数
char *rio_bufptr;//内部缓冲区未读的位置
char rio_buf[RIO_BUFSIZE];//内部缓冲区
}
void rio_readinitb(rio_t *rp,int fd); //初始化结构体变量rp、初始化内部缓冲区并绑定一个fd
ssize_t rio_read(rio_t *rp,void *usrbuf,size_t n);//一次read填满内部缓冲区,并从内部缓冲区读取一部分数据
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);//从rp中的内部缓冲区读取n个字节,将rio_readn中的read替换为rio_read
ssize_t rio_readlineb(rio_t *rp,void *usrbuf, size_t maxlen);//从rp中的内部缓冲区读取一个文本行的内容
rio_read示例过程
- 初始状态
- 调用read填充内部缓冲区
- 读取2048字节内容到usrbuf
- 再次读取2048字节内容到usrbuf
函数实现
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;//尚未读取字节数
ssize_t nread;//本次读取字节数
char *bufp = usrbuf;//临时操作内存的指针(保持usrbuf的位置不变)
while (nleft > 0)
{
if ((nread = read(fd, bufp, nleft)) < 0){
return -1;//报错
}else if (nread == 0){
break; //EOF
}
nleft -= nread;//更新尚未读取字节数
bufp += nread;//移动临时操作内存的指针
}
return (n - nleft);
}
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;//尚未写入字节数
ssize_t nwritten;//本次写入字节数
char *bufp = usrbuf;//临时操作内存的指针(保持usrbuf的位置不变)
while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) <= 0){
return -1;//报错
}
nleft -= nwritten;//更新尚未写入字节数
bufp += nwritten;//移动临时操作内存的指针
}
return n;
}
void rio_readinitb(rio_t *rp,int fd){
rp->rio_fd=fd;
rp->rio_cnt=0;
rp->rio_bufptr=rp->rio_buf;
}
static ssize_t rio_read(rio_t *rp,void *usrbuf,size_t n){
int cnt;
//如果内部缓冲区为空
while(rp->rio_cnt<=0){
rp->rio_cnt=read(rp->rio_fd,rp->rio_buf,sizeof(rp->rio_buf));//一次read将内部缓冲区填满
if(rp->rio_cnt<0){
return -1;//报错
}else if(rp->rio_cnt==0){
return 0;//EOF
}else{
rp->rio_bufptr=rp->rio_buf;//未读指针指向内部缓冲区开头
}
}
cnt=(rp->rio_cnt<n?rp->rio_cnt:n); //cnt=min(rp->rio_cnt,n)
memcpy(usrbuf,rp->rio_bufptr,cnt);
rp->rio_bufptr+=cnt;
rp->rio_cnt-=cnt;
return cnt;
}
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
size_t nleft = n;//尚未读取字节数
ssize_t nread;//本次读取字节数
char *bufp = usrbuf;//临时操作内存的指针(保持usrbuf的位置不变)
while (nleft > 0)
{
if ((nread = rio_read(rp, bufp, nleft)) < 0){
return -1;//报错
}else if (nread == 0){
break; //EOF
}
nleft -= nread;//更新尚未读取字节数
bufp += nread;//移动临时操作内存的指针
}
return (n - nleft);
}
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c;
char *bufp = usrbuf; //临时操作内存的指针(保持usrbuf的位置不变)
for (n = 1; n < maxlen; n++)
{
if ((rc = rio_read(rp, &c, nleft)) == 1){
*bufp++ = c;
if (c == \'\\n\'){
n++;
break;
}
}else if (rc == 0){
if (n == 1){
return 0; //EOF 尚未读取数据
}else{
break; //EOF 读取部分数据
}
}else{
return -1; //报错
}
}
*bufp = 0;//结束符0x00
return n - 1;
}