[CS:APP]程序间的交互和通信

炒沙作縻终不饱,缕冰文章费工巧。这篇文章主要讲述[CS:APP]程序间的交互和通信相关的知识,希望能为你提供帮助。
《鸟哥的Linux私房菜:基础学习篇(第四版)》

  • 第5章 Linux的文件权限与目录配置
  • 第6章 LInux文件与目录管理(正在进行)
《CSAPP》
  • 第10章 系统级IO 10.1~10.5(正在进行)

目录
  • 文件
    • 文件名
    • 文件扩展名
    • 文件种类
    • 多用户
    • 文件权限
    • Linux命令
    • Linux目录规范
  • Unix I/O函数
    • 访问文件方式
    • 访问权限位
    • 打开文件
    • 关闭文件
    • 改变当前文件位置k
    • 读写文件
  • 奥哈拉伦博士对Unix I/O函数的封装:RIO包
    • RIO无缓冲的输入输出函数(化零为整)
    • RIO带缓冲的输入函数(化整为零)
    • 函数实现

[CS:APP]程序间的交互和通信

文章图片

文件 文件名文件或目录名的最大长度: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}
ugo代表三种身份、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)
打开文件
  1. 应用程序要求内核打开相关文件,将filename转为文件描述符,来声明使用某个IO设备。
  2. 并返回一个非负整数(描述符:在当前进程中当前未被打开的最小描述符,一般从3开始)
  3. linux shell在每个进程开始时自动打开三个文件(0-标准输入STDIN_FILENO、1-标准输出STDOUT_FILENO、2-标准错误STDERR_FILENO)。
  4. 使用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

【[CS:APP]程序间的交互和通信】使用命令可以跟踪调用函数过程
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示例过程
[CS:APP]程序间的交互和通信

文章图片

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、初始化内部缓冲区并绑定一个fdssize_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示例过程
  1. 初始状态
    [CS:APP]程序间的交互和通信

    文章图片
  2. 调用read填充内部缓冲区
    [CS:APP]程序间的交互和通信

    文章图片
  3. 读取2048字节内容到usrbuf
    [CS:APP]程序间的交互和通信

    文章图片
  4. 再次读取2048字节内容到usrbuf
    [CS:APP]程序间的交互和通信

    文章图片
函数实现
#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; }


    推荐阅读