summaryrefslogtreecommitdiffstats
path: root/sys/compat/linux/linux_emul.c
diff options
context:
space:
mode:
authordchagin <dchagin@FreeBSD.org>2016-01-09 15:16:13 +0000
committerdchagin <dchagin@FreeBSD.org>2016-01-09 15:16:13 +0000
commitcb3b38d1640b6a07cdc16de9cc37c2da8cfff4c1 (patch)
treea6590a108af61dda64e892cb9d59ab130e0f805b /sys/compat/linux/linux_emul.c
parentccb67dae7f74133acb97e4894ad207af1d13a6c4 (diff)
downloadFreeBSD-src-cb3b38d1640b6a07cdc16de9cc37c2da8cfff4c1.zip
FreeBSD-src-cb3b38d1640b6a07cdc16de9cc37c2da8cfff4c1.tar.gz
MFC r283383:
Switch linuxulator to use the native 1:1 threads. The reasons: 1. Get rid of the stubs/quirks with process dethreading, process reparent when the process group leader exits and close to this problems on wait(), waitpid(), etc. 2. Reuse our kernel code instead of writing excessive thread managment routines in Linuxulator. Implementation details: 1. The thread is created via kern_thr_new() in the clone() call with the CLONE_THREAD parameter. Thus, everything else is a process. 2. The test that the process has a threads is done via P_HADTHREADS bit p_flag of struct proc. 3. Per thread emulator state data structure is now located in the struct thread and freed in the thread_dtor() hook. Mandatory holdig of the p_mtx required when referencing emuldata from the other threads. 4. PID mangling has changed. Now Linux pid is the native tid and Linux tgid is the native pid, with the exception of the first thread in the process where tid and pid are one and the same. Ugliness: In case when the Linux thread is the initial thread in the thread group thread id is equal to the process id. Glibc depends on this magic (assert in pthread_getattr_np.c). So for system calls that take thread id as a parameter we should use the special method to reference struct thread.
Diffstat (limited to 'sys/compat/linux/linux_emul.c')
-rw-r--r--sys/compat/linux/linux_emul.c406
1 files changed, 145 insertions, 261 deletions
diff --git a/sys/compat/linux/linux_emul.c b/sys/compat/linux/linux_emul.c
index 61156ba..887f72e 100644
--- a/sys/compat/linux/linux_emul.c
+++ b/sys/compat/linux/linux_emul.c
@@ -1,5 +1,6 @@
/*-
* Copyright (c) 2006 Roman Divacky
+ * Copyright (c) 2013 Dmitry Chagin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -36,6 +37,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/imgact.h>
#include <sys/kernel.h>
+#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
@@ -59,6 +61,7 @@ __FBSDID("$FreeBSD$");
#include <compat/linux/linux_emul.h>
#include <compat/linux/linux_futex.h>
#include <compat/linux/linux_misc.h>
+#include <compat/linux/linux_util.h>
/**
* Special DTrace provider for the linuxulator.
@@ -74,33 +77,21 @@ __FBSDID("$FreeBSD$");
LIN_SDT_PROVIDER_DEFINE(LINUX_DTRACE);
/**
- * Special DTrace module "locks", it covers some linuxulator internal
- * locks.
- */
-LIN_SDT_PROBE_DEFINE1(locks, emul_lock, locked, "struct mtx *");
-LIN_SDT_PROBE_DEFINE1(locks, emul_lock, unlock, "struct mtx *");
-LIN_SDT_PROBE_DEFINE1(locks, emul_shared_rlock, locked, "struct sx *");
-LIN_SDT_PROBE_DEFINE1(locks, emul_shared_rlock, unlock, "struct sx *");
-LIN_SDT_PROBE_DEFINE1(locks, emul_shared_wlock, locked, "struct sx *");
-LIN_SDT_PROBE_DEFINE1(locks, emul_shared_wlock, unlock, "struct sx *");
-
-/**
* DTrace probes in this module.
*/
-LIN_SDT_PROBE_DEFINE2(emul, em_find, entry, "struct proc *", "int");
+LIN_SDT_PROBE_DEFINE1(emul, em_find, entry, "struct thread *");
LIN_SDT_PROBE_DEFINE0(emul, em_find, return);
-LIN_SDT_PROBE_DEFINE3(emul, proc_init, entry, "struct thread *", "pid_t",
- "int");
+LIN_SDT_PROBE_DEFINE3(emul, proc_init, entry, "struct thread *",
+ "struct thread *", "int");
LIN_SDT_PROBE_DEFINE0(emul, proc_init, create_thread);
LIN_SDT_PROBE_DEFINE0(emul, proc_init, fork);
LIN_SDT_PROBE_DEFINE0(emul, proc_init, exec);
LIN_SDT_PROBE_DEFINE0(emul, proc_init, return);
LIN_SDT_PROBE_DEFINE1(emul, proc_exit, entry, "struct proc *");
-LIN_SDT_PROBE_DEFINE0(emul, proc_exit, futex_failed);
-LIN_SDT_PROBE_DEFINE3(emul, proc_exit, reparent, "pid_t", "pid_t",
- "struct proc *");
-LIN_SDT_PROBE_DEFINE1(emul, proc_exit, child_clear_tid_error, "int");
-LIN_SDT_PROBE_DEFINE0(emul, proc_exit, return);
+LIN_SDT_PROBE_DEFINE1(emul, linux_thread_detach, entry, "struct thread *");
+LIN_SDT_PROBE_DEFINE0(emul, linux_thread_detach, futex_failed);
+LIN_SDT_PROBE_DEFINE1(emul, linux_thread_detach, child_clear_tid_error, "int");
+LIN_SDT_PROBE_DEFINE0(emul, linux_thread_detach, return);
LIN_SDT_PROBE_DEFINE2(emul, proc_exec, entry, "struct proc *",
"struct image_params *");
LIN_SDT_PROBE_DEFINE0(emul, proc_exec, return);
@@ -109,284 +100,208 @@ LIN_SDT_PROBE_DEFINE1(emul, linux_schedtail, copyout_error, "int");
LIN_SDT_PROBE_DEFINE0(emul, linux_schedtail, return);
LIN_SDT_PROBE_DEFINE1(emul, linux_set_tid_address, entry, "int *");
LIN_SDT_PROBE_DEFINE0(emul, linux_set_tid_address, return);
-LIN_SDT_PROBE_DEFINE2(emul, linux_kill_threads, entry, "struct thread *",
- "int");
-LIN_SDT_PROBE_DEFINE1(emul, linux_kill_threads, kill, "pid_t");
-LIN_SDT_PROBE_DEFINE0(emul, linux_kill_threads, return);
-
-struct sx emul_shared_lock;
-struct mtx emul_lock;
-/* this returns locked reference to the emuldata entry (if found) */
+/*
+ * This returns reference to the emuldata entry (if found)
+ *
+ * Hold PROC_LOCK when referencing emuldata from other threads.
+ */
struct linux_emuldata *
-em_find(struct proc *p, int locked)
+em_find(struct thread *td)
{
struct linux_emuldata *em;
- LIN_SDT_PROBE2(emul, em_find, entry, p, locked);
+ LIN_SDT_PROBE1(emul, em_find, entry, td);
- if (locked == EMUL_DOLOCK)
- EMUL_LOCK(&emul_lock);
-
- em = p->p_emuldata;
-
- if (em == NULL && locked == EMUL_DOLOCK)
- EMUL_UNLOCK(&emul_lock);
+ em = td->td_emuldata;
LIN_SDT_PROBE1(emul, em_find, return, em);
+
return (em);
}
-int
-linux_proc_init(struct thread *td, pid_t child, int flags)
+void
+linux_proc_init(struct thread *td, struct thread *newtd, int flags)
{
- struct linux_emuldata *em, *p_em;
- struct proc *p;
+ struct linux_emuldata *em;
- LIN_SDT_PROBE3(emul, proc_init, entry, td, child, flags);
+ LIN_SDT_PROBE3(emul, proc_init, entry, td, newtd, flags);
- if (child != 0) {
- /* fork or create a thread */
- em = malloc(sizeof *em, M_LINUX, M_WAITOK | M_ZERO);
- em->pid = child;
+ if (newtd != NULL) {
+ /* non-exec call */
+ em = malloc(sizeof(*em), M_TEMP, M_WAITOK | M_ZERO);
em->pdeath_signal = 0;
em->flags = 0;
em->robust_futexes = NULL;
if (flags & LINUX_CLONE_THREAD) {
- /* handled later in the code */
LIN_SDT_PROBE0(emul, proc_init, create_thread);
- } else {
- struct linux_emuldata_shared *s;
+ em->em_tid = newtd->td_tid;
+ } else {
LIN_SDT_PROBE0(emul, proc_init, fork);
- s = malloc(sizeof *s, M_LINUX, M_WAITOK | M_ZERO);
- s->refs = 1;
- s->group_pid = child;
-
- LIST_INIT(&s->threads);
- em->shared = s;
+ em->em_tid = newtd->td_proc->p_pid;
}
+ newtd->td_emuldata = em;
} else {
/* exec */
LIN_SDT_PROBE0(emul, proc_init, exec);
/* lookup the old one */
- em = em_find(td->td_proc, EMUL_DOLOCK);
+ em = em_find(td);
KASSERT(em != NULL, ("proc_init: emuldata not found in exec case.\n"));
+
+ em->em_tid = td->td_proc->p_pid;
}
em->child_clear_tid = NULL;
em->child_set_tid = NULL;
+ LIN_SDT_PROBE0(emul, proc_init, return);
+}
+
+void
+linux_proc_exit(void *arg __unused, struct proc *p)
+{
+ struct thread *td = curthread;
+
+ if (__predict_false(SV_CURPROC_ABI() != SV_ABI_LINUX)) {
+ LIN_SDT_PROBE1(emul, proc_exit, entry, p);
+ (p->p_sysent->sv_thread_detach)(td);
+ }
+}
+
+int
+linux_common_execve(struct thread *td, struct image_args *eargs)
+{
+ struct linux_emuldata *em;
+ struct proc *p;
+ int error;
+
+ p = td->td_proc;
+
/*
- * allocate the shared struct only in clone()/fork cases in the case
- * of clone() td = calling proc and child = pid of the newly created
- * proc
+ * Unlike FreeBSD abort all other threads before
+ * proceeding exec.
*/
- if (child != 0) {
- if (flags & LINUX_CLONE_THREAD) {
- /* lookup the parent */
- /*
- * we dont have to lock the p_em because
- * its waiting for us in linux_clone so
- * there is no chance of it changing the
- * p_em->shared address
- */
- p_em = em_find(td->td_proc, EMUL_DONTLOCK);
- KASSERT(p_em != NULL, ("proc_init: parent emuldata not found for CLONE_THREAD\n"));
- em->shared = p_em->shared;
- EMUL_SHARED_WLOCK(&emul_shared_lock);
- em->shared->refs++;
- EMUL_SHARED_WUNLOCK(&emul_shared_lock);
- } else {
- /*
- * handled earlier to avoid malloc(M_WAITOK) with
- * rwlock held
- */
- }
+ PROC_LOCK(p);
+ /* See exit1() comments. */
+ thread_suspend_check(0);
+ while (p->p_flag & P_HADTHREADS) {
+ if (!thread_single(p, SINGLE_EXIT))
+ break;
+ thread_suspend_check(0);
+ }
+ PROC_UNLOCK(p);
- EMUL_SHARED_WLOCK(&emul_shared_lock);
- LIST_INSERT_HEAD(&em->shared->threads, em, threads);
- EMUL_SHARED_WUNLOCK(&emul_shared_lock);
+ error = kern_execve(td, eargs, NULL);
+ if (error != 0)
+ return (error);
- p = pfind(child);
- KASSERT(p != NULL, ("process not found in proc_init\n"));
- p->p_emuldata = em;
+ /*
+ * In a case of transition from Linux binary execing to
+ * FreeBSD binary we destroy linux emuldata thread entry.
+ */
+ if (SV_CURPROC_ABI() != SV_ABI_LINUX) {
+ PROC_LOCK(p);
+ em = em_find(td);
+ KASSERT(em != NULL, ("proc_exec: emuldata not found.\n"));
+ td->td_emuldata = NULL;
PROC_UNLOCK(p);
- } else
- EMUL_UNLOCK(&emul_lock);
- LIN_SDT_PROBE0(emul, proc_init, return);
+ free(em, M_TEMP);
+ }
return (0);
}
-void
-linux_proc_exit(void *arg __unused, struct proc *p)
+void
+linux_proc_exec(void *arg __unused, struct proc *p, struct image_params *imgp)
{
- struct linux_emuldata *em;
- int error, shared_flags, shared_xstat;
- struct thread *td = FIRST_THREAD_IN_PROC(p);
- int *child_clear_tid;
- struct proc *q, *nq;
-
- if (__predict_true(p->p_sysent != &elf_linux_sysvec))
- return;
+ struct thread *td = curthread;
- LIN_SDT_PROBE1(emul, proc_exit, entry, p);
-
- release_futexes(p);
+ /*
+ * In a case of execing to linux binary we create linux
+ * emuldata thread entry.
+ */
+ if (__predict_false((imgp->sysent->sv_flags & SV_ABI_MASK) ==
+ SV_ABI_LINUX)) {
+ LIN_SDT_PROBE2(emul, proc_exec, entry, p, imgp);
+ if (SV_PROC_ABI(p) == SV_ABI_LINUX)
+ linux_proc_init(td, NULL, 0);
+ else
+ linux_proc_init(td, td, 0);
- /* find the emuldata */
- em = em_find(p, EMUL_DOLOCK);
+ LIN_SDT_PROBE0(emul, proc_exec, return);
+ }
+}
- KASSERT(em != NULL, ("proc_exit: emuldata not found.\n"));
+void
+linux_thread_detach(struct thread *td)
+{
+ struct linux_sys_futex_args cup;
+ struct linux_emuldata *em;
+ int *child_clear_tid;
+ int null = 0;
+ int error;
- /* reparent all procs that are not a thread leader to initproc */
- if (em->shared->group_pid != p->p_pid) {
- LIN_SDT_PROBE3(emul, proc_exit, reparent,
- em->shared->group_pid, p->p_pid, p);
+ LIN_SDT_PROBE1(emul, linux_thread_detach, entry, td);
- child_clear_tid = em->child_clear_tid;
- EMUL_UNLOCK(&emul_lock);
- sx_xlock(&proctree_lock);
- wakeup(initproc);
- PROC_LOCK(p);
- proc_reparent(p, initproc);
- p->p_sigparent = SIGCHLD;
- PROC_UNLOCK(p);
- sx_xunlock(&proctree_lock);
- } else {
- child_clear_tid = em->child_clear_tid;
- EMUL_UNLOCK(&emul_lock);
- }
+ em = em_find(td);
+ KASSERT(em != NULL, ("thread_detach: emuldata not found.\n"));
- EMUL_SHARED_WLOCK(&emul_shared_lock);
- shared_flags = em->shared->flags;
- shared_xstat = em->shared->xstat;
- LIST_REMOVE(em, threads);
+ LINUX_CTR1(exit, "thread detach(%d)", em->em_tid);
- em->shared->refs--;
- if (em->shared->refs == 0) {
- EMUL_SHARED_WUNLOCK(&emul_shared_lock);
- free(em->shared, M_LINUX);
- } else
- EMUL_SHARED_WUNLOCK(&emul_shared_lock);
+ release_futexes(td, em);
- if ((shared_flags & EMUL_SHARED_HASXSTAT) != 0)
- p->p_xstat = shared_xstat;
+ child_clear_tid = em->child_clear_tid;
if (child_clear_tid != NULL) {
- struct linux_sys_futex_args cup;
- int null = 0;
+ LINUX_CTR2(exit, "thread detach(%d) %p",
+ em->em_tid, child_clear_tid);
+
error = copyout(&null, child_clear_tid, sizeof(null));
if (error) {
- LIN_SDT_PROBE1(emul, proc_exit,
+ LIN_SDT_PROBE1(emul, linux_thread_detach,
child_clear_tid_error, error);
- free(em, M_LINUX);
-
- LIN_SDT_PROBE0(emul, proc_exit, return);
+ LIN_SDT_PROBE0(emul, linux_thread_detach, return);
return;
}
- /* futexes stuff */
cup.uaddr = child_clear_tid;
cup.op = LINUX_FUTEX_WAKE;
cup.val = 0x7fffffff; /* Awake everyone */
cup.timeout = NULL;
cup.uaddr2 = NULL;
cup.val3 = 0;
- error = linux_sys_futex(FIRST_THREAD_IN_PROC(p), &cup);
+ error = linux_sys_futex(td, &cup);
/*
* this cannot happen at the moment and if this happens it
* probably means there is a user space bug
*/
if (error) {
- LIN_SDT_PROBE0(emul, proc_exit, futex_failed);
- printf(LMSG("futex stuff in proc_exit failed.\n"));
- }
- }
-
- /* clean the stuff up */
- free(em, M_LINUX);
-
- /* this is a little weird but rewritten from exit1() */
- sx_xlock(&proctree_lock);
- q = LIST_FIRST(&p->p_children);
- for (; q != NULL; q = nq) {
- nq = LIST_NEXT(q, p_sibling);
- if (q->p_flag & P_WEXIT)
- continue;
- if (__predict_false(q->p_sysent != &elf_linux_sysvec))
- continue;
- em = em_find(q, EMUL_DOLOCK);
- KASSERT(em != NULL, ("linux_reparent: emuldata not found: %i\n", q->p_pid));
- PROC_LOCK(q);
- if ((q->p_flag & P_WEXIT) == 0 && em->pdeath_signal != 0) {
- kern_psignal(q, em->pdeath_signal);
+ LIN_SDT_PROBE0(emul, linux_thread_detach, futex_failed);
+ printf(LMSG("futex stuff in thread_detach failed.\n"));
}
- PROC_UNLOCK(q);
- EMUL_UNLOCK(&emul_lock);
}
- sx_xunlock(&proctree_lock);
- LIN_SDT_PROBE0(emul, proc_exit, return);
+ LIN_SDT_PROBE0(emul, linux_thread_detach, return);
}
-/*
- * This is used in a case of transition from FreeBSD binary execing to linux binary
- * in this case we create linux emuldata proc entry with the pid of the currently running
- * process.
- */
-void
-linux_proc_exec(void *arg __unused, struct proc *p, struct image_params *imgp)
+void
+linux_thread_dtor(void *arg __unused, struct thread *td)
{
- if (__predict_false(imgp->sysent == &elf_linux_sysvec)) {
- LIN_SDT_PROBE2(emul, proc_exec, entry, p, imgp);
- }
- if (__predict_false(imgp->sysent == &elf_linux_sysvec
- && p->p_sysent != &elf_linux_sysvec))
- linux_proc_init(FIRST_THREAD_IN_PROC(p), p->p_pid, 0);
- if (__predict_false((p->p_sysent->sv_flags & SV_ABI_MASK) ==
- SV_ABI_LINUX))
- /* Kill threads regardless of imgp->sysent value */
- linux_kill_threads(FIRST_THREAD_IN_PROC(p), SIGKILL);
- if (__predict_false(imgp->sysent != &elf_linux_sysvec
- && p->p_sysent == &elf_linux_sysvec)) {
- struct linux_emuldata *em;
-
- /*
- * XXX:There's a race because here we assign p->p_emuldata NULL
- * but the process is still counted as linux one for a short
- * time so some other process might reference it and try to
- * access its p->p_emuldata and panicing on a NULL reference.
- */
- em = em_find(p, EMUL_DONTLOCK);
-
- KASSERT(em != NULL, ("proc_exec: emuldata not found.\n"));
-
- EMUL_SHARED_WLOCK(&emul_shared_lock);
- LIST_REMOVE(em, threads);
-
- PROC_LOCK(p);
- p->p_emuldata = NULL;
- PROC_UNLOCK(p);
+ struct linux_emuldata *em;
- em->shared->refs--;
- if (em->shared->refs == 0) {
- EMUL_SHARED_WUNLOCK(&emul_shared_lock);
- free(em->shared, M_LINUX);
- } else
- EMUL_SHARED_WUNLOCK(&emul_shared_lock);
+ em = em_find(td);
+ if (em == NULL)
+ return;
+ td->td_emuldata = NULL;
- free(em, M_LINUX);
- }
+ LINUX_CTR1(exit, "thread dtor(%d)", em->em_tid);
- if (__predict_false(imgp->sysent == &elf_linux_sysvec)) {
- LIN_SDT_PROBE0(emul, proc_exec, return);
- }
+ free(em, M_TEMP);
}
void
@@ -397,30 +312,28 @@ linux_schedtail(struct thread *td)
int error = 0;
int *child_set_tid;
- p = td->td_proc;
-
- LIN_SDT_PROBE1(emul, linux_schedtail, entry, p);
+ LIN_SDT_PROBE1(emul, linux_schedtail, entry, td);
- /* find the emuldata */
- em = em_find(p, EMUL_DOLOCK);
+ p = td->td_proc;
+ em = em_find(td);
KASSERT(em != NULL, ("linux_schedtail: emuldata not found.\n"));
child_set_tid = em->child_set_tid;
- EMUL_UNLOCK(&emul_lock);
if (child_set_tid != NULL) {
- error = copyout(&p->p_pid, (int *)child_set_tid,
- sizeof(p->p_pid));
+ error = copyout(&em->em_tid, (int *)child_set_tid,
+ sizeof(em->em_tid));
+ LINUX_CTR4(clone, "schedtail(%d) %p stored %d error %d",
+ td->td_tid, child_set_tid, em->em_tid, error);
if (error != 0) {
LIN_SDT_PROBE1(emul, linux_schedtail, copyout_error,
error);
}
- }
+ } else
+ LINUX_CTR1(clone, "schedtail(%d)", em->em_tid);
LIN_SDT_PROBE0(emul, linux_schedtail, return);
-
- return;
}
int
@@ -430,45 +343,16 @@ linux_set_tid_address(struct thread *td, struct linux_set_tid_address_args *args
LIN_SDT_PROBE1(emul, linux_set_tid_address, entry, args->tidptr);
- /* find the emuldata */
- em = em_find(td->td_proc, EMUL_DOLOCK);
-
+ em = em_find(td);
KASSERT(em != NULL, ("set_tid_address: emuldata not found.\n"));
em->child_clear_tid = args->tidptr;
- td->td_retval[0] = td->td_proc->p_pid;
- EMUL_UNLOCK(&emul_lock);
+ td->td_retval[0] = em->em_tid;
- LIN_SDT_PROBE0(emul, linux_set_tid_address, return);
- return 0;
-}
-
-void
-linux_kill_threads(struct thread *td, int sig)
-{
- struct linux_emuldata *em, *td_em, *tmp_em;
- struct proc *sp;
-
- LIN_SDT_PROBE2(emul, linux_kill_threads, entry, td, sig);
-
- td_em = em_find(td->td_proc, EMUL_DONTLOCK);
+ LINUX_CTR3(set_tid_address, "tidptr(%d) %p, returns %d",
+ em->em_tid, args->tidptr, td->td_retval[0]);
- KASSERT(td_em != NULL, ("linux_kill_threads: emuldata not found.\n"));
-
- EMUL_SHARED_RLOCK(&emul_shared_lock);
- LIST_FOREACH_SAFE(em, &td_em->shared->threads, threads, tmp_em) {
- if (em->pid == td_em->pid)
- continue;
-
- sp = pfind(em->pid);
- if ((sp->p_flag & P_WEXIT) == 0)
- kern_psignal(sp, sig);
- PROC_UNLOCK(sp);
-
- LIN_SDT_PROBE1(emul, linux_kill_threads, kill, em->pid);
- }
- EMUL_SHARED_RUNLOCK(&emul_shared_lock);
-
- LIN_SDT_PROBE0(emul, linux_kill_threads, return);
+ LIN_SDT_PROBE0(emul, linux_set_tid_address, return);
+ return (0);
}
OpenPOWER on IntegriCloud