TL;DR:
- If process B is tracing process A using something like
ptrace()
, then B is the parent of A. In that case, B is not necessarily the real_parent of A. - If B creates A (e.g. using
fork()
) but terminates before A, theninit
(PID 1) now becomes both the parent and the real_parent of A – at least on my 5.8.0. - When process A terminates, a
SIGCHLD
is sent to the parent of A.
I am not 100% sure – please leave a comment if I said anything wrong, and let’s learn together!
Okay, okay. I know that a struct task_struct
instance represents a task, or a “schedulable entity”, not a “process”, but I’m still gonna use “process” for the sake of brevity. No pthread is created in this post!
We all say that “B is the parent of A” if B “created” A using something like fork()
. However, in the Linux kernel, there are two “parent” fields in our task descriptor, struct task_struct
, as defined in include/linux/sched.h
:
/* Real parent process: */ struct task_struct __rcu *real_parent; /* Recipient of SIGCHLD, wait4() reports: */ struct task_struct __rcu *parent;
What on earth is the difference between them?
Short answer: no difference for “normal processes” – they both point to the same “parent”. In order to confirm, I wrote a very simple miscellaneous character driver:
#include <linux/fs.h> #include <linux/kernel.h> #include <linux/miscdevice.h> #include <linux/module.h> static ssize_t hello_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { printk("%s: current->pid: %d\n", __func__, task_pid_nr(current)); rcu_read_lock(); printk("%s: current->real_parent->pid: %d\n", __func__, task_pid_nr(rcu_dereference(current->real_parent))); printk("%s: current->parent->pid: %d\n", __func__, task_pid_nr(rcu_dereference(current->parent))); rcu_read_unlock(); return 0; } static const struct file_operations hello_fops = { .owner = THIS_MODULE, .read = hello_read, }; static struct miscdevice hello_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "hello", .fops = &hello_fops }; static int __init hello_init(void) { return misc_register(&hello_dev); } static void __exit hello_exit(void) { misc_deregister(&hello_dev); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Peilin Ye");
It is poorly written in many ways: missing SPDX-License-Identifier tag, missing MODULE_DESCRIPTION
…the list goes on.
Anyway, it exposes a device node, /dev/hello
, and it dumps current->pid
, current->real_parent->pid
and current->parent->pid
into dmesg, if someone (tries to) read it. The latter two printk()
s are RCU-protected – I borrowed some code from task_ppid_nr_ns()
, see include/linux/sched.h
.
Yeah, and a Makefile:
obj-m += hello.o KDIR := /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean
Install it:
peilin@PWN:~/Desktop/playground$ sudo insmod hello.ko
peilin@PWN:~/Desktop/playground$ lsmod hello | grep hello
hello 16384 0
peilin@PWN:~/Desktop/playground$ file /dev/hello
/dev/hello: character special (10/56)
peilin@PWN:~/Desktop/playground$ sudo chmod 0444 /dev/hello
Now we cat
/dev/hello
, and take a look at dmesg:
peilin@PWN:~/Desktop/playground$ cat /dev/hello
peilin@PWN:~/Desktop/playground$ dmesg | tail -3
[334231.298794] hello_read: current->pid: 449498
[334231.298800] hello_read: current->real_parent->pid: 365698
[334231.298800] hello_read: current->parent->pid: 365698
peilin@PWN:~/Desktop/playground$ echo $$
365698
Nice! It worked. 449498
seems to be the PID of cat
. As you can see, here both real_parent
and parent
points to my Bash shell, so there’s no difference between them.
Now, now. What if the fork()
parent terminates before the child? In order to figure out, I wrote another (userspace) program:
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { if (fork() == 0) { int ret = 0; /* make sure that our parent terminates before us */ sleep(3); int fd = open("/dev/hello", O_RDONLY); if (fd < 0) { fprintf(stderr, "%d: failed to open /dev/hello\n", getpid()); exit(-1); } if (read(fd, NULL, 0) < 0) { fprintf(stderr, "%d: failed to read /dev/hello\n", getpid()); ret = -1; } close(fd); return ret; } return 0; }
By the way, it’s quite surprising to me that read(fd, NULL, 0);
actually works…
Anyway, run it, wait 3 seconds, and take another look at dmesg:
peilin@PWN:~/Desktop/playground$ gcc -o demo demo.c
peilin@PWN:~/Desktop/playground$ ./demo
peilin@PWN:~/Desktop/playground$ sleep 3
peilin@PWN:~/Desktop/playground$ dmesg | tail -3
[335409.670896] hello_read: current->pid: 453660
[335409.670899] hello_read: current->real_parent->pid: 1
[335409.670899] hello_read: current->parent->pid: 1
Interesting! Since the parent has already terminated when the child issues read()
, both real_parent
and parent
now point to init
, “the parent of all processes”.
Finally, if process B traces process A using ptrace()
, B becomes the parent
of A. B is not necessarily the real_parent
of A.
I modified my userspace demo a little bit to make the child process loop forever, so I can get a chance to attach GDB to it. GDB uses ptrace()
to debug processes. Note, in order to attach GDB to a process (i.e. gdb -p <PID>
), you may have to switch to the root user, or do echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
if you “don’t care about security” 🙂
Anyway, after attaching GDB to the child process and resuming it using the c
(“continue”) command, let’s take one last look at dmesg:
peilin@PWN:~/Desktop/playground$ pidof gdb
459290
peilin@PWN:~/Desktop/playground$ dmesg | tail -3
[337238.115535] hello_read: current->pid: 459265
[337238.115537] hello_read: current->real_parent->pid: 1
[337238.115538] hello_read: current->parent->pid: 459290
Wonderful! Three different PIDs! The child process reading /dev/hello
is 459265
; its real_parent
now becomes init
(1
) since its “original parent” who created it has already terminated; finally, its parent
is now GDB (459290
).
As the parent
of the child, GDB will receive a SIGCHLD
when the child exits.
…and that’s it! I hope you enjoyed it.
wow, what fun relationships between parents and children!
…I’m glad that you enjoyed it 🙂