In shell we can get a simple process list with:
$ for P in $(seq 1 32168); do [ -d "/proc/$P" ] || continue echo "$P: $(cat /proc/$P/cmdline 2>/dev/null |sed 's/\x00/ /g')" done 1: init [2] 1423: -bash [...]But what if we want more information, just like ps from procps gives us?
Well, simply download procps and patch it to iterate over all PIDs instead of reading entries of /proc with opendir/readdir. By the way, the maximum PID is given by the kernel at the following location:
$ cat /proc/sys/kernel/pid_max 32768
So here is my small patch, just a few lines:
$ diff -ur procps-3.2.8{,.mine} --- procps-3.2.8/proc/readproc.c 2006-06-16 08:18:13.000000000 +0000 +++ procps-3.2.8.mine/proc/readproc.c 2010-08-28 04:25:03.000000000 +0000 @@ -27,6 +27,10 @@ #include <sys/types.h> #include <sys/stat.h> +/* global variables to read /proc entries blindly if proc is not readable */ +int current_pid; +int max_pid; + // sometimes it's easier to do this manually, w/o gcc helping #ifdef PROF extern void __cyg_profile_func_enter(void*,void*); @@ -700,15 +704,25 @@ static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) { static struct direct *ent; /* dirent handle */ char *restrict const path = PT->path; - for (;;) { - ent = readdir(PT->procfs); - if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0; - if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break; + if (PT->procfs) { + for (;;) { + ent = readdir(PT->procfs); + if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0; + if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break; + } + p->tgid = strtoul(ent->d_name, NULL, 10); + p->tid = p->tgid; + memcpy(path, "/proc/", 6); + strcpy(path+6, ent->d_name); // trust /proc to not contain evil top-level entries + } + else { + for (;;) { + if (unlikely(++current_pid > max_pid)) return 0; + snprintf(path, PROCPATHLEN, "/proc/%i", current_pid); + if (unlikely(!access(path,F_OK))) break; + } + p->tid = p->tgid = current_pid; } - p->tgid = strtoul(ent->d_name, NULL, 10); - p->tid = p->tgid; - memcpy(path, "/proc/", 6); - strcpy(path+6, ent->d_name); // trust /proc to not contain evil top-level entries return 1; } @@ -859,7 +873,17 @@ PT->finder = listed_nextpid; }else{ PT->procfs = opendir("/proc"); - if(!PT->procfs) return NULL; + if(!PT->procfs) { + FILE *fp; + char maxpid[32]; + if ((fp = fopen("/proc/sys/kernel/pid_max", "r")) > 0) { + fread(maxpid,sizeof(maxpid),1,fp); + max_pid = atoi(maxpid); + fclose(fp); + } + else max_pid = 32768; /* assume it would be the case */ + current_pid = 0; /* start at 0, first iteration will increment */ + } PT->finder = simple_nextpid; } PT->flags = flags;
For testing and portability we compile ps as a static binary:
$ make SHARED=0 CFLAGS=-static ps/ps [...] $ file ps/ps ps/ps: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.8, not stripped $ ps/ps PID TTY TIME CMD 3479 pts/8 00:00:03 bash 19787 pts/8 00:00:00 ps
When /proc is not readable, this ps now works! And good to know, iterating over all pids does not make things much slower.
>And good to know, iterating over all pids does not make things much slower.
ReplyDeleteWhy do you say that?
With /proc +r:
ReplyDelete$ time ps
[...]
real 0m0.008s
With /proc -r:
$ time ps
real 0m0.088s
Still acceptable :)
Oh okie, for a moment I underestimated ps(1)'s code :)
ReplyDeleteThanks for taking the time to reply :)