Skip to contentSkip to conversation

proc: test /proc/*/fd a bit (+ PF_KTHREAD is ABI!)

PATCH In review Alexey Dobriyan 2 participants 3 messages May 5, 2018 – May 9, 2018

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 proc: test /proc/*/fd a bit (+ PF_KTHREAD is ABI!)

* Test lookup in /proc/self/fd.
"map_files" lookup story showed that lookup is not that simple.

* Test that all those symlinks open the same file. Check with (st_dev, st_info).

* Test that kernel threads do not have anything in their /proc/*/fd/ directory.

Now this is where things get interesting.

First, kernel threads aren't pinned by /proc/self or equivalent, thus some "atomicity" is required.

Second, ->comm can contain whitespace and ')'.
No, they are not escaped.

Third, the only reliable way to check if process is kernel thread appears to be field #9 in /proc/*/stat.

This field is struct task_struct::flags in decimal! Check is done by testing PF_KTHREAD flags like we do in kernel.

PF_KTREAD value is a part of userspace ABI !!!

Other methods for determining kernel threadness are not reliable:
* RSS can be 0 if everything is swapped, even while reading from /proc/self.

* ->total_vm CAN BE ZERO if process is finishing

	munmap(NULL, whole address space);

* /proc/*/maps and similar files can be empty because unmapping everything works. Read returning 0 can't distinguish between kernel thread and such suicide process.

---

 tools/testing/selftests/proc/.gitignore        |    3 
 tools/testing/selftests/proc/Makefile          |    5 
 tools/testing/selftests/proc/fd-001-lookup.c   |  168 +++++++++++++++++++++++
 tools/testing/selftests/proc/fd-002-posix-eq.c |   57 ++++++++
 tools/testing/selftests/proc/fd-003-kthread.c  |  178 +++++++++++++++++++++++++
 tools/testing/selftests/proc/proc-uptime.h     |   16 --
 tools/testing/selftests/proc/proc.h            |   39 +++++
 tools/testing/selftests/proc/read.c            |   17 --
 8 files changed, 451 insertions(+), 32 deletions(-)
tools/testing/selftests/proc/.gitignore +3 -0
@@ -1,3 +1,6 @@
+/fd-001-lookup
+/fd-002-posix-eq
+/fd-003-kthread
 /proc-loadavg-001
 /proc-self-map-files-001
 /proc-self-map-files-002
tools/testing/selftests/proc/Makefile +4 -1
@@ -1,6 +1,9 @@
-CFLAGS += -Wall -O2
+CFLAGS += -Wall -O2 -Wno-unused-function
 
 TEST_GEN_PROGS :=
+TEST_GEN_PROGS += fd-001-lookup
+TEST_GEN_PROGS += fd-002-posix-eq
+TEST_GEN_PROGS += fd-003-kthread
 TEST_GEN_PROGS += proc-loadavg-001
 TEST_GEN_PROGS += proc-self-map-files-001
 TEST_GEN_PROGS += proc-self-map-files-002

new file mode 100644

tools/testing/selftests/proc/fd-001-lookup.c new file +168 -0

168 lines not on this page · raw message

new file mode 100644

tools/testing/selftests/proc/fd-002-posix-eq.c new file +57 -0

57 lines not on this page · raw message

new file mode 100644

tools/testing/selftests/proc/fd-003-kthread.c new file +178 -0

178 lines not on this page · raw message

tools/testing/selftests/proc/proc-uptime.h +1 -15
@@ -20,21 +20,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 
-static unsigned long long xstrtoull(const char *p, char **end)
-{
-	if (*p == '0') {
-		*end = (char *)p + 1;
-		return 0;
-	} else if ('1' <= *p && *p <= '9') {
-		unsigned long long val;
-
-		errno = 0;
-		val = strtoull(p, end, 10);
-		assert(errno == 0);
-		return val;
-	} else
-		assert(0);
-}
+#include "proc.h"
 
 static void proc_uptime(int fd, uint64_t *uptime, uint64_t *idle)
 {

new file mode 100644

tools/testing/selftests/proc/proc.h new file +39 -0
@@ -0,0 +1,39 @@
+#pragma once
+#undef NDEBUG
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline bool streq(const char *s1, const char *s2)
+{
+	return strcmp(s1, s2) == 0;
+}
+
+static unsigned long long xstrtoull(const char *p, char **end)
+{
+	if (*p == '0') {
+		*end = (char *)p + 1;
+		return 0;
+	} else if ('1' <= *p && *p <= '9') {
+		unsigned long long val;
+
+		errno = 0;
+		val = strtoull(p, end, 10);
+		assert(errno == 0);
+		return val;
+	} else
+		assert(0);
+}
+
+static struct dirent *xreaddir(DIR *d)
+{
+	struct dirent *de;
+
+	errno = 0;
+	de = readdir(d);
+	assert(de || errno == 0);
+	return de;
+}
tools/testing/selftests/proc/read.c +1 -16
@@ -31,22 +31,7 @@
 #include <fcntl.h>
 #include <unistd.h>
 
-static inline bool streq(const char *s1, const char *s2)
-{
-	return strcmp(s1, s2) == 0;
-}
-
-static struct dirent *xreaddir(DIR *d)
-{
-	struct dirent *de;
-
-	errno = 0;
-	de = readdir(d);
-	if (!de && errno != 0) {
-		exit(1);
-	}
-	return de;
-}
+#include "proc.h"
 
 static void f_reg(DIR *d, const char *filename)
 {
#1 lore raw
Quoted 17 lines · On Sat, 5 May 2018 03:04:14 +0300 Alexey Dobriyan <[email protected]> wrote:

* Test lookup in /proc/self/fd.
"map_files" lookup story showed that lookup is not that simple.

* Test that all those symlinks open the same file. Check with (st_dev, st_info).

* Test that kernel threads do not have anything in their /proc/*/fd/ directory.

Now this is where things get interesting.

First, kernel threads aren't pinned by /proc/self or equivalent, thus some "atomicity" is required.

Second, ->comm can contain whitespace and ')'.
No, they are not escaped.

Quoted 7 lines

Third, the only reliable way to check if process is kernel thread appears to be field #9 in /proc/*/stat.

This field is struct task_struct::flags in decimal! Check is done by testing PF_KTHREAD flags like we do in kernel.

PF_KTREAD value is a part of userspace ABI !!!

erk. Well if there's a need the we could export and support some stable interface. I wonder how ps determines this.

Quoted 12 lines

Other methods for determining kernel threadness are not reliable:
* RSS can be 0 if everything is swapped, even while reading from /proc/self.

* ->total_vm CAN BE ZERO if process is finishing

	munmap(NULL, whole address space);

* /proc/*/maps and similar files can be empty because unmapping everything works. Read returning 0 can't distinguish between kernel thread and such suicide process.

#2 lore raw
Quoted 9 lines · Andrew Morton
Quoted 4 lines · On Sat, 5 May 2018 03:04:14 +0300 Alexey Dobriyan <[email protected]> wrote:

This field is struct task_struct::flags in decimal! Check is done by testing PF_KTHREAD flags like we do in kernel.

PF_KTREAD value is a part of userspace ABI !!!

erk. Well if there's a need the we could export and support some stable interface. I wonder how ps determines this.

Turned out ps (from procps-ng) reads /proc/*/cmdline and if read() returns 0 prints [] brackets. This is unreliable as

	execve("", NULL, NULL);

exists and processes can empty their argv/envp area.

It can do sorting based on "flags" field, so we hopefully can just mask most of the bits.

At least it deals with ->comm containing ')' right.