execでコマンドを実行すると、実行元タスクがスレッドなら、プロセスID等は実行元タスクのグループリーダ(通常は親プロセス)のプロセスID等に更新されます。
検証サンプル
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>
void get_pid(char *);
static int childFunc(void *arg)
{
get_pid("clone");
execl("./show_pid", "./exe_pid", NULL);
eturn 0;
}
#define STACK_SIZE (1024 * 1024)
int main(int argc, char *argv[])
{
char *stack;
char *stackTop;
pid_t pid;
stack = malloc(STACK_SIZE);
stackTop = stack + STACK_SIZE;
if(argc == 2) {
get_pid("thread parent");
pid = clone(childFunc, stackTop, CLONE_THREAD | CLONE_SIGHAND | CLONE_VM, NULL);
}
else {
get_pid("parent");
pid = clone(childFunc, stackTop, 0, NULL);
}
sleep(1);
waitpid(pid, NULL, 0);
exit(EXIT_SUCCESS);
}
void get_pid(char* p)
{
printf("%-20s: pid=%d, tid=%d, sid=%d gid=%d\n",
p,
syscall(SYS_getpid),
syscall(SYS_gettid),
syscall(SYS_getsid, 0),
syscall(SYS_getpgid, 0));
}
show_pid.c
#include <stdio.h>
#include <sys/syscall.h>
void main()
{
printf("%-20s: pid=%d, tid=%d, sid=%d gid=%d\n",
"exe cmd",
syscall(SYS_getpid),
syscall(SYS_gettid),
syscall(SYS_getsid, 0),
syscall(SYS_getpgid, 0));
}
結果
スレッドグループリーダのプロセス(pid=tid)なら、cloneプロセスのidとなり、cloneタスクと差し替えます。
[root@localhost test]# ./a.out
parent : pid=12069, tid=12069, sid=11772 gid=12069
clone : pid=12070, tid=12070, sid=11772 gid=12069
exe cmd : pid=12070, tid=12070, sid=11772 gid=12069
スレッド(pid != tid)なら、スレッドの親プロセスのidとなり、thread parentと差し替えます。(呼び出し元スレッドのcloneはkillされます。)
[root@localhost test]# ./a.out thread
thread parent : pid=12072, tid=12072, sid=11772 gid=12072
clone : pid=12072, tid=12073, sid=11772 gid=12072
exe cmd : pid=12072, tid=12072, sid=11772 gid=12072
execlは、struct linux_binfmtの.load_shlibコールバックからflush_old_exec()がコールされ、該当するタスクと差し替えます。その時、de_thread()で差替えられるプロセスの処理と、差替えるプロセスのIDが設定されます。
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
tskは差替えられるタスクです。thread_group_empty(tsk)は子スレッドを有しているかチェックします。子スレッド有しているなら、そのスレッド削除します。子のプロセスは親とリソースは独立しているため問題ありません。
子プロセスのスレッド有無のチェックは、thread_group_empty(tsk)で行います。tskがスレッドでもthread_group_empty(tsk)はFALSEとなります。従ってgoto no_thread_groupはスレッドを有しないプロセスのみです。
存在する子スレッドを削除し、if (!thread_group_leader(tsk))で、tskがスレッドの場合の処理となります。
スレッドの場合、leader = tsk->group_leaderは親プロセスです。tsk->group_leader = tskでexecはスレッドグループリーダとなり、親プロセスIDをexecのtskのPIDTYPE_PIDとし、親プロセスのpid/tidと同値となり、transfer_pid()は、スレッドリストのヘッドのleaderをtskに差し替えることで、スレッドのtask_structがスレッドグループリーダとなります。
親だったプロセスは、exeのスレッドと名実ともに入れ替わり、leader->exit_state = EXIT_DEAD/release_task(leader)で親のリソースは解放されます。
static int de_thread(struct task_struct *tsk)
{
struct signal_struct *sig = tsk->signal;
struct sighand_struct *oldsighand = tsk->sighand;
spinlock_t *lock = &oldsighand->siglock;
if (thread_group_empty(tsk))
goto no_thread_group;
spin_lock_irq(lock);
if (signal_group_exit(sig)) {
spin_unlock_irq(lock);
return -EAGAIN;
}
sig->group_exit_task = tsk;
sig->notify_count = zap_other_threads(tsk);
if (!thread_group_leader(tsk))
sig->notify_count--;
while (sig->notify_count) {
__set_current_state(TASK_UNINTERRUPTIBLE);
spin_unlock_irq(lock);
schedule();
spin_lock_irq(lock);
}
spin_unlock_irq(lock);
if (!thread_group_leader(tsk)) {
struct task_struct *leader = tsk->group_leader;
sig->notify_count = -1; /* for exit_notify() */
for (;;) {
write_lock_irq(&tasklist_lock);
if (likely(leader->exit_state))
break;
__set_current_state(TASK_UNINTERRUPTIBLE);
write_unlock_irq(&tasklist_lock);
schedule();
}
tsk->start_time = leader->start_time;
BUG_ON(!same_thread_group(leader, tsk));
BUG_ON(has_group_leader_pid(tsk));
detach_pid(tsk, PIDTYPE_PID);
tsk->pid = leader->pid;
attach_pid(tsk, PIDTYPE_PID, task_pid(leader));
transfer_pid(leader, tsk, PIDTYPE_PGID);
transfer_pid(leader, tsk, PIDTYPE_SID);
list_replace_rcu(&leader->tasks, &tsk->tasks);
list_replace_init(&leader->sibling, &tsk->sibling);
tsk->group_leader = tsk;
leader->group_leader = tsk;
tsk->exit_signal = SIGCHLD;
leader->exit_signal = -1;
BUG_ON(leader->exit_state != EXIT_ZOMBIE);
leader->exit_state = EXIT_DEAD;
if (unlikely(leader->ptrace))
__wake_up_parent(leader, leader->parent);
write_unlock_irq(&tasklist_lock);
release_task(leader);
}
sig->group_exit_task = NULL;
sig->notify_count = 0;
no_thread_group:
tsk->exit_signal = SIGCHLD;
if (current->mm)
setmax_mm_hiwater_rss(&sig->maxrss, current->mm);
exit_itimers(sig);
flush_itimer_signals();
if (atomic_read(&oldsighand->count) != 1) {
struct sighand_struct *newsighand;
newsighand = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
if (!newsighand)
return -ENOMEM;
atomic_set(&newsighand->count, 1);
memcpy(newsighand->action, oldsighand->action,
sizeof(newsighand->action));
write_lock_irq(&tasklist_lock);
spin_lock(&oldsighand->siglock);
rcu_assign_pointer(tsk->sighand, newsighand);
spin_unlock(&oldsighand->siglock);
write_unlock_irq(&tasklist_lock);
__cleanup_sighand(oldsighand);
}
BUG_ON(!thread_group_leader(tsk));
return 0;
}
copy_process()で、スレッドはグループリーダの親のthread_groupをヘッダーとするリストに必ず登録され、初期値であるlist->next = listは、スレッドならlist->next != listです。従ってthread_group_empty()はFALSEとなります。
copy_process()
{
:
INIT_LIST_HEAD(&p->thread_group);
:
if (clone_flags & CLONE_THREAD) {
current->signal->nr_threads++;
atomic_inc(¤t->signal->live);
atomic_inc(¤t->signal->sigcnt);
p->group_leader = current->group_leader;
list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
}
:
}
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
static inline int thread_group_empty(struct task_struct *p)
{
return list_empty(&p->thread_group);
}
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
追記
プロセス/スレッド関係なく、親のPIDTYPE_PGID/PIDTYPE_SIDも継承しており、従ってシェルは、fork()したプロセスのPIDTYPE_PGIDをPIDTYPE_PIDで更新後、execする事でグループリーダとする実装と言えそうです。