特殊文件这一章讲了两种特殊文件,一种是符号链接一种是proc文件系统。/proc目录下的文件是系统运行进程的映像,它是一个伪文件系统,这些文件只存在于内存之中,不占用外部存储空间。
proc文件系统不是普通意义上的文件系统,它既是一个到运行进程地址空间的访问接口,同时又是一个访问内核数据的结构。用户和应用程序可以通过proc得到系统的信息和运行中的进程信息,这是两个主要的功能。能够访问系统内核的信息,同时也能修改内核的信息,达到配置系统的目的。
对于proc文件系统两个不同的功能,设计了两个不同的程序进行测试。
1、读取内核信息
内核信息包括很多,其中包括:
(1)CPU型号和类型
(2)使用的Linux内核版本
(3)从系统最后一次启动以来经历的时间
(4)总共有多少CPU时间执行在用户态、系统态和空闲态。
(5)配置内存大小
(6)当前可用内存数
(7)磁盘读写请求数
(8)内核已经进行多少次进程上下文切换
(9)从系统创建以来创建的进程数
(10)平均系统负载列表
(11)系统最后的启动时间
测试程序分为三个版本,STANDARD版本、SHORT版本和LONG版本。STANDARD版本输出(1)、(2)、(3);SHORT版本输出(4)、(7)、(8)、(9)、(11);LONG版本输出(5)、(6)、(10)。
int get_load_avg() { char line_buf[MAX]; int fd; int n; int res = -1; if((fd = open("/proc/loadavg", O_RDONLY)) == -1){ /* 打开存储系统平均负载的porc文件 */ perror("fail to loadavg"); return -1; } if((n = read(fd, line_buf, MAX)) == -1){ /* 读取系统平均负载的内容 */ perror("fail to read"); goto err; } line_buf[n] = '\0'; printf("Average Load: %s", line_buf); /* 输出系统的负载 */ res = 0; err: close(fd); /* 关闭文件 */ return res; } /* 在指定的proc文件中查找需要的内核信息,成功返回0,失败返回-1。 * path : 指定的proc文件的路径 * name : 所要查找的内核信息 */ int search(char *path, char *name) { int fd; char char_all[MAX] = "\0"; char line_buf[MAX] = "\0"; char *p; char *s; int n; int res = -1; if((fd = open(path ,O_RDONLY)) == -1){ /* 打开指定的proc文件 */ perror("fail to open"); return -1; } if((n = read(fd, char_all, MAX)) == -1){ /* 将文件内容读入缓冲区 */ perror("fail to read"); goto err; } char_all[n] = '\0'; p = strstr(char_all, name); /* 搜索name表示的内核信息,strstr函数查找字符串,返回该字符串的地址 */ s = strstr(p, "\n"); /* 这一行就是所需要的内核信息 */ n = s - p + 1; strncpy(line_buf, p, n); printf("%s\n",line_buf); /* 输出缓冲区的内容 */ res = 0; err: close(fd); return res; }
上边的代码中也可以看出来对于proc文件的操作就是用到文件操作的read函数、open函数和write函数等。上边自己写的代码中search函数是先将内容读到一个字符串中,然后根据寻找字符串中需要查找的字符串返回要查找字符串的首地址,从该首地址往后一直读,直到换行符号,这一段都是要读取的信息,这个对于字符串的操作是一个很好的例子,以后程序中可以借鉴。
主函代码如下:
#include "common.h" int main(int argc,char *argv[]) { char c1,c2; int interval; int duration; int intervation = 0; int reportType = 0; char repTypeName[16]; struct timeval now; reportType = STANDARD; /* 该程序默认使用STANDARD版本 */ strcpy(repTypeName,"Standard"); if(argc>1){ sscanf(argv[1],"%c%c",&c1,&c2); /* 得到该程序的命令行参数 */ if(c1 != '-'){ /* 命令行参数出错 */ printf("wrong command]\n"); exit(1); } if(c2 == 's'){ /* -s使用SHORT版本 */ reportType = SHORT; strcpy(repTypeName,"Short"); }else if(c2 == 'l'){ /* -l 使用LONG版本 */ reportType = LONG; strcpy(repTypeName,"Long"); interval = atoi(argv[2]); /* 得到两个参数,分别是取平均系统负载的时间间隔和总时长 */ duration = atoi(argv[3]); } } gettimeofday(&now,NULL); /* 得到当前的系统时间 */ /* 输出版本的类型,standard、short和long */ printf("Status report: %s\nat the time of : %s\n", TypeName,(char *)ctime(&(now.tv_sec))); /* 输出主机名,该信息存储在/proc/sys/kernel/hostname文件中 */ printf("the hostname is: "); search( "/proc/sys/kernel/hostname" , "" ); switch(reportType){ case 0: /* STANDARD版本 */ search( "/proc/cpuinfo" , "model name" ); /* CPU的类型和型号 */ search( "/proc/version" , "" ); /* 所使用的Linux内核的版本 */ break; case 1: /* SHORT版本 */ search( "/proc/stat" , "cpu" ); /* 总共有多少CPU时间执行在用户态、系统态和空闲态 */ search( "/proc/stat" , "intr" ); /* 磁盘读写请求数 */ search( "/proc/stat" , "ctxt" ); /* 内核已经进行了多少次进程上下文的切换 */ search( "/proc/stat" , "btime" ); /*系统最后的启动时间*/ search( "/proc/stat" , "processes" ); /* 从系统启动以来创建的进程数 */ break; case 2: /* LONG版本 */ search( "/proc/meminfo" , "MemTotal" ); /* 配置内存的大小 */ search( "/proc/meminfo" , "MemFree" ); /* 当前可用内存数 */ while(intervation < duration){ /* 取得平均系统负载 */ sleep(interval); if(get_load_avg() == -1) exit(1); intervation += interval; } break; default: printf("should not be here\n"); } return 0; }
主函数主要判断输入是否错误,不同输入对应不同的输出版本,这种对于外部输入的判断在以后程序中也会经常遇到,可以作为一种参考。
头文件common.h如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <fcntl.h> #include <unistd.h> #define MAX 4096 #define STANDARD 0 #define SHORT 1 #define LONG 2 extern int search(char *path,char *name); extern int get_load_avg();
2、读取进程状态
proc文件系统下有一个self的子目录,该目录中存储的就是当前正在执行的进程的状态信息,这些状态信息存储在数据结构中,proc文件系统提供了一个和这些信息交互的接口,如果需要知道当前正在运行的进程的信息,可以通过proc文件系统来获取,需要访问/proc/self/task/“进程号”/status文件可以得到这些信息。
下面是一个实例代码显示当前进程的信息:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #define MAX 1024 #define PID 6 #define PATH_SIZE 128 int main(void) { FILE *fp; pid_t pid; char pid_str[PID]; char path[PATH_SIZE]; char buf[MAX]; pid = getpid(); /* 得到进程ID */ sprintf(pid_str, "%d", pid); /* 将进程ID由数字转换为字符串 */ strcpy(path, "/proc/self/task/"); /* 拼接路径,打开"/proc/self/task/进程ID"目录下的status文件 */ strcat(path, pid_str); strcat(path, "/status"); fp = fopen(path, "r"); /* 打开该文件,以只读的方式打开 */ if(fp == NULL){ perror("fail to open"); exit(1); } while(fgets(buf, MAX, fp) != NULL) /* 顺序读取每一行,并且打印 */ printf("%s", buf); fclose(fp); /* 关闭文件 */ return 0; }
这个实例程序中对于文件的操作还是基本的文件操作,关键在于路径的拼接,先要获得进程ID,将进程ID转换成字符串,将进程ID字符串拼接到路径中,然后根据这个路径去打开文件,逐行打印。这也是一种对于字符串的操作,这种处理方法应该记下来,以后会用得到。