UNIX环境高级编程(APUE)
一、文件系统
第四、六、七章
二、 I/0
3-5章、14章
文件I/O
标准I/O
三、并发
信号10章节
多进程并发
多线程并发10-11章节
四、IPC:进程间通信
8章节 进程基础(涉及到多进程)
13章 守护进程
15-16章
注意事项:
1、弃用root用户
2、重构代码
3、课堂重点:项目,课堂代码,面试题,实验性题目,推荐书籍课后题
I/O:input & output,是一切实现的基础
标准io:stdio 依赖于sysio
系统调用io:sysio(文件IO)
优先使用标准IO,可移植性强。
I/O操作
stdio:FILE 类型贯穿始终
打开文件流
fopen();
r r+ 文件必须存在,w w+ a a+文件不存在则创建。
关闭文件流
fclose();
从文件流获取单个字符
fgetc();
从文件流打印单个字符
fputc();
获取从文件流中获取字符串
fgets();
eg:fgets(buf,size,stream);
退出条件
1、输入字符达到了size-1
2、‘\n’
从文件流中打印字符串
fputs();
代码例程。
从指定文件流中读取n个size大小的数据,存储到buf中
fread(buf,size,nmemb,fp);
1->数据量足够
2->只有5个字节
fread(buf,1,10,fp);
1->返回值 10->10字节
2->5 ->5字节
fread(buf,10,1,fp);
1->返回值 1->10字节
2- 不够1个字节 返回为0 ->???喵喵喵?
向文件流中写入数据
fwrite();
打印函数
printf();
更倾向于用 fprintf
指向一个指针的流
其他文件打印/输出流
sprintf()、snprintf()、scanf()、fscanf()、sscanf();
跳转文件流到指定字符位置
fseek();
相当于rewind的封装。可以帮助完成空洞文件。
返回字符长度
ftell();
返回到文件开头位置
rewind();
刷新缓冲区
fflush();
缓冲区的作用:大多数情况下是好事,合并系统调用
行缓冲:换行的时候刷新,满了的时候刷新,强制刷新(标准输出是这样的,因为这是终端设备)
全缓冲:满了的时候刷新,强制刷新(默认,只要不是中断设备)
无缓冲:如stderr,需要立即输出的内容
用于设定文件流的缓冲区
setvbuf
打开文件
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
FILE *fp;
fp = fopen("tmp","w");
if (fp == NULL)
{
/*case 1:*/
fprintf(stderr,"fopen() failed! errno = %d \n ",errno);
/*case 2:常用*/
perror("fopen()");
/*case 3:常用*/
fprintf(stderr,"fopen():%s\n ",strerror(errno));
exit(1);
}
puts("OK!");
fclose(fp);
exit(0);
}
最大打开文件个数
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
int count;
FILE *fp = NULL;
while(1)
{
/*以读的形式打开文件*/
fp = fopen("tmp","w");
if (fp == NULL)
{
perror("fopen()");
break;
}
count++;
}
/*打印出最大能够打印出的文件个数*/
printf("count = %d\n",count);
exit(0);
}
统计文件字符个数
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char **argv)
{
FILE *fp,*fpd;
int count = 0;
/*判断参数是否足够,不足则报错*/
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
/*以读的形式打开文件*/
fp = fopen(argv[1],"r");
if (fp == NULL)
{
perror("fpoen()");
exit(1);
}
/*跳转到文件尾巴*/
fseek(fp,0,SEEK_END);
/*打印出文件字符个数*/
printf("%ld\n",ftell(fp));
fclose(fp);
exit(0);
}
自制copy函数
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char **argv)
{
FILE *fps,*fpd;
int ch;
/*判断参数是否足够,不足则报错*/
if (argc < 3)
{
fprintf(stderr,"Usage : %s <source_filename> <dest_filename>\n",argv[0]);
exit(1);
}
fps = fopen(argv[1],"r");
if (fps == NULL)
{
/*释放fps*/
fclose(fps);
perror("fopen()");
exit(1);
}
fpd = fopen(argv[2],"w");
if (fpd == NULL)
{
perror("fopen()");
exit(1);
}
while (1)
{
/*获取文件字符*/
ch = fgetc(fps);
/*如果到达文件尾*/
if (ch == EOF)
break;
/*打印字符到文件fpd中*/
fputc(ch,fpd);
}
/*释放文件指针fpd、fps*/
fclose(fpd);
fclose(fps);
exit(0);
}
自制fgets获取字符串函数
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define BUFSIZE 1024
int main(int argc,char **argv)
{
FILE *fps,*fpd;
char buf[BUFSIZE];
/*判断参数是否足够,不足则报错*/
if (argc < 3)
{
fprintf(stderr,"Usage : %s <source_filename> <dest_filename>\n",argv[0]);
exit(1);
}
fps = fopen(argv[1],"r");
if (fps == NULL)
{
/*指针为空,直接释放*/
fclose(fps);
perror("fopen()");
exit(1);
}
fpd = fopen(argv[2],"w");
if (fpd == NULL)
{
perror("fopen()");
exit(1);
}
while (fgets(buf,BUFSIZE,fps)!= NULL)
{
fputs(buf,fpd);
}
fclose(fpd);
fclose(fps);
exit(0);
}
自制fread
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define BUFSIZE 1024
int main(int argc,char **argv)
{
FILE *fps,*fpd;
char buf[BUFSIZE];
int n;
if (argc < 3)
{
fprintf(stderr,"Usage : %s <source_filename> <dest_filename>\n",argv[0]);
exit(1);
}
fps = fopen(argv[1],"r");
if (fps == NULL)
{
fclose(fps);
perror("fopen()");
exit(1);
}
fpd = fopen(argv[2],"w");
if (fpd == NULL)
{
perror("fopen()");
exit(1);
}
while ((n = fread(buf,1,BUFSIZE,fps))>0)
{
fwrite(buf,1,n,fpd);
}
fclose(fpd);
fclose(fps);
exit(0);
}
获取文件字符长度
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char **argv)
{
FILE *fp,*fpd;
char *linebuf;
int count = 0;
size_t linsize;
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
fp = fopen(argv[1],"r");
if (fp == NULL)
{
perror("fpoen()");
exit(1);
}
while (1)
{
if(getline(&linebuf,&linsize,fp) < 0)
break;
printf("%ld\n",strlen(linebuf));
printf("%d\n",linsize);
}
fseek(fp,0,SEEK_END);
printf("%ld\n",ftell(fp));
fclose(fp);
exit(0);
}
系统调用IO
P135:重要
文件描述符的概念(通俗来讲,整形数找到指针,指针找到结构体,通过结构体来找到正确的文件。文件描述符通常使用当前范围内最小的)。
fd文件描述符是文件IO中贯穿始终的类型。
文件IO 的操作:
open(), close(), read(), write(), lseek():相当于是flseek和ftell的综合,返回值是字节的距离。
f***都是依赖文件I/O
文件描述符的本质是一个整型数组下标,int类型。
标准IO和系统调用IO的映射:
r -> O_RDONLY
r+ -> O_RDWR
w -> O_WRONLY|O_CREAT|O_TRUNC
w+ -> O_RDWR|O_CREAT|O_TRUNC
将文件IO与标准IO进行区别
举例:传达室老大爷跑邮局。标准IO有缓冲操作。文件IO没有缓冲操作。
区别:响应速度和吞吐量。文件IO响应速度快,标准IO 的吞吐量大。
面试:如何是一个程序变快?两面回答。
用户体验是吞吐量。最好用标准IO。
提醒:标准IO与文件IO不可混用。
函数fileno(),可以实现标准IO转换成文件IO。
函数 fdopen(),可以实现把一个已经打开的文件描述符封装成一个FILE*使用。
IO的效率问题
习题:将mycopy.c程序进行更改,将BUFSIZE的值放大,并且观察进程的时间,注意性能最佳拐点出现时的BUFSIZE值,以及何时程序会出问题。
文件共享
多个任务共同操作一个任务或者协同完成任务。
面试:删除一个文件的第十行。
补充函数 :truncate //截断文件多长。
/******************************************/
1-> open r ->fd1->lseek 11
1-> open r+(读写) ->fd2->lseek 10
while()
{
1->fd1->read
2->fd2-> write
}
/******************************************/
process1 ->open ->r
process2 ->open ->r+
p1->read ->p2->write
原子操作:不可分割的操作。
原子:不可分割的最小单位。
原子操作的作用:解决竞争和冲突。
程序中的重定向是怎么实现的:dup,dup2
同步:
sync(全局催促)
解除设备挂载
fsync
同步一个文件的 buff或者cache。
fdatasync
只刷数据,不刷亚数据(文件最后的修改时间,文件属性)。
管家函数:
fcntl() ; 文件描述符所变的魔术,都来自fantl();
ioctl(); 设备相关的内容
/dev/fd/目录:虚目录,显示当前文件描述符的信息。
复制的实现
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFSIZE 1024
int main(int argc,char **argv)
{
int sfd,dfd;
int ch,len,ret,pos = 0;
char buf[BUFSIZE];
if (argc < 3)
{
fprintf(stderr,"Usage : %s <source_filename> <dest_filename>\n",argv[0]);
exit(1);
}
sfd = open(argv[1],O_RDONLY);
if (sfd < 0)
{
perror("open()");
exit(1);
}
dfd = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0600);
if (dfd < 0)
{
close(sfd);
perror("open()");
exit(1);
}
while (1)
{
/*len read 10 byte*/
len = read(sfd,buf,BUFSIZE);
if (len<0)
{
perror("read()");
break;
}
if (len == 0)
{
break;
}
pos = 0;
/*判断len是否全部填入*/
while (len > 0)
{
/*from buf read len byte to dfd*/
ret = write(dfd,buf+pos,len);
if (ret < 0 )
{
perror("write()");
exit(1);
}
/*计算下一个写入的位置*/
pos += ret;
/*计算剩下的没填入的字节数*/
len -= ret;
}
}
close(dfd);
close(sfd);
exit(0);
}
重定向
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFSIZE 1024
#define FNAME "/tmp/out"
int main(int argc,char **argv)
{
/*完成重定向操作*/
int fd;
fd = open(FNAME,O_WRONLY|O_CREAT|O_TRUNC,0600);
if (fd < 0 )
{
perror("open()");
exit(1);
}
close(1);
/*文件描述符复制了一份,产生一个新的没用过的最小描述符,和之前指向同一个结构体*/
// dup(fd);
// close(fd);
/*原子操作*/
dup2(fd,1);
if (fd != 1)
{
close(fd);
}
/************************************/
puts("hello!");
exit(0);
}
读取目录readdir的使用—P150
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glob.h>
#include <dirent.h>
#define PAT "/etc"
int main(int argc,char **argv)
{
DIR *dp;
struct dirent *cur;
dp = opendir(PAT);
if (dp == NULL)
{
perror("opendir()");
exit(1);
}
while ((cur = readdir(dp)) != NULL)
puts(cur->d_name);
closedir(dp);
exit(0);
}
stat函数详解
函数原型:int stat(const char *path, struct stat *buf)
返回值:成功返回0,失败返回-1;
参数:文件路径(名),struct stat 类型的结构体
struct stat
{
dev_t st_dev; /* ID of device containing file */文件使用的设备号
ino_t st_ino; /* inode number */ 索引节点号
mode_t st_mode; /* protection */ 文件对应的模式,文件,目录等
nlink_t st_nlink; /* number of hard links */ 文件的硬连接数
uid_t st_uid; /* user ID of owner */ 所有者用户识别号
gid_t st_gid; /* group ID of owner */ 组识别号
dev_t st_rdev; /* device ID (if special file) */ 设备文件的设备号
off_t st_size; /* total size, in bytes */ 以字节为单位的文件容量
blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小
blkcnt_t st_blocks; /* number of 512B blocks allocated */ 该文件所占的磁盘块
time_t st_atime; /* time of last access */ 最后一次访问该文件的时间
time_t st_mtime; /* time of last modification */ /最后一次修改该文件的时间
time_t st_ctime; /* time of last status change */ 最后一次改变该文件状态的时间
};
stat结构体中的st_mode 则定义了下列数种情况:
S_IFMT 0170000 文件类型的位遮罩
S_IFSOCK 0140000 套接字
S_IFLNK 0120000 符号连接
S_IFREG 0100000 一般文件
S_IFBLK 0060000 区块装置
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符装置
S_IFIFO 0010000 先进先出
S_ISUID 04000 文件的(set user-id on execution)位
S_ISGID 02000 文件的(set group-id on execution)位
S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限
S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限
S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限
S_IWGRP 00020 用户组具可写入权限
S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限
S_IWOTH 00002 其他用户具可写入权限
S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLNK (st_mode) 判断是否为符号连接
S_ISREG (st_mode) 是否为一般文件
S_ISDIR (st_mode) 是否为目录
S_ISCHR (st_mode) 是否为字符装置文件
S_ISBLK (s3e) 是否为先进先出
S_ISSOCK (st_mode) 是否为socket
若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录啦。
st_mode 的结构
st_mode 主要包含了 3 部分信息:
1、 15-12 位保存文件类型
2、 11-9 位保存执行文件时设置的信息
3、 8-0 位保存文件访问权限
示例:mydu查看文件大小
模拟du命令查看当前目录下的文件大小
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glob.h>
#include <dirent.h>
#define PATHSIZE 1024
static int path_noloop(const char *path)
{
char *pos;
//找到最后一次出现‘/’的字符后的串,最右边的串
pos = strrchr(path,'/');
if (pos == NULL)
exit(1);
//解析隐藏文件
if(strcmp(pos+1,".") == 0||strcmp(pos+1,"..")==0)
return 0;
return 1;
}
static int64_t mydu(const char *path)
{
int64_t sum;
static struct stat statres;
static char nextpath[PATHSIZE];
glob_t globres;
//解析传入路径,将解析结果保存在statres中。
if(lstat(path,&statres)<0)
{
perror("lstat()");
exit(1);
}
//判断是否是目录文件,非目录返回
if(!S_ISDIR(statres.st_mode))
return statres.st_blocks;
/*解析非隐藏的文件,到globres当中*/
strncpy(nextpath,path,PATHSIZE);//path传入nextpath
strncat(nextpath,"/*",PATHSIZE);//追加新的路径
glob(nextpath,0,NULL,&globres);
/*解析隐藏的文件,到globres当中*/
strncpy(nextpath,path,PATHSIZE);
strncat(nextpath,"/.*",PATHSIZE);
glob(nextpath,GLOB_APPEND,NULL,&globres);//追加,把这次解析的结果(隐藏的结果),追加到结果中
sum = 0;
for (int i = 0; i < globres.gl_pathc; i++)
{
//解析的path不是循环
if (path_noloop(globres.gl_pathv[i]))
{
//递归调用
sum += mydu(globres.gl_pathv[i]);
}
}
sum += statres.st_blocks;
globfree(&globres);
return sum;
}
int main(int argc,char **argv)
{
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
printf("%ld\n",mydu(argv[1])/2);
exit(0);
}
}
username 的使用
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glob.h>
#include <dirent.h>
#include <pwd.h>
int main(int argc,char **argv)
{
struct passwd *pwdline;
if (argc < 2)
{
fprintf(stderr,"Usage...\n");
exit(1);
}
pwdline = getpwuid(atoi(argv[1]));
puts(pwdline->pw_name);
exit(0);
}