The files in /proc file system are useful to check the health of the system. So, many software monitors them with basic open/read/close operations. Let's see how we can do it more efficiently.
Let's consider the following example program which maps the content of /proc/meminfo
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define MEMINFO_FNAME "/proc/meminfo"
void *mmap_file(const char *name, size_t *sz)
{
int fd;
int rc;
size_t size_in_bytes, page_size, s;
void *mem;
// Open the file
fd = open(MEMINFO_FNAME, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "open(%s): '%m' (%d)\n", name, errno);
return NULL;
}
// Get the size of the file rounded in page size (fstat() would return 0 for the size)
s = (size_t)lseek(fd, SEEK_END, 0);
if ((off_t)s < 0 || !s) {
fprintf(stderr, "lseek(%s, %zu, %d): '%m' (%d)\n", name, s, fd, errno);
return NULL;
}
page_size = (size_t)sysconf(_SC_PAGESIZE);
size_in_bytes = (s / page_size);
size_in_bytes += (s % page_size ? 1 : 0);
size_in_bytes = size_in_bytes * page_size;
// Map the file in memory
mem = mmap(NULL, size_in_bytes, PROT_READ, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED) {
fprintf(stderr, "mmap(%s, %zu, %zu, %d): '%m' (%d)\n", name, size_in_bytes, page_size, fd, errno);
return NULL;
}
// File descriptor non longer needed
close(fd);
*sz = s;
return mem;
}
int main(void)
{
void *mem;
size_t sz;
char *str;
mem = mmap_file(MEMINFO_FNAME, &sz);
if (!mem) {
return 1;
}
// Null terminated string
str = (char *)malloc(sz + 1);
if (!str) {
fprintf(stderr, "malloc(%zu + 1): '%m' (%d)\n", sz, errno);
return 1;
}
while (1) {
memcpy(str, (char *)mem, sz);
str[sz] = '\0';
printf("%s", str);
sleep(1);
}
return 0;
}
|
The execution shows an error from mmap() because procfs does not accept this operation:
$ gcc meminfo.c -o meminfo $ ./meminfo mmap(/proc/meminfo, 4096, 4096, 3): 'Input/output error' (5) |
The following example program keeps the file opened and rewinds the read pointer thanks to the call to lseek():
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define MEMINFO_FNAME "/proc/meminfo"
int read_file(int fd)
{
int rc;
char buffer[512];
off_t off;
off = lseek(fd, 0, SEEK_SET);
if (off == (off_t)-1) {
fprintf(stderr, "lseek(%s): '%m' (%d)\n", MEMINFO_FNAME, errno);
return -1;
}
do {
rc = read(fd, buffer, sizeof(buffer));
if (rc > 0) {
buffer[rc] = '\0';
printf("%s", buffer);
}
} while(rc > 0);
return rc;
}
int main(void)
{
int fd;
int rc;
// Open the file
fd = open(MEMINFO_FNAME, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "open(%s): '%m' (%d)\n", MEMINFO_FNAME, errno);
return 1;
}
do {
printf("==========\n");
rc = read_file(fd);
sleep(1);
} while(rc == 0);
close(fd);
return 0;
}
|
The execution shows varying values after each reads in the file:
$ gcc meminfo.c -o meminfo $ ./meminfo ========== MemTotal: 131927796 kB MemFree: 9305304 kB MemAvailable: 125498348 kB Buffers: 53300112 kB [...] ========== MemTotal: 131927796 kB MemFree: 9323984 kB MemAvailable: 125517048 kB Buffers: 53300112 kB [...] ========== MemTotal: 131927796 kB MemFree: 9324236 kB MemAvailable: 125517300 kB Buffers: 53300120 kB [...] |
Running the preceding program with strace points out 3 calls to read() to get the content of the whole file:
$ strace ./meminfo openat(AT_FDCWD, "/proc/meminfo", O_RDONLY) = 3 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0 brk(NULL) = 0x559be3d21000 brk(0x559be3d42000) = 0x559be3d42000 write(1, "==========\n", 11========== ) = 11 lseek(3, 0, SEEK_SET) = 0 read(3, "MemTotal: 131927796 kB\nMem"..., 512) = 512 write(1, "MemTotal: 131927796 kB\nMem"..., 506MemTotal: 131927796 kB MemFree: 17196132 kB MemAvailable: 125515556 kB Buffers: 51164896 kB Cached: 1804120 kB SwapCached: 87472 kB Active: 43977780 kB Inactive: 10878860 kB Active(anon): 1299996 kB Inactive(anon): 641428 kB Active(file): 42677784 kB Inactive(file): 10237432 kB Unevictable: 576 kB Mlocked: 576 kB SwapTotal: 4194300 kB SwapFree: 74032 kB Dirty: 56 kB Writeback: 0 kB ) = 506 read(3, "ges: 1801660 kB\nMapped: "..., 512) = 512 write(1, "AnonPages: 1801660 kB\nMapp"..., 507AnonPages: 1801660 kB Mapped: 258984 kB Shmem: 59168 kB KReclaimable: 56462272 kB Slab: 59304320 kB SReclaimable: 56462272 kB SUnreclaim: 2842048 kB KernelStack: 29088 kB PageTables: 90196 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 70158196 kB Committed_AS: 15263204 kB VmallocTotal: 34359738367 kB VmallocUsed: 226536 kB VmallocChunk: 0 kB Percpu: 53184 kB ) = 507 read(3, "rupted: 0 kB\nAnonHugePages: "..., 512) = 453 write(1, "HardwareCorrupted: 0 kB\nAnon"..., 464HardwareCorrupted: 0 kB AnonHugePages: 0 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 0 kB FilePmdMapped: 0 kB CmaTotal: 0 kB CmaFree: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB DirectMap4k: 57466820 kB DirectMap2M: 76664832 kB DirectMap1G: 0 kB ) = 464 read(3, "", 512) = 0 nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffec60a68d0) = 0 write(1, "==========\n", 11========== [...] |
This is because of the size of the read buffer which is only 512 bytes. To auto-adapt the size of the read buffer to the size of the monitored file, at open time, the size could be obtained with a call to lseek() to move the pointer up to the end of the file. But a call like lseek(fd, 0, SEEK_END) would return an error as well as a call to stat() would return a size equal to 0 bytes. Depending on the content of the file, the size is not a fixed value. Its content is dynamically built upon the calls to read(). As a consequence, the new read procedure consists to allocate and adjust the read buffer to the last amount of read data:
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define MEMINFO_FNAME "/proc/meminfo"
int open_file(const char *fname, size_t *sz) {
int fd;
off_t off;
// Open the file
fd = open(fname, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "open(%s): '%m' (%d)\n", fname, errno);
return -1;
}
*sz = (size_t)off;
return fd;
}
int read_file(int fd, char **buffer, size_t *sz)
{
int rc;
size_t off;
char *ptr;
off_t offset;
size_t max;
#define BUFFER_INCREMENT 512
if (!(*buffer)) {
*buffer = (char *)malloc(BUFFER_INCREMENT);
if (!(*buffer)) {
fprintf(stderr, "malloc(%zu): '%m' (%d)\n", (size_t)BUFFER_INCREMENT, errno);
*sz = 0;
return 1;
}
*sz = BUFFER_INCREMENT;
}
// Rewind the read pointer
offset = lseek(fd, 0, SEEK_SET);
if (offset == (off_t)-1) {
fprintf(stderr, "lseek(%d): '%m' (%d)\n", fd, errno);
return -1;
}
off = 0;
do {
max = *sz - off - 1;
rc = read(fd, *buffer + off, max);
if (rc < 0) {
fprintf(stderr, "read(%d): '%m' (%d)\n", fd, errno);
return -1;
}
// Last read?
if (rc < max) {
(*buffer + off)[rc] = '\0';
return (int)(off + rc);
}
ptr = realloc(*buffer, *sz + BUFFER_INCREMENT);
if (!ptr) {
fprintf(stderr, "realloc(%p, %zu): '%m' (%d)\n", *buffer, (size_t)BUFFER_INCREMENT, errno);
break;
}
off += rc;
*sz += BUFFER_INCREMENT;
*buffer = ptr;
} while (rc > 0);
return -1;
}
int main(void)
{
int fd;
int rc;
size_t sz;
char *buffer = NULL;
// Open the file
fd = open_file(MEMINFO_FNAME, &sz);
if (fd < 0) {
fprintf(stderr, "open(%s): '%m' (%d)\n", MEMINFO_FNAME, errno);
return 1;
}
do {
printf("==========\n");
rc = read_file(fd, &buffer, &sz);
if (rc > 0) {
printf("%s", buffer);
sleep(1);
}
} while(rc > 0);
if (buffer) {
free(buffer);
}
close(fd);
return 0;
}
|
At the first display, there are several read() system calls in order to adjust the size of the read buffer. Then, the subsequent calls invoke read() only once:
$ strace ./meminfo [...] openat(AT_FDCWD, "/proc/meminfo", O_RDONLY) = 3 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0 brk(NULL) = 0x55656c585000 brk(0x55656c5a6000) = 0x55656c5a6000 write(1, "==========\n", 11========== ) = 11 lseek(3, 0, SEEK_SET) = 0 read(3, "MemTotal: 131927796 kB\nMem"..., 511) = 511 read(3, "ages: 1877528 kB\nMapped: "..., 512) = 512 read(3, "rrupted: 0 kB\nAnonHugePages:"..., 512) = 454 write(1, "MemTotal: 131927796 kB\nMem"..., 1024MemTotal: 131927796 kB MemFree: 19769048 kB MemAvailable: 125432008 kB Buffers: 50652156 kB Cached: 1641168 kB SwapCached: 87460 kB Active: 43863936 kB Inactive: 10393316 kB Active(anon): 1375796 kB Inactive(anon): 643528 kB Active(file): 42488140 kB Inactive(file): 9749788 kB Unevictable: 576 kB Mlocked: 576 kB SwapTotal: 4194300 kB SwapFree: 74268 kB Dirty: 104 kB Writeback: 0 kB AnonPages: 1877528 kB Mapped: 255816 kB Shmem: 61260 kB KReclaimable: 54483096 kB Slab: 57327396 kB SReclaimable: 54483096 kB SUnreclaim: 2844300 kB KernelStack: 29040 kB PageTables: 91048 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 70158196 kB Committed_AS: 15320692 kB VmallocTotal: 34359738367 kB VmallocUsed: 226424 kB VmallocChunk: 0 kB Percpu: 53184 kB HardwareCor) = 1024 write(1, "rupted: 0 kB\n", 17rupted: 0 kB ) = 17 write(1, "AnonHugePages: 0 kB\n", 28AnonHugePages: 0 kB ) = 28 write(1, "ShmemHugePages: 0 kB\n", 28ShmemHugePages: 0 kB ) = 28 write(1, "ShmemPmdMapped: 0 kB\n", 28ShmemPmdMapped: 0 kB ) = 28 write(1, "FileHugePages: 0 kB\n", 28FileHugePages: 0 kB ) = 28 write(1, "FilePmdMapped: 0 kB\n", 28FilePmdMapped: 0 kB ) = 28 write(1, "CmaTotal: 0 kB\n", 28CmaTotal: 0 kB ) = 28 write(1, "CmaFree: 0 kB\n", 28CmaFree: 0 kB ) = 28 write(1, "HugePages_Total: 0\n", 25HugePages_Total: 0 ) = 25 write(1, "HugePages_Free: 0\n", 25HugePages_Free: 0 ) = 25 write(1, "HugePages_Rsvd: 0\n", 25HugePages_Rsvd: 0 ) = 25 write(1, "HugePages_Surp: 0\n", 25HugePages_Surp: 0 ) = 25 write(1, "Hugepagesize: 2048 kB\n", 28Hugepagesize: 2048 kB ) = 28 write(1, "Hugetlb: 0 kB\n", 28Hugetlb: 0 kB ) = 28 write(1, "DirectMap4k: 57470916 kB\n", 28DirectMap4k: 57470916 kB ) = 28 write(1, "DirectMap2M: 76660736 kB\n", 28DirectMap2M: 76660736 kB ) = 28 write(1, "DirectMap1G: 0 kB\n", 28DirectMap1G: 0 kB ) = 28 nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffcebc0b270) = 0 write(1, "==========\n", 11========== ) = 11 lseek(3, 0, SEEK_SET) = 0 read(3, "MemTotal: 131927796 kB\nMem"..., 1535) = 1477 write(1, "MemTotal: 131927796 kB\nMem"..., 1024MemTotal: 131927796 kB MemFree: 19788924 kB MemAvailable: 125451896 kB Buffers: 50652156 kB Cached: 1641180 kB SwapCached: 87460 kB Active: 43843404 kB Inactive: 10393328 kB Active(anon): 1355264 kB Inactive(anon): 643528 kB Active(file): 42488140 kB Inactive(file): 9749800 kB Unevictable: 576 kB Mlocked: 576 kB SwapTotal: 4194300 kB SwapFree: 74268 kB Dirty: 116 kB Writeback: 0 kB AnonPages: 1856296 kB Mapped: 255636 kB Shmem: 61260 kB KReclaimable: 54483096 kB Slab: 57327008 kB SReclaimable: 54483096 kB SUnreclaim: 2843912 kB KernelStack: 28960 kB PageTables: 90512 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 70158196 kB Committed_AS: 15343516 kB VmallocTotal: 34359738367 kB VmallocUsed: 226408 kB VmallocChunk: 0 kB Percpu: 53184 kB HardwareCor) = 1024 write(1, "rupted: 0 kB\n", 17rupted: 0 kB ) = 17 write(1, "AnonHugePages: 0 kB\n", 28AnonHugePages: 0 kB ) = 28 write(1, "ShmemHugePages: 0 kB\n", 28ShmemHugePages: 0 kB ) = 28 write(1, "ShmemPmdMapped: 0 kB\n", 28ShmemPmdMapped: 0 kB ) = 28 write(1, "FileHugePages: 0 kB\n", 28FileHugePages: 0 kB ) = 28 write(1, "FilePmdMapped: 0 kB\n", 28FilePmdMapped: 0 kB ) = 28 write(1, "CmaTotal: 0 kB\n", 28CmaTotal: 0 kB ) = 28 write(1, "CmaFree: 0 kB\n", 28CmaFree: 0 kB ) = 28 write(1, "HugePages_Total: 0\n", 25HugePages_Total: 0 ) = 25 write(1, "HugePages_Free: 0\n", 25HugePages_Free: 0 ) = 25 write(1, "HugePages_Rsvd: 0\n", 25HugePages_Rsvd: 0 ) = 25 write(1, "HugePages_Surp: 0\n", 25HugePages_Surp: 0 ) = 25 write(1, "Hugepagesize: 2048 kB\n", 28Hugepagesize: 2048 kB ) = 28 write(1, "Hugetlb: 0 kB\n", 28Hugetlb: 0 kB ) = 28 write(1, "DirectMap4k: 57470916 kB\n", 28DirectMap4k: 57470916 kB ) = 28 write(1, "DirectMap2M: 76660736 kB\n", 28DirectMap2M: 76660736 kB ) = 28 write(1, "DirectMap1G: 0 kB\n", 28DirectMap1G: 0 kB ) = 28 nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffcebc0b270) = 0 [...] |
We showed that the monitoring of the files in /proc does not necessary need an open() operation before each read. To minimize the number of system calls, the size of the read buffer can be adapted to the average size of the file to have only one call to read().
The author is an engineer in computer sciences located in France. He can be contacted here.