Unix/Linux编程实践读书笔记
Unix/Linux编程实践教程
作者: Bruce Molay
原作名: Understanding UNIX/LINUX Programming
译者: 杨宗源 / 黄海涛
第1章 Unix系统编程概述
通过三个方法理解学习系统编程
- 分析程序
- 学习系统调用
- 编程实现
对应三个问题
what does it do->how does it work->let's write our own version
more 的实现
more的实现版本1
1 /** 2 * read and print 24 lines then pause for a few special commands 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 9 #define PAGELEN 24 10 #define LINELEN 512 11 void do_more(FILE*); 12 int see_more(); 13 14 int main(int ac, char *av[]) { 15 FILE* fp; 16 if (ac == 1) { 17 do_more(stdin); 18 } else { 19 while (--ac) { 20 if ((fp = fopen(*++av, "r")) != NULL) { 21 do_more(fp); 22 fclose(fp); 23 } else { 24 exit(1); 25 } 26 } 27 } 28 return 0; 29 } 30 31 /** 32 * read PAGELEN lines, then call see_more() for further instructions 33 */ 34 void do_more(FILE* fp) { 35 char line[LINELEN]; 36 int num_of_lines = 0; 37 int see_more(), reply; 38 while (fgets(line, LINELEN, fp)) { 39 if (num_of_lines == PAGELEN) { 40 reply = see_more(); 41 if (reply == 0) 42 break; 43 num_of_lines -= reply; 44 } 45 if (fputs(line, stdout) == EOF) 46 exit(1); 47 num_of_lines++; 48 } 49 } 50 51 /** 52 * print message, wait for response, return # of lines to advance 53 * q means no, space means yes, CR means one line 54 */ 55 int see_more() { 56 int c; 57 printf("\033[7m more? \033[m"); 58 while ((c = getchar()) != EOF) { 59 if (c == 'q') 60 return 0; 61 if (c == ' ') 62 return PAGELEN; 63 if (c == '\n') 64 return 1; 65 } 66 return 0; 67 }
上述版本存在许多问题,比如输入q和空格后,需要按回车,而且输入是有回显的。
输入重定向问题
cat more01.c | more
cat more01.c | ./more01
more01的标准输入重定向到了cat命令的标准输出,而程序中使用getchar(),从标准输入读取数据,此时就会出现问题。
解决办法是,从标准输入中读入分页数据,改为从键盘读用户输入。
more的实现版本2
1 /** 2 * read and print 24 lines then pause for a few special commands 3 * feature of version 0.2: reads from /dev/tty for commands 4 */ 5 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #define PAGELEN 24 11 #define LINELEN 512 12 void do_more(FILE*); 13 int see_more(FILE*); 14 15 int main(int ac, char *av[]) { 16 FILE* fp; 17 if (ac == 1) { 18 do_more(stdin); 19 } else { 20 while (--ac) { 21 if ((fp = fopen(*++av, "r")) != NULL) { 22 do_more(fp); 23 fclose(fp); 24 } else { 25 exit(1); 26 } 27 } 28 } 29 return 0; 30 } 31 32 /** 33 * read PAGELEN lines, then call see_more() for further instructions 34 */ 35 void do_more(FILE* fp) { 36 char line[LINELEN]; 37 int num_of_lines = 0; 38 int reply; 39 40 FILE* fp_tty = fopen("/dev/tty", "r"); 41 if (fp_tty == NULL) 42 exit(1); 43 44 while (fgets(line, LINELEN, fp)) { 45 if (num_of_lines == PAGELEN) { 46 reply = see_more(fp_tty); 47 if (reply == 0) 48 break; 49 num_of_lines -= reply; 50 } 51 if (fputs(line, stdout) == EOF) 52 exit(1); 53 num_of_lines++; 54 } 55 } 56 57 /** 58 * print message, wait for response, return # of lines to advance 59 * q means no, space means yes, CR means one line 60 */ 61 int see_more(FILE* cmd) { 62 int c; 63 printf("\033[7m more? \033[m"); 64 while ((c = getc(cmd)) != EOF) { 65 if (c == 'q') 66 return 0; 67 if (c == ' ') 68 return PAGELEN; 69 if (c == '\n') 70 return 1; 71 } 72 return 0; 73 }
文件/dev/tty,是键盘和显示器的设备描述文件,向这个文件写相当于显示在用户的屏幕上,读相当于从键盘获取用户的输入。即使程序的输入/输出被重定向,程序仍可通过这个文件同终端交换数据。
第2章 用户、文件操作与联机帮助:编写who命令
概念
- 联机帮助作用与使用方法;
- 文件操作、建立读写;
- 缓冲:用户级缓冲与内核级缓冲;
- 内核模式、用户模式和系统调用的代价;
命令who
who命令能做些什么?
执行who命令,查看手册帮助man who,
localhost ~]$ man who
WHO(1) User Commands WHO(1)NAME
who - show who is logged onSYNOPSIS
who [OPTION]... [ FILE | ARG1 ARG2 ]DESCRIPTION
Print information about users who are currently logged in.-a, --all
same as -b -d --login -p -r -t -T -u
who命令是如何工作的?
从unix中学习unix
- 阅读联机帮助
- 搜索联机帮助
- 阅读.h文件
- 从参阅部分得到启示
阅读联机帮助
localhost ~]$ man who
...
DESCRIPTION
...
If FILE is not specified, use /var/run/utmp. /var/log/wtmp as FILE is common. If ARG1
ARG2 given, -m presumed: 'am i' or 'mom likes' are usual....
从描述中可以看到通常使用文件 /var/run/utmp. /var/log/wtmp
搜索联机帮助
localhost ~]$ man -k utmp
dump-utmp (8) - print an utmp file in human-readable format.
getutmp (3) - copy utmp structure to utmpx, and vice versa
getutmpx (3) - copy utmp structure to utmpx, and vice versa
utmp (5) - login records
endutent (3) - access utmp file entries
或可直接使用man utmp
从搜索的联机帮助中看到 utmp (5) - login records,查找该帮助
localhost ~]$ man 5 utmp
UTMP(5) Linux Programmer's Manual UTMP(5)NAME
utmp, wtmp - login recordsSYNOPSIS
#include <utmp.h>DESCRIPTION
The utmp file allows one to discover information about who is currently using the system.
There may be more users currently using the system, because not all programs use utmp log‐ging.
阅读.h文件
上述帮助 man 5 utmp,给出了结构体 utmp 的描述
The file is a sequence of utmp structures, declared as follows in <utmp.h> (note that this
is only one of several definitions around; details depend on the version of libc):
当然也可查找阅读.h源文件
localhost ~]$ sudo find / -name "utmp.h"
find: ‘/run/user/1000/gvfs’: Permission denied
/usr/lib/x86_64-redhat-linux6E/include/bits/utmp.h
/usr/lib/x86_64-redhat-linux6E/include/utmp.h
/usr/include/bits/utmp.h
/usr/include/utmp.h
who的工作原理
who 通过读文件获取需要的信息,每个登录的用户信息都记录在文件中。文件中的结构存放用户登录信息,读取信息并显示出来实现who操作。
如何编写who
- 从文件中读取数据结构
- 将结构中的信息以适当的方式显示出来
查找联机帮助获取文件读写方法:man -k file | grep read
...
pwrite64 (2) - read from or write to a file descriptor at a given offset
read (2) - read from a file descriptor
read (3p) - read from a file
readahead (2) - perform file readahead into page cache
...
man 2 read
得到函数原型 ssize_t read(int fd, void *buf, size_t count);
SEE ALSO
close(2), fcntl(2), ioctl(2), lseek(2), open(2), pread(2), readdir(2), readlink(2),
readv(2), select(2), write(2), fread(3)
通过open 获取文件描述符。
通过open帮助,找到对close的引用。
因此整个过程使用open,read,close系统调用。
who1版本
1 /** 2 * open, read UTMP file, and show 3 */ 4 5 #include <stdio.h> 6 #include <utmp.h> 7 #include <fcntl.h> 8 #include <unistd.h> 9 #include <stdlib.h> 10 11 #define SHOWHOST 12 13 void show_info(struct utmp*); 14 15 int main() { 16 struct utmp current_record; 17 int utmpfd; 18 int reclen = sizeof(current_record); 19 20 printf("Utmp_file = %s\n\n",UTMP_FILE); 21 if ((utmpfd = open(UTMP_FILE, O_RDONLY)) == -1) { 22 perror(UTMP_FILE); 23 exit(1); 24 } 25 26 while (read(utmpfd, ¤t_record, reclen) == reclen) 27 show_info(¤t_record); 28 close(utmpfd); 29 return 0; 30 } 31 32 /** 33 * display cotents of the utmp struct in human readable form 34 * *note* these sizes should not be hardwired 35 */ 36 void show_info(struct utmp* utbufp) { 37 printf("%-8.8s", utbufp->ut_user); 38 printf(" "); 39 printf("%-8.8s", utbufp->ut_line); 40 printf(" "); 41 printf("%10d", utbufp->ut_time); 42 printf(" "); 43 #ifdef SHOWHOST 44 printf("(%s)", utbufp->ut_host); 45 #endif 46 printf("\n"); 47 }
其中宏UTMP_FILE 为 /var/run/utmp
grep "UTMP_FILE" /usr/include/utmp.h
#define UTMP_FILE _PATH_UTMP
#define UTMP_FILENAME _PATH_UTMPgrep "_PATH_UTMP" /usr/include/*.h
/usr/include/paths.h:#define _PATH_UTMP "/var/run/utmp"
/usr/include/utmp.h:#define UTMP_FILE _PATH_UTMP
/usr/include/utmp.h:#define UTMP_FILENAME _PATH_UTMP
/usr/include/utmpx.h:# define UTMPX_FILE _PATH_UTMPX
/usr/include/utmpx.h:# define UTMPX_FILENAME _PATH_UTMPX
改进地方:正确显示登录时间,去除空白记录。
去除空白记录
utmp结构体中成员 ut_type 值为7的时候,表示这是一个已经登录的用户,据此修改程序消除空白行。
以可读方式显示登录时间
man -k time | grep transform
man -k time | grep convert
man ctime
编写cp(读和写)
- 问题1:cp命令能做些什么
- 问题2:cp命令是如何创建/重写文件的
- 问题3:如何编写cp
代码
1 /** 2 * uses read and write with tunable buffer size 3 */ 4 #include <stdio.h> 5 #include <unistd.h> 6 #include <fcntl.h> 7 #include <stdlib.h> 8 9 #define BUFFERSIZE 4096 10 #define COPYMODE 0644 11 12 void oops(char *, char *); 13 14 int main(int argc, char *argv[]) { 15 int in_fd, out_fd, n_chars; 16 char buf[BUFFERSIZE]; 17 18 if (argc != 3) { 19 fprintf(stderr, "usage: %s source destination\n", *argv); 20 exit(1); 21 } 22 23 if ((in_fd = open(argv[1], O_RDONLY)) == -1) 24 oops("Cannot open ", argv[1]); 25 26 if ((out_fd = creat(argv[2], COPYMODE)) == -1) 27 oops("Cannot create ", argv[2]); 28 29 // copy files 30 while ((n_chars = read(in_fd, buf, BUFFERSIZE)) > 0) { 31 if (write(out_fd, buf, n_chars) != n_chars) 32 oops("Write error to ", argv[2]); 33 } 34 35 if (n_chars == -1) 36 oops("Read error from ", argv[1]); 37 38 if (close(in_fd) == -1 || close(out_fd) == -1) 39 oops("Error closing files", ""); 40 41 return 0; 42 } 43 44 void oops(char *s1, char *s2) { 45 fprintf(stderr, "Error: %s ", s1); 46 perror(s2); 47 exit(1); 48 }
提高文件I/O效率的方法:使用缓冲
缓冲区的大小对性能的影响
为什么系统调用需要很多时间
在who2中添加缓冲区,每次读取多个结构体数据,减少系统调用次数。
1 /** 2 * utmplib.h - function to buffer read from utmp file 3 * functions are 4 * utmp_open(filename) - open file 5 * return -1 on error 6 * utmp_next() - return pointer to next struct 7 * return NULL on eof 8 * ump_close() - close file 9 * 10 * read NRECS per read and then doles them out from the buffer 11 */ 12 13 #include <stdio.h> 14 #include <fcntl.h> 15 #include <unistd.h> 16 #include <utmp.h> 17 18 #define NRECS 16 19 #define NULLUT ((struct utmp *)NULL) 20 #define UTSIZE (sizeof(struct utmp)) 21 22 static char utmpbuf[NRECS * UTSIZE]; // storage 23 static int num_recs; // num stored 24 static int cur_rec; // next to go 25 static int fd_utmp = -1; // read from 26 27 int utmp_open(char *filename) { 28 fd_utmp = open(filename, O_RDONLY); 29 cur_rec = num_recs = 0; 30 return fd_utmp; 31 } 32 33 int utmp_reload() { 34 int amt_read; 35 amt_read = read(fd_utmp, utmpbuf, NRECS * UTSIZE); 36 num_recs = amt_read / UTSIZE; 37 cur_rec = 0; 38 return num_recs; 39 } 40 41 struct utmp * utmp_next() { 42 struct utmp *recp; 43 if (fd_utmp == -1) 44 return NULLUT; 45 if (cur_rec == num_recs && utmp_reload() == 0) 46 return NULLUT; 47 48 recp = (struct utmp *)&utmpbuf[cur_rec * UTSIZE]; 49 cur_rec++; 50 return recp; 51 } 52 53 void utmp_close() { 54 if (fd_utmp != -1) 55 close(fd_utmp); 56 }
主函数
1 /** 2 * command 'who' with buffered reads 3 * - open, read UTMP file, and show 4 * - suppresses empty records 5 * - formats time nicely 6 * - buffers input (using utmplib) 7 */ 8 9 #include <stdio.h> 10 #include <utmp.h> 11 #include <fcntl.h> 12 #include <unistd.h> 13 #include <stdlib.h> 14 #include <time.h> 15 #include "utmplib.c" 16 17 #define SHOWHOST 18 void show_time(long); 19 void show_info(struct utmp*); 20 21 int main() { 22 struct utmp * utbufp; 23 24 if (utmp_open(UTMP_FILE) == -1) { 25 perror(UTMP_FILE); 26 exit(1); 27 } 28 29 while ((utbufp = utmp_next()) != ((struct utmp *)NULL)) 30 show_info(utbufp); 31 utmp_close(); 32 return 0; 33 } 34 35 /** 36 * display cotents of the utmp struct in human readable form 37 * *note* these sizes should not be hardwired 38 */ 39 void show_info(struct utmp* utbufp) { 40 if (utbufp->ut_type != USER_PROCESS) // user only 41 return; 42 43 printf("%-8.8s", utbufp->ut_name); 44 printf(" "); 45 printf("%-8.8s", utbufp->ut_line); 46 printf(" "); 47 show_time(utbufp->ut_time); 48 #ifdef SHOWHOST 49 if (utbufp->ut_host[0] != '\0') 50 printf("(%s)", utbufp->ut_host); 51 #endif 52 printf("\n"); 53 } 54 55 /** 56 * display time in a format fit for human consumption 57 * use ctime to build a string then picks pars out of it 58 * Node: %12.12s prints a string 12 chars wide and LIMITS 59 * it to 12 chars 60 */ 61 void show_time(long timeval) { 62 char *cp; 63 cp = ctime(&timeval); // looks like: Mon Feb 4 00:46:40 EST 1991 64 printf("%12.12s", cp + 4); 65 }
内核缓冲技术
应用缓冲技术对提高系统效率是明显的,主要思想是一次读入大量的数据存入缓冲区,需要的时候然后从缓冲区读取数据。
内核模式和用户模式之间的切换需要时间,相比之下,磁盘的I/O操作消耗更多时间,效率更低,因此也需要缓冲技术。
内核将磁盘上的数据块复制到内核缓冲区,当一个用户空间中的进程需要从磁盘上读数据时,内核一般不直接读磁盘,而是将内核缓冲区的数据复制到进程缓冲区。
从理论上讲,内核可以在任何时候写磁盘,但不是任意一次write操作都会导致内核的写动作,内核会把要写的数据缓存在内核缓冲区,积累到一定数量后,再一次性写入磁盘。
应用内核缓冲技术结果:
- 提高磁盘I/O效率,
- 优化磁盘写操作,
- 需及时的将缓存数据写入磁盘。
第3章 目录与文件属性:编写Is
问题1:ls命令能做什么
问题2:ls是如何工作的
man -k direct | grep read
readdir (3) - read a directory
readdir_r (3) - read a directory
seekdir (3) - set the position of the next readdir() call in the directory stream.
readdir (2) - read directory entry
readdir (3p) - read a directory
readdir_r (3p) - read a directory
readlinkat (2) - read value of a symbolic link relative to a directory file descriptor
问题3:如何编写ls
ls算法如下
main
opendir
while(readdir)
print dname
closedir
实现代码ls1
1 /** 2 * purpose list contents of directory or directories 3 * action if no args, user . else list files args 4 */ 5 6 #include <stdio.h> 7 #include <unistd.h> 8 #include <dirent.h> 9 10 void do_ls(char[]); 11 12 int main(int argc, char *argv[]) { 13 if (argc == 1) { 14 do_ls("."); 15 } else { 16 while (--argc) { 17 printf("%s:\n", *++argv); 18 do_ls(*argv); 19 } 20 } 21 } 22 23 /** 24 * list files in directory called dirname 25 */ 26 void do_ls(char dirname[]) { 27 DIR *dir_ptr; 28 struct dirent *direntp; 29 30 if ((dir_ptr = opendir(dirname)) == NULL) { 31 fprintf(stderr, "ls1: cannot open %s \n", dirname); 32 } else { 33 while ((direntp = readdir(dir_ptr)) != NULL) { 34 printf("%s\n", direntp->d_name); 35 } 36 closedir(dir_ptr); 37 } 38 }
上述版本还能做什么
排序,分栏,隐藏. ..目录,选项 -l等
编写ls -l
问题1:ls-l能做些什么
问题2:ls-l是如何工作的
man -k file | grep -i information
man -k file | grep status
实现ls -l
显示文件状态信息代码:显示模式,链接数,文件所有者,组,大小,最后修改时间,文件名
1 /** 2 * - use stat() to obtain and print file properties 3 * - some members are just numbers 4 */ 5 #include <stdio.h> 6 #include <unistd.h> 7 #include <sys/stat.h> 8 9 void show_stat_info(char *fname, struct stat *buf); 10 11 int main(int argc, char *argv[]) { 12 struct stat info; 13 14 if (argc > 1) { 15 if (stat(argv[1], &info) != -1) { 16 show_stat_info(argv[1], &info); 17 return 0; 18 } 19 } else { 20 perror(argv[1]); 21 } 22 return 1; 23 } 24 25 /** 26 * display some info from stat in a name = value format 27 */ 28 void show_stat_info(char *fname, struct stat *buf) { 29 printf(" mode: %o\n", buf->st_mode); 30 printf(" links: %lu\n", buf->st_nlink); 31 printf(" user: %d\n", buf->st_uid); 32 printf(" group: %d\n", buf->st_gid); 33 printf(" size: %ld\n", buf->st_size); 34 printf(" modtime: %ld\n", buf->st_mtime); 35 printf(" name: %s\n", fname); 36 }
编译运行结果如下
cc -o fileinfo fileinfo.c
@localhost]$ ./fileinfo fileinfo.c
mode: 100644
links: 1
user: 1000
group: 1000
size: 1152
modtime: 1029894340
将模式字段转化成字符,将用户/组ID转换为字符串
1 /** 2 * purpose list contents of directory or directories 3 * action if no args, user . else list files args 4 * note: users stat and pwd.h and grp.h 5 * BUG: try ls2 /tmp 6 */ 7 8 #include <stdio.h> 9 #include <unistd.h> 10 #include <dirent.h> 11 #include <sys/stat.h> 12 #include <string.h> 13 #include <time.h> 14 15 void do_ls(char[]); 16 void do_stat(char *); 17 void show_file_info(char *, struct stat *); 18 void mode_to_letters(int, char []); 19 char * uid_to_name(uid_t); 20 char * gid_to_name(gid_t); 21 22 int main(int argc, char *argv[]) { 23 if (argc == 1) { 24 do_ls("."); 25 } else { 26 while (--argc) { 27 printf("%s:\n", *++argv); 28 do_ls(*argv); 29 } 30 } 31 } 32 33 /** 34 * list files in directory called dirname 35 */ 36 void do_ls(char dirname[]) { 37 DIR *dir_ptr; 38 struct dirent *direntp; 39 40 if ((dir_ptr = opendir(dirname)) == NULL) { 41 fprintf(stderr, "ls2: cannot open %s \n", dirname); 42 } else { 43 while ((direntp = readdir(dir_ptr)) != NULL) { 44 do_stat(direntp->d_name); 45 } 46 closedir(dir_ptr); 47 } 48 } 49 50 void do_stat(char *filename) { 51 struct stat info; 52 if (stat(filename, &info) == -1) { 53 perror(filename); 54 } else { 55 show_file_info(filename, &info); 56 } 57 } 58 59 /** 60 * display the info about filename. 61 * the info is stored in struct at * info_p 62 */ 63 void show_file_info(char *filename, struct stat * info_p) { 64 char modestr[11]; 65 66 mode_to_letters(info_p->st_mode, modestr); 67 68 printf("%s", modestr); 69 printf("%4d ", (int)info_p->st_nlink); 70 printf("%-8s ", uid_to_name(info_p->st_uid)); 71 printf("%-8s ", gid_to_name(info_p->st_gid)); 72 printf("%8ld ", (long)info_p->st_size); 73 printf("%.12s ", 4 + ctime(&info_p->st_mtime)); 74 printf("%s\n", filename); 75 } 76 77 /** 78 * utility functions 79 */ 80 void mode_to_letters(int mode, char str[]) { 81 strcpy(str, "----------"); 82 if (S_ISDIR(mode)) str[0] = 'd'; 83 if (S_ISCHR(mode)) str[0] = 'c'; 84 if (S_ISBLK(mode)) str[0] = 'b'; 85 86 if (mode & S_IRUSR) str[1] = 'r'; // 3 bits for user 87 if (mode & S_IWUSR) str[2] = 'w'; 88 if (mode & S_IXUSR) str[3] = 'x'; 89 90 if (mode & S_IRGRP) str[4] = 'r'; // 3 bits for group 91 if (mode & S_IWGRP) str[5] = 'w'; 92 if (mode & S_IXGRP) str[6] = 'x'; 93 94 if (mode & S_IROTH) str[7] = 'r'; // 3 bits for other 95 if (mode & S_IWOTH) str[8] = 'w'; 96 if (mode & S_IXOTH) str[9] = 'x'; 97 } 98 99 100 #include <pwd.h> 101 102 /** 103 * returns pointer to username associated with uid, uses getpw() 104 */ 105 char * uid_to_name(uid_t uid) { 106 struct passwd * getpwuid(), *pw_ptr; 107 static char numstr[10]; 108 109 if ((pw_ptr = getpwuid(uid)) == NULL) { 110 sprintf(numstr, "%d", uid); 111 return numstr; 112 } else { 113 return pw_ptr->pw_name; 114 } 115 } 116 117 #include <grp.h> 118 119 /** 120 * returns pointer to group number gid, used getgrgid(3) 121 */ 122 char * gid_to_name(gid_t gid) { 123 struct group * grp_ptr; 124 static char numstr[10]; 125 if ((grp_ptr = getgrgid(gid)) == NULL) { 126 sprintf(numstr, "%d", gid); 127 return numstr; 128 } else { 129 return grp_ptr->gr_name; 130 } 131 }
小结:
- ls命令:可以列出给定目录的内容;显示给定文件的属性信息。ls的使用方法,各个常用命令行选项。理解ls包括三个方面:
如何分辨给定的目录还是文件,如何列出目录信息,如何读取并显示文件属性
- 文件树:Unix下磁盘上的文件和目录被组织成一颗目录树,每个节点是目录或者是文件。
- ls命令的工作方式与who命令相似,只是who是打开文件、读取信息指导末尾,而ls是打开目录,读取信息直到目录末尾。
- 目录是什么?目录如utmp一样是一个特殊文件,其内容的每一项记录是文件和目录的名字(其实这一项中还包含其他很多有用的信息),且目录文件永远不为空,因为每个目录中包含有“.”“..”两项,分别表示当前目录和上一级目录。
- 既然ls命令的工作原理同who相似,那么也有一套与之相应的函数:opendir、readdir、closedir、seekdir、telldir、rewinddir。这些函数与文件操作的函数功能类似。
- readdir读取的便是目录文件中的记录,返回的是指向当前记录的指针。记录的类型是struct direent,这个结构体定义在/usr/include/dirent.h中。