- 浏览: 114248 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
zhang131ping:
3) 下载引导文件:
Intel:http://www.med ...
如何在win7系统上装苹果雪豹操作系统 -
cncfophp:
function array_max_value($arra ...
实现对数组值为整型的一维数组的排序 -
cncfophp:
function array_max_value($array ...
实现对数组值为整型的一维数组的排序 -
cncfophp:
这代码写的。。。
实现对数组值为整型的一维数组的排序
本文章中的示例代码是在 CentOS 5.4 64 位环境下运行通过的,在其它 unix 系统上没有测试过。
Linux 操作系统中的命令实际上是编译好的可执行程序,比如说 ls 这个命令,这个文件位于 /bin 目录下面,当我们用 file /bin/ls 命令查看的时候会有以下输出:
这个命令通过调用 stat 系统调用和 /usr/share/file/magic.mgc 文件来决定文件的类型。如上的 /bin/ls 是一个 ELF 格式的动态链接的 64 位的可执行文件。
系统调用是用户程序和操作系统内核之间的接口,我们可以使用操作系统提供的系统调用来请求分配资源和服务。我们可以通过 man 2 章节来查找 Linux 提供的系统调用的具体使用方法。有关文件操作的常见系统调用命令有:open、creat、close、read、write、lseek、opendir、readdir、mkdir、stat 等等。
cp 命令的实现
cp 命令的模拟实现
大家也都知道 cp 这个命令主要的作用就是把一个文件从一个位置复制到另一个位置。比如现在 /root 目录下有一个 test.txt 文件,如果我们用 cp test.txt test2.txt 命令的话,在同一个目录下面就会生成一个同样内容的 test2.txt 文件了。
那么 cp 命令是怎么实现的呢,我们看如下代码:
清单 1. cp 命令实现代码
该程序的主要实现思想是:打开一个输入文件,创建一个输出文件,建立一个 BUFFERSIZE 大小的缓冲区;然后在判断输入文件未完的循环中,每次读入多少就向输出文件中写入多少,直到输入文件结束。
cp 命令实现的说明
让我来详细的讲述一下这个程序:
开头四行包含了 4 个头文件,<stdio.h> 文件包含了 fprintf、perror 的函数原型定义;<unistd.h> 文件包含了 read、write 的函数原型定义;<fcntl.h> 文件包含了 open、creat 的函数原型定义、<stdlib.h> 文件包含了 exit 的函数原型定义。这些函数原型有些是系统调用、有些是库函数,通常都可以在 /usr/include 目录中找到这些头文件。
接下来的 2 行以宏定义的方式定义了 2 个常量。BUFFERSIZE 用来表示缓冲区的大小、COPYMODE 用来定义创建文件的权限。
接下来的一行定义了一个函数原型 oops,该函数的具体定义在最后出现,用来输出出错信息到 stderr,也就是标准错误输出的文件流。
接下来主程序开始。首先定义了 2 个文件描述符、一个存放读出字节数的变量 n_chars、和一个 BUFFERSIZE 大小的字符数组用来作为拷贝文件的缓冲区。
接下来判断输入参数的个数是否为 3,也就是程序名 argv[0]、拷贝源文件 argv[1]、目标文件 argv[2]。不为 3 的话就输出错误信息到 stderr,然后退出程序。
接下来的 2 行,用 open 系统调用以 O_RDONLY 只读模式打开拷贝源文件,如果打开失败就输出错误信息并退出。如果想了解文件打开模式的详细内容请使用命令 man 2 open,来查看帮助文档。
接下来的 2 行,用 creat 系统调用以 COPYMODE 的权限建立一个文件,如果建立失败函数的返回值为 -1 的话,就输出错误信息并退出。
接下来的循环是拷贝的主要过程。它从输入文件描述符 in_fd 中,读入 BUFFERSIZE 字节的数据,存放到 buf 字符数组中。在正常读入的情况下,read 函数返回实际读入的字节数,也就是说只要没有异常情况和文件没有读到结尾,那么 n_chars 中存放的就是实际读出的字节的数字。然后 write 函数将从 buf 缓冲区中,读出 n_chars 个字符,写入 in_out 输出文件描述符。由于 write 系统调用返回的是实际写入成功的字节数。所以当读出 N 个字符,又成功写入 N 个字符到输出文件描述符中去的时候,就表示写成功了,否则就报告写入错误。
最后就是用 close 系统调用关闭打开的输入和输出文件描述符。
回页首
rm 命令的实现
rm 命令的模拟实现
rm 命令主要是用来删除一个文件。
该命令的实现代码如下:
清单 2. rm 命令代码
其中程序的关键是 unlink 系统调用,unlink 函数原型包含在 <unistd.h> 头文件里面。
用 strace 来跟踪命令
我们从这个程序的创建过程来分析这个程序。
这个命令的模拟程序是怎么写出来的呢?
首先,我们可以在机器上 touch test 建立一个 test 文件,然后调用 strace rm test 命令来查看 rm 命令具体使用了那些系统调用。
通过查看,我们看到主要使用的系统调用如下:
我们可以看到起主要作用的就是 unlink(“test”) 这个系统调用。
让我们来分析一下这些输出的含义:
首先第一行 execve 系统调用。该系统调用执行参数“/bin/rm”中的程序(以 #! 开头的可执行脚本也可以),后面第一个方括号中表示执行的参数,第二个方括号中表示执行的环境变量。
接下来的 brk 和 mmap 命令,主要是用来给可执行命令分配内存空间。
后面的 lstat 系统调用用来确定文件的 mode 信息,包括文件的类型和权限,文件大小等等。
然后 access 系统调用检查当前用户进程对于 test 文件的写入访问权限。这里返回值为 0 也就是说进程对于 test 文件有写入的权限。
最后调用 unlink 系统调用删除文件。
这里如果我们建立一个目录 test1,然后用 rm test1 去删除这个目录会有什么结果呢?
我们看到有如下输出:
rm: cannot remove `test1': Is a directory
这时我们用 strace 命令来追踪一下,发现输出主要是如下不同。
unlink("test") = -1 EISDIR (Is a directory)
这里说明了删除不掉的原因是 unlink 系统调用报错,unlink 它认为 test 是一个目录,不予处理。
那么怎么删除一个目录呢?应该是用 rmdir 系统调用,这样就不会出现上述的问题了。
回页首
mkdir 命令的实现
mkdir 命令的模拟实现
再让我们来看看 mkdir 的实现。
完整的代码如下:
清单 3. mkdir 实现代码
这段代码也比较简单,我这里就不逐行解释了,主要说以下几点:
首先 mkdir 函数是定义于 <sys/stat.h> 和 <sys/types.h> 头文件之中的。
而 fprintf 函数是位于 <stdio.h> 文件之中的。
mkdir 的函数原型如下:
int mkdir(const char *pathname, mode_t mode);
mode 声明为 mode_t 类型。
那么 mode_t 数据类型是什么数据类型,应该从哪个文件去查看它的定义呢?
mode_t 数据类型究竟是什么类型
让我们逐步查找一下。
首先从文件 /usr/include/sys/stat.h 中找到 mode_t 类型
/usr/include/sys/stat.h -> typedef __mode_t mode_t;
说明 mode_t 只是对 __mode_t 的一种定义。
然后从 /usr/include/bits/types.h 中找到 __mode_t 类型
/usr/include/bits/types.h -> __STD_TYPE __MODE_T_TYPE __mode_t;
说明 __mode_t 也只是对 __MODE_T_TYPE 的一种定义。
/usr/include/bits/typesizes.h -> #define __MODE_T_TYPE __U32_TYPE
说明 __MODE_T_TYPE 是对 __U32_TYPE 的一种定义。
/usr/include/bits/types.h -> #define __U32_TYPE unsigned int
最后 __U32_TYPE 是一种无符号的整数的定义。
从上述推导可以看出,mode_t 实际上也就是一种无符号整数。
另外如下结构 struct stat 定义中的 st_mode 成员变量也是使用的 mode_t 类型的变量。
从 man 2 stat 中可以找到结构 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 filesystem I/O */
blkcnt_t st_blocks; /* number of 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 */
};
该结构也是我们在后面的 tac 命令实现中需要用到的结构体。我们需要用到结构体中的 st_size 成员,该成员反映了被读取的文件描述符对应的文件的大小。
回页首
tac 命令的实现
tac 命令的模拟实现
tac 命令主要用来以倒序的方式显示一个文本文件的内容,也就是先显示最后一行的内容,最后显示第一行的内容。代码如下:
清单 4. tac 命令实现代码
让我们来运行一下该程序:
程序的运行情况如下,假设编译后的可执行文件名为 emulatetac,有一个文本文件 test.txt。
# gcc emulatetac.c -o emulatetac
# cat test.txt
1
2
3
a
b
# ./emulatetac test.txt
b
a
3
2
1
可以看出文件内容以倒序方式显示输出了。
tac 命令实现的说明
下面逐行讲解:
#include 的头文件,都应该通过 man 2 系统调用命令来查找,这里就不多说了。
下面定义了一个宏常量 SIZE,该常量主要用来表示能够读入最大多少个字节的文件,当文件过大的时候程序就不执行,直接退出。然后定义了宏常量 NLINE 表示换行符'\n'。
接下来主程序体开始了:首先定义一个字符数组 buf,用来把读入文件的每个字节都存在该数组里面。
然后定义了 4 个字符串指针,一个指向结构体 struct stat 的指针 fp,一个文件描述符。
然后为指向结构体的指针 fp 分配存储空间。
接下来判断输入参数是否为 2 个,也就是命令本身和文件名。不是 2 个就直接退出。
然后以只读方式打开输入文件名的文件,也就是 test.txt。打开成功的话,把打开的文件赋值到文件描述符 fd 中,错误的话退出。
然后用 fstat 系统调用把文件描述符 fd 中对应文件的元信息,存放到结构体指针 fp 指向的结构中。
下面判断当文件的大小超过缓冲区数组 buf 的大小 SIZE-1 时,就退出。
下面将把文件 test.txt 中的每个字符存放到数组 buf 中。
下面是程序的核心部分:首先我们找到字符串 buf 中的第一个换行字符存放到 p1 指针里面,然后把最后一个换行字符置为字符串结束符。
接下来我们从后往前查找字符串 buf 中的换行符,直到遇到第一个换行符 p1。同时打印每个找到的换行符'\n'中的下一个字符开始的字符串,也就刚好是一行文本。
最后当从后向前找到第一个换行字符时,打印第一行,程序结束。
回页首
df 命令的实现
df 命令的模拟实现
通过 strace 命令查看 df 主要使用了如下的系统调用:open、fstat、read、statfs
我这里实际上是模拟实现的 df --block-size=4096 这个命令,也就是说以 4096 字节为块大小来显示磁盘使用情况。
这里最为关键的是 statfs 这个结构体,该结构体的某些字段被用作 df 命令的输出字段:
struct statfs {
long f_type; /* type of filesystem (see below) */
long f_bsize; /* optimal transfer block size */
long f_blocks; /* total data blocks in file system */
long f_bfree; /* free blocks in fs */
long f_bavail; /* free blocks avail to non-superuser */
long f_files; /* total file nodes in file system */
long f_ffree; /* free file nodes in fs */
fsid_t f_fsid; /* file system id */
long f_namelen; /* maximum length of filenames */
};
比如:df --block-size=4096 的输出如下(纵向列出):
Filesystem
/dev/sda1
4K-blocks
5077005 f_blocks 字段
Used
145105 f_blocks 字段 -f_bfree 字段
Available
4669841 f_bavail 字段
Use%
4% (f_blocks-f_bfree)/ f_blocks*100% 来计算磁盘使用率。
Mounted on
/
模拟实现的代码如下:
清单 5. 模拟实现代码
df 命令实现的说明
下面解释一下这个程序:
首先,该程序定义了一个函数 displayapartition, 这里先定义它的函数原型。
然后我们从主程序说起:首先定义了一个 char tmpline[SIZE1] 数组,该数组用来存放从宏定义常量 FN 代表的文件中,打开后存入文件的每行记录。
接着定义了一个文件流指针和 3 个字符串指针。
接下来打开文件 FN 并把结果赋值给文件流变量 fp, 如果打开失败就退出。
下面从打开的文件流中读出 SIZE1 个字符到临时数组 tmpline。比如读出一行数据为:/dev/sda1 / ext3 rw 0 0 将把 /dev/sda1 放入数组 tmpline,把加载点 / 放入指针 pt2,同时判断字符串 tmpline 是否包含 /dev 字符串,这样来判断是否是一个磁盘文件,如果是的话就调用子函数 displayapartition,不是则返回。
子函数 displayapartition 是做什么的呢?该函数接受 2 个参数,一个是行 /dev/sda1 / ext3 rw 0 0 中的第一列比如:/dev/sda1 也就是实际磁盘作为 pt 指针,一个是行中的第二列比如:/ 也就是挂载点作为 pt1 指针。然后子函数通过 pt1 指针,读取挂载上的文件系统信息到 buf 数据结构里面。
根据开头介绍过的 statfs 结构体,buf.f_blocks 表示打开的文件系统的总数据块,buf.f_blocks-buf.f_bfree 表示已经使用的数据块,buf.f_bavail 表示非超级用户可用的剩余数据块,磁盘使用率就是前面列出过的计算表达式:(f_blocks- f_bfree)/ f_blocks*100%。通过子函数就可以打印出 df 需要显示的所有信息到标准输出了。
Linux 操作系统中的命令实际上是编译好的可执行程序,比如说 ls 这个命令,这个文件位于 /bin 目录下面,当我们用 file /bin/ls 命令查看的时候会有以下输出:
[root@localhost ~]# file /bin/ls /bin/ls: ELF 64-bit LSB executable, AMD x86-64, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped
这个命令通过调用 stat 系统调用和 /usr/share/file/magic.mgc 文件来决定文件的类型。如上的 /bin/ls 是一个 ELF 格式的动态链接的 64 位的可执行文件。
系统调用是用户程序和操作系统内核之间的接口,我们可以使用操作系统提供的系统调用来请求分配资源和服务。我们可以通过 man 2 章节来查找 Linux 提供的系统调用的具体使用方法。有关文件操作的常见系统调用命令有:open、creat、close、read、write、lseek、opendir、readdir、mkdir、stat 等等。
cp 命令的实现
cp 命令的模拟实现
大家也都知道 cp 这个命令主要的作用就是把一个文件从一个位置复制到另一个位置。比如现在 /root 目录下有一个 test.txt 文件,如果我们用 cp test.txt test2.txt 命令的话,在同一个目录下面就会生成一个同样内容的 test2.txt 文件了。
那么 cp 命令是怎么实现的呢,我们看如下代码:
清单 1. cp 命令实现代码
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdlib.h> #define BUFFERSIZE 4096 #define COPYMODE 0644 void oops(char *, char *); main(int argc, char * argv[]) { int in_fd, out_fd, n_chars; char buf[BUFFERSIZE]; if ( argc != 3 ){ fprintf( stderr, "usage: %s source destination\n", *argv); exit(1); } if ( (in_fd=open(argv[1], O_RDONLY)) == -1 ){ oops("Cannot open ", argv[1]); } if ( (out_fd=creat( argv[2], COPYMODE)) == -1 ){ oops( "Cannot creat", argv[2]); } while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 ){ if ( write( out_fd, buf, n_chars ) != n_chars ){ oops("Write error to ", argv[2]); } } if ( n_chars == -1 ){ oops("Read error from ", argv[1]); } if ( close(in_fd) == -1 || close(out_fd) == -1 ) oops("Error closing files",""); } void oops(char *s1, char *s2) { fprintf(stderr,"Error: %s ", s1); perror(s2); exit(1); }
该程序的主要实现思想是:打开一个输入文件,创建一个输出文件,建立一个 BUFFERSIZE 大小的缓冲区;然后在判断输入文件未完的循环中,每次读入多少就向输出文件中写入多少,直到输入文件结束。
cp 命令实现的说明
让我来详细的讲述一下这个程序:
开头四行包含了 4 个头文件,<stdio.h> 文件包含了 fprintf、perror 的函数原型定义;<unistd.h> 文件包含了 read、write 的函数原型定义;<fcntl.h> 文件包含了 open、creat 的函数原型定义、<stdlib.h> 文件包含了 exit 的函数原型定义。这些函数原型有些是系统调用、有些是库函数,通常都可以在 /usr/include 目录中找到这些头文件。
接下来的 2 行以宏定义的方式定义了 2 个常量。BUFFERSIZE 用来表示缓冲区的大小、COPYMODE 用来定义创建文件的权限。
接下来的一行定义了一个函数原型 oops,该函数的具体定义在最后出现,用来输出出错信息到 stderr,也就是标准错误输出的文件流。
接下来主程序开始。首先定义了 2 个文件描述符、一个存放读出字节数的变量 n_chars、和一个 BUFFERSIZE 大小的字符数组用来作为拷贝文件的缓冲区。
接下来判断输入参数的个数是否为 3,也就是程序名 argv[0]、拷贝源文件 argv[1]、目标文件 argv[2]。不为 3 的话就输出错误信息到 stderr,然后退出程序。
接下来的 2 行,用 open 系统调用以 O_RDONLY 只读模式打开拷贝源文件,如果打开失败就输出错误信息并退出。如果想了解文件打开模式的详细内容请使用命令 man 2 open,来查看帮助文档。
接下来的 2 行,用 creat 系统调用以 COPYMODE 的权限建立一个文件,如果建立失败函数的返回值为 -1 的话,就输出错误信息并退出。
接下来的循环是拷贝的主要过程。它从输入文件描述符 in_fd 中,读入 BUFFERSIZE 字节的数据,存放到 buf 字符数组中。在正常读入的情况下,read 函数返回实际读入的字节数,也就是说只要没有异常情况和文件没有读到结尾,那么 n_chars 中存放的就是实际读出的字节的数字。然后 write 函数将从 buf 缓冲区中,读出 n_chars 个字符,写入 in_out 输出文件描述符。由于 write 系统调用返回的是实际写入成功的字节数。所以当读出 N 个字符,又成功写入 N 个字符到输出文件描述符中去的时候,就表示写成功了,否则就报告写入错误。
最后就是用 close 系统调用关闭打开的输入和输出文件描述符。
回页首
rm 命令的实现
rm 命令的模拟实现
rm 命令主要是用来删除一个文件。
该命令的实现代码如下:
清单 2. rm 命令代码
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc , char * argv[]) { int rt; if(argc != 2){ exit(2); }else{ if((rt = unlink(argv[1])) != 0){ fprintf(stderr,"error."); exit(3); } } return 0; }
其中程序的关键是 unlink 系统调用,unlink 函数原型包含在 <unistd.h> 头文件里面。
用 strace 来跟踪命令
我们从这个程序的创建过程来分析这个程序。
这个命令的模拟程序是怎么写出来的呢?
首先,我们可以在机器上 touch test 建立一个 test 文件,然后调用 strace rm test 命令来查看 rm 命令具体使用了那些系统调用。
通过查看,我们看到主要使用的系统调用如下:
[root@localhost aa]# strace rm test execve("/bin/rm", ["rm", "test"], [/* 24 vars */]) = 0 brk(0) = 0xcc66000 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2aff83ffb000 uname({sys="Linux", node="localhost.localdomain", ...}) = 0 ... ... lstat("test", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 stat("test", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 geteuid() = 0 getegid() = 0 getuid() = 0 getgid() = 0 access("test", W_OK) = 0 unlink("test") = 0 close(1) = 0 exit_group(0) = ?
我们可以看到起主要作用的就是 unlink(“test”) 这个系统调用。
让我们来分析一下这些输出的含义:
首先第一行 execve 系统调用。该系统调用执行参数“/bin/rm”中的程序(以 #! 开头的可执行脚本也可以),后面第一个方括号中表示执行的参数,第二个方括号中表示执行的环境变量。
接下来的 brk 和 mmap 命令,主要是用来给可执行命令分配内存空间。
后面的 lstat 系统调用用来确定文件的 mode 信息,包括文件的类型和权限,文件大小等等。
然后 access 系统调用检查当前用户进程对于 test 文件的写入访问权限。这里返回值为 0 也就是说进程对于 test 文件有写入的权限。
最后调用 unlink 系统调用删除文件。
这里如果我们建立一个目录 test1,然后用 rm test1 去删除这个目录会有什么结果呢?
我们看到有如下输出:
rm: cannot remove `test1': Is a directory
这时我们用 strace 命令来追踪一下,发现输出主要是如下不同。
unlink("test") = -1 EISDIR (Is a directory)
这里说明了删除不掉的原因是 unlink 系统调用报错,unlink 它认为 test 是一个目录,不予处理。
那么怎么删除一个目录呢?应该是用 rmdir 系统调用,这样就不会出现上述的问题了。
回页首
mkdir 命令的实现
mkdir 命令的模拟实现
再让我们来看看 mkdir 的实现。
完整的代码如下:
清单 3. mkdir 实现代码
#include <sys/stat.h> #include <sys/types.h> #include <stdio.h> int main(int argc, char *argv[]){ int rt; if( (rt = mkdir (argv[1],10705)) == -1 ){ fprintf(stderr,"cannot mkdir"); } return 0; }
这段代码也比较简单,我这里就不逐行解释了,主要说以下几点:
首先 mkdir 函数是定义于 <sys/stat.h> 和 <sys/types.h> 头文件之中的。
而 fprintf 函数是位于 <stdio.h> 文件之中的。
mkdir 的函数原型如下:
int mkdir(const char *pathname, mode_t mode);
mode 声明为 mode_t 类型。
那么 mode_t 数据类型是什么数据类型,应该从哪个文件去查看它的定义呢?
mode_t 数据类型究竟是什么类型
让我们逐步查找一下。
首先从文件 /usr/include/sys/stat.h 中找到 mode_t 类型
/usr/include/sys/stat.h -> typedef __mode_t mode_t;
说明 mode_t 只是对 __mode_t 的一种定义。
然后从 /usr/include/bits/types.h 中找到 __mode_t 类型
/usr/include/bits/types.h -> __STD_TYPE __MODE_T_TYPE __mode_t;
说明 __mode_t 也只是对 __MODE_T_TYPE 的一种定义。
/usr/include/bits/typesizes.h -> #define __MODE_T_TYPE __U32_TYPE
说明 __MODE_T_TYPE 是对 __U32_TYPE 的一种定义。
/usr/include/bits/types.h -> #define __U32_TYPE unsigned int
最后 __U32_TYPE 是一种无符号的整数的定义。
从上述推导可以看出,mode_t 实际上也就是一种无符号整数。
另外如下结构 struct stat 定义中的 st_mode 成员变量也是使用的 mode_t 类型的变量。
从 man 2 stat 中可以找到结构 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 filesystem I/O */
blkcnt_t st_blocks; /* number of 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 */
};
该结构也是我们在后面的 tac 命令实现中需要用到的结构体。我们需要用到结构体中的 st_size 成员,该成员反映了被读取的文件描述符对应的文件的大小。
回页首
tac 命令的实现
tac 命令的模拟实现
tac 命令主要用来以倒序的方式显示一个文本文件的内容,也就是先显示最后一行的内容,最后显示第一行的内容。代码如下:
清单 4. tac 命令实现代码
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #define SIZE 1000001 #define NLINE '\n' int main(int argc , char *argv[]){ char buf[SIZE]; char *p1,*p2,*p3,*p4; struct stat *fp; int fd; fp=(struct stat *)malloc(sizeof(struct stat)); if(argc != 2){ fprintf(stderr,"input error %s \n"); exit(1); } if( (fd=open(argv[1],O_RDONLY)) == -1 ){ fprintf(stderr,"open error %s \n",strerror(errno)); exit(1); } if(fstat(fd,fp)== -1){ fprintf(stderr,"fstat error %s \n",strerror(errno)); exit(2); } if(fp->st_size > (SIZE-1)){ fprintf(stderr,"buffer size is not big enough \n"); exit(3); } if(read(fd,buf,fp->st_size) == -1){ fprintf(stderr,"read error.\n"); exit(4); } p1=strchr(buf,NLINE); p2=strrchr(buf,NLINE); *p2='\0'; do{ p2=strrchr(buf,NLINE); p4=p2; p3=p2+sizeof(char); printf("%s\n",p3); *p4='\0'; }while(p2 != p1); if(p2 == p1){ *p2 = '\0'; printf("%s\n",buf); } return 0; }
让我们来运行一下该程序:
程序的运行情况如下,假设编译后的可执行文件名为 emulatetac,有一个文本文件 test.txt。
# gcc emulatetac.c -o emulatetac
# cat test.txt
1
2
3
a
b
# ./emulatetac test.txt
b
a
3
2
1
可以看出文件内容以倒序方式显示输出了。
tac 命令实现的说明
下面逐行讲解:
#include 的头文件,都应该通过 man 2 系统调用命令来查找,这里就不多说了。
下面定义了一个宏常量 SIZE,该常量主要用来表示能够读入最大多少个字节的文件,当文件过大的时候程序就不执行,直接退出。然后定义了宏常量 NLINE 表示换行符'\n'。
接下来主程序体开始了:首先定义一个字符数组 buf,用来把读入文件的每个字节都存在该数组里面。
然后定义了 4 个字符串指针,一个指向结构体 struct stat 的指针 fp,一个文件描述符。
然后为指向结构体的指针 fp 分配存储空间。
接下来判断输入参数是否为 2 个,也就是命令本身和文件名。不是 2 个就直接退出。
然后以只读方式打开输入文件名的文件,也就是 test.txt。打开成功的话,把打开的文件赋值到文件描述符 fd 中,错误的话退出。
然后用 fstat 系统调用把文件描述符 fd 中对应文件的元信息,存放到结构体指针 fp 指向的结构中。
下面判断当文件的大小超过缓冲区数组 buf 的大小 SIZE-1 时,就退出。
下面将把文件 test.txt 中的每个字符存放到数组 buf 中。
下面是程序的核心部分:首先我们找到字符串 buf 中的第一个换行字符存放到 p1 指针里面,然后把最后一个换行字符置为字符串结束符。
接下来我们从后往前查找字符串 buf 中的换行符,直到遇到第一个换行符 p1。同时打印每个找到的换行符'\n'中的下一个字符开始的字符串,也就刚好是一行文本。
最后当从后向前找到第一个换行字符时,打印第一行,程序结束。
回页首
df 命令的实现
df 命令的模拟实现
通过 strace 命令查看 df 主要使用了如下的系统调用:open、fstat、read、statfs
我这里实际上是模拟实现的 df --block-size=4096 这个命令,也就是说以 4096 字节为块大小来显示磁盘使用情况。
这里最为关键的是 statfs 这个结构体,该结构体的某些字段被用作 df 命令的输出字段:
struct statfs {
long f_type; /* type of filesystem (see below) */
long f_bsize; /* optimal transfer block size */
long f_blocks; /* total data blocks in file system */
long f_bfree; /* free blocks in fs */
long f_bavail; /* free blocks avail to non-superuser */
long f_files; /* total file nodes in file system */
long f_ffree; /* free file nodes in fs */
fsid_t f_fsid; /* file system id */
long f_namelen; /* maximum length of filenames */
};
比如:df --block-size=4096 的输出如下(纵向列出):
Filesystem
/dev/sda1
4K-blocks
5077005 f_blocks 字段
Used
145105 f_blocks 字段 -f_bfree 字段
Available
4669841 f_bavail 字段
Use%
4% (f_blocks-f_bfree)/ f_blocks*100% 来计算磁盘使用率。
Mounted on
/
模拟实现的代码如下:
清单 5. 模拟实现代码
#include <stdio.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <sys/vfs.h> #include <math.h> #define SIZE1 100 #define FN "/etc/mtab" #define SPACE ' ' int displayapartition(char * pt,char * pt1); int main(void){ char tmpline[SIZE1]; FILE * fp; char * pt1; char * pt2; char * pt3; if( (fp = fopen(FN,"r")) == NULL ){ fprintf(stderr,"%s \n",strerror(errno)); exit(5); } while( fgets(tmpline, SIZE1, fp) != NULL ){ pt1=strchr(tmpline, SPACE); pt2=pt1+sizeof(char); *pt1='\0'; pt3=strchr(pt2,SPACE); *pt3='\0'; if(strstr(tmpline,"/dev") != NULL ){ displayapartition(tmpline,pt2); } } return 0; } int displayapartition(char * pt,char * pt1){ struct statfs buf; statfs(pt1,&buf); int usage; usage=ceil((buf.f_blocks-buf.f_bfree)*100/buf.f_blocks); printf("%s ",pt); printf("%ld ",buf.f_blocks); printf("%ld ",buf.f_blocks-buf.f_bfree); printf("%ld ",buf.f_bavail); printf("%d%% ",usage); printf("%s ",pt1); printf("\n"); return 0; }
df 命令实现的说明
下面解释一下这个程序:
首先,该程序定义了一个函数 displayapartition, 这里先定义它的函数原型。
然后我们从主程序说起:首先定义了一个 char tmpline[SIZE1] 数组,该数组用来存放从宏定义常量 FN 代表的文件中,打开后存入文件的每行记录。
接着定义了一个文件流指针和 3 个字符串指针。
接下来打开文件 FN 并把结果赋值给文件流变量 fp, 如果打开失败就退出。
下面从打开的文件流中读出 SIZE1 个字符到临时数组 tmpline。比如读出一行数据为:/dev/sda1 / ext3 rw 0 0 将把 /dev/sda1 放入数组 tmpline,把加载点 / 放入指针 pt2,同时判断字符串 tmpline 是否包含 /dev 字符串,这样来判断是否是一个磁盘文件,如果是的话就调用子函数 displayapartition,不是则返回。
子函数 displayapartition 是做什么的呢?该函数接受 2 个参数,一个是行 /dev/sda1 / ext3 rw 0 0 中的第一列比如:/dev/sda1 也就是实际磁盘作为 pt 指针,一个是行中的第二列比如:/ 也就是挂载点作为 pt1 指针。然后子函数通过 pt1 指针,读取挂载上的文件系统信息到 buf 数据结构里面。
根据开头介绍过的 statfs 结构体,buf.f_blocks 表示打开的文件系统的总数据块,buf.f_blocks-buf.f_bfree 表示已经使用的数据块,buf.f_bavail 表示非超级用户可用的剩余数据块,磁盘使用率就是前面列出过的计算表达式:(f_blocks- f_bfree)/ f_blocks*100%。通过子函数就可以打印出 df 需要显示的所有信息到标准输出了。
发表评论
-
gtk+分栏列表实现例子
2011-05-30 18:24 1499此分栏列表会从指定文本文件中读取数据显示出来 文本文件中的内容 ... -
基于libevent的webserver的实现
2011-05-11 16:37 4615主要实现了静态文件访问、记录访问日志、文件目录列表 编译脚本: ... -
多进程插入mysql数据库
2011-05-09 11:29 1550如何编译? gcc -Wall -o a.out $(mysq ... -
str_replace的c语言实现
2011-05-05 17:19 4598//./str_replace_all "(ui ... -
tctdb的数据查询
2011-05-04 14:24 1099#include <tcutil.h> #i ... -
mysql数据库导入Tokyo Cabinet的table数据库
2011-05-04 12:12 968#include <stdlib.h> ... -
tokyotyrant的API
2011-05-03 15:29 3438#include <tcutil.h> #i ... -
mysql udf 的实现过程
2011-04-29 11:28 1492首先编写一个实现curl提交的udf,文件名起名为lib_my ... -
生成随机数并计算随机数分布情况
2011-03-09 13:02 875#include <stdio.h> # ... -
c语言连接mysql
2011-02-24 18:13 1001#include <mysql/mysql.h> ... -
c语言copy函数实现
2011-02-24 18:07 3602#include <stdio.h> #in ... -
c语言curl简单功能实现
2011-02-24 18:06 4606#include <stdio.h> #in ...
相关推荐
linux下大部分常用命令源码,偶正要开始学习-_- base64.c basename.c c99-to-c89.diff cat.c chcon.c chgrp.c chmod.c chown.c chown-core.c chown-core.h chroot.c cksum.c comm.c copy.c cp.c cp-hash.c csplit.c ...
自己学习《APUE》时写的linux下一些命令(大概40个左右)实现,仅当学习使用,这些命令包括cat cp echo head ls paste rmdir tail umask who chattr cut expand join mkdir pwd sed tee uniq chgrp date find last ...
ls,cp等源码包Coreutils GNU Coreutils包括如下命令: [ date false ln paste sha1sum test vdir ...cp expand join nl rm tac unlink csplit expr kill nohup rmdir tail uptime cut factor link od seq tee users
"Linux服务器常用命令" Linux服务器常用命令是Linux操作系统中最常用的命令,掌握这些命令对于Linux服务器的管理和维护至关重要。本电子书将详细介绍Linux服务器常用命令的定义、分类和使用方法。 一、目录操作...
创建目录使用“mkdir”,删除目录使用“rmdir”,若要删除非空目录则使用“rm -rf”。移动或重命名文件和目录,可以使用“mv”命令,复制文件和目录则使用“cp”。若要查看当前所在目录的完整路径,可以使用“pwd”...
7. df命令:显示文件系统磁盘空间的使用情况,显示已经挂载的分区列表。 8. htop命令:实时动态查看系统资源使用情况,比top更直观。 9. free命令:查看系统内存。 10. date命令:显示与设置系统时间。 11. cal命令...
`df` 命令用于显示文件系统的磁盘空间使用情况。 6. **du**: 显示文件和目录的磁盘使用情况。`du` 命令可以显示单个文件或整个目录的磁盘使用情况。 7. **free**: 显示内存使用情况。`free` 命令用于显示系统的...
在某些系统中可能需要使用 `mv` 来实现重命名功能。 **11. rm** - **命令**: `rm [选项] [文件或目录]` - **功能说明**: 删除一个或多个文件或目录。使用 `-r` 或 `--recursive` 选项递归删除目录。 **12. rmdir**...
`df` 命令可以用来查看系统中各个文件系统的磁盘使用情况。 8. **top** - **功能说明**:实时显示系统资源使用情况。`top` 命令可以动态显示系统中进程的状态,包括CPU使用率、内存使用情况等。 9. **free** - **...
2. tac_tac 是 cat 的反向拼写,因此命令的功能为反向显示文件内容。 3. more 分页显示文件内容。 4. less 分页显示文件内容,more 命令的相反用法。 5. head 显示文件内容的头部。 6. tail 显示文件内容的尾部。 7....
14. **uname**、**hostname**、**dmesg**、**uptime**、**stat**、**du**、**df**、**top**、**free**、**date** 和 **cal**:这些命令用于显示系统信息,如操作系统详情、主机名、系统运行时间和内存使用情况。...
### Linux基础命令详解 随着Linux系统的广泛应用,掌握其基本命令对于任何从事IT行业的人来说变得尤为重要。本文将基于“150个常用Linux命令”的内容,详细介绍其中的关键知识点,特别是那些对于初学者而言至关重要...
25. **df**:显示磁盘空间使用情况,包括总的、已用的和可用的空间。 26. **free**:报告当前系统的内存和交换空间使用情况。 27. **fdisk -L**:列出系统上的所有磁盘,是磁盘管理的起点。 28. **locale**:显示...
- **命令**: `df -h`。 - **输出**: 显示文件系统的磁盘空间使用情况。 - **应用场景**: 监控磁盘空间使用情况,确保有足够的空间供应用程序运行。 **8. `lsof` 详解** - **用途**: `lsof` (list open files) 命令...
### 常用Linux命令总结 #### 一、目录相关 **1. cd 切换目录 (changedirectory)** - `cd ..`:回到上一级目录 - `cd /path/to/directory`:切换到指定目录 **2. pwd 显示当前工作目录 (printworkingdirectory)**...
Linux运维必备命令 Linux运维是一个庞大的领域,需要熟悉大量的命令来完成各种任务。本文档总结了150个常用的Linux运维命令,涵盖了文件和目录操作、文件处理、信息显示、搜索文件、用户管理、网络操作、磁盘文件...
"150个常用的Linux命令汇总" Linux 命令是 Linux 操作系统的核心组件之一,它们提供了对系统的控制和管理能力。以下是 150 个常用的 Linux 命令汇总,涵盖了...这些命令可以帮助用户更好地管理和使用 Linux 系统。