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
SIGCHLDis 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 🙂