mirror of
https://github.com/torvalds/linux.git
synced 2024-12-28 05:41:55 +00:00
dcca2bde4f
We have had complaints where a threaded application is left in a bad state after one of it's threads is killed when we hit a VM: out_of_memory condition. Killing just one of the process threads can leave the application in a bad state, whereas killing the entire process group would allow for the application to restart, or be otherwise handled, and makes it very obvious that something has gone wrong. This change allows the entire process group to be taken down, rather than just the one thread. Signed-off-by: Will Schmidt <will_schmidt@vnet.ibm.com> Cc: Richard Henderson <rth@twiddle.net> Cc: Ivan Kokshaysky <ink@jurassic.park.msu.ru> Cc: Russell King <rmk@arm.linux.org.uk> Cc: Ian Molton <spyro@f2s.com> Cc: Haavard Skinnemoen <hskinnemoen@atmel.com> Cc: Mikael Starvik <starvik@axis.com> Cc: David Howells <dhowells@redhat.com> Cc: Andi Kleen <ak@suse.de> Cc: "Luck, Tony" <tony.luck@intel.com> Cc: Hirokazu Takata <takata@linux-m32r.org> Cc: Geert Uytterhoeven <geert@linux-m68k.org> Cc: Roman Zippel <zippel@linux-m68k.org> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Kyle McMartin <kyle@mcmartin.ca> Cc: Matthew Wilcox <willy@debian.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Heiko Carstens <heiko.carstens@de.ibm.com> Cc: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: Paul Mundt <lethal@linux-sh.org> Cc: Kazumoto Kojima <kkojima@rr.iij4u.or.jp> Cc: Richard Curnow <rc@rc0.org.uk> Cc: William Lee Irwin III <wli@holomorphy.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Chris Zankel <chris@zankel.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
242 lines
5.8 KiB
C
242 lines
5.8 KiB
C
/*
|
|
* linux/arch/alpha/mm/fault.c
|
|
*
|
|
* Copyright (C) 1995 Linus Torvalds
|
|
*/
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mm.h>
|
|
#include <asm/io.h>
|
|
|
|
#define __EXTERN_INLINE inline
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/tlbflush.h>
|
|
#undef __EXTERN_INLINE
|
|
|
|
#include <linux/signal.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/string.h>
|
|
#include <linux/types.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/mman.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <asm/system.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
extern void die_if_kernel(char *,struct pt_regs *,long, unsigned long *);
|
|
|
|
|
|
/*
|
|
* Force a new ASN for a task.
|
|
*/
|
|
|
|
#ifndef CONFIG_SMP
|
|
unsigned long last_asn = ASN_FIRST_VERSION;
|
|
#endif
|
|
|
|
void
|
|
__load_new_mm_context(struct mm_struct *next_mm)
|
|
{
|
|
unsigned long mmc;
|
|
struct pcb_struct *pcb;
|
|
|
|
mmc = __get_new_mm_context(next_mm, smp_processor_id());
|
|
next_mm->context[smp_processor_id()] = mmc;
|
|
|
|
pcb = ¤t_thread_info()->pcb;
|
|
pcb->asn = mmc & HARDWARE_ASN_MASK;
|
|
pcb->ptbr = ((unsigned long) next_mm->pgd - IDENT_ADDR) >> PAGE_SHIFT;
|
|
|
|
__reload_thread(pcb);
|
|
}
|
|
|
|
|
|
/*
|
|
* This routine handles page faults. It determines the address,
|
|
* and the problem, and then passes it off to handle_mm_fault().
|
|
*
|
|
* mmcsr:
|
|
* 0 = translation not valid
|
|
* 1 = access violation
|
|
* 2 = fault-on-read
|
|
* 3 = fault-on-execute
|
|
* 4 = fault-on-write
|
|
*
|
|
* cause:
|
|
* -1 = instruction fetch
|
|
* 0 = load
|
|
* 1 = store
|
|
*
|
|
* Registers $9 through $15 are saved in a block just prior to `regs' and
|
|
* are saved and restored around the call to allow exception code to
|
|
* modify them.
|
|
*/
|
|
|
|
/* Macro for exception fixup code to access integer registers. */
|
|
#define dpf_reg(r) \
|
|
(((unsigned long *)regs)[(r) <= 8 ? (r) : (r) <= 15 ? (r)-16 : \
|
|
(r) <= 18 ? (r)+8 : (r)-10])
|
|
|
|
asmlinkage void
|
|
do_page_fault(unsigned long address, unsigned long mmcsr,
|
|
long cause, struct pt_regs *regs)
|
|
{
|
|
struct vm_area_struct * vma;
|
|
struct mm_struct *mm = current->mm;
|
|
const struct exception_table_entry *fixup;
|
|
int fault, si_code = SEGV_MAPERR;
|
|
siginfo_t info;
|
|
|
|
/* As of EV6, a load into $31/$f31 is a prefetch, and never faults
|
|
(or is suppressed by the PALcode). Support that for older CPUs
|
|
by ignoring such an instruction. */
|
|
if (cause == 0) {
|
|
unsigned int insn;
|
|
__get_user(insn, (unsigned int __user *)regs->pc);
|
|
if ((insn >> 21 & 0x1f) == 0x1f &&
|
|
/* ldq ldl ldt lds ldg ldf ldwu ldbu */
|
|
(1ul << (insn >> 26) & 0x30f00001400ul)) {
|
|
regs->pc += 4;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* If we're in an interrupt context, or have no user context,
|
|
we must not take the fault. */
|
|
if (!mm || in_atomic())
|
|
goto no_context;
|
|
|
|
#ifdef CONFIG_ALPHA_LARGE_VMALLOC
|
|
if (address >= TASK_SIZE)
|
|
goto vmalloc_fault;
|
|
#endif
|
|
|
|
down_read(&mm->mmap_sem);
|
|
vma = find_vma(mm, address);
|
|
if (!vma)
|
|
goto bad_area;
|
|
if (vma->vm_start <= address)
|
|
goto good_area;
|
|
if (!(vma->vm_flags & VM_GROWSDOWN))
|
|
goto bad_area;
|
|
if (expand_stack(vma, address))
|
|
goto bad_area;
|
|
|
|
/* Ok, we have a good vm_area for this memory access, so
|
|
we can handle it. */
|
|
good_area:
|
|
si_code = SEGV_ACCERR;
|
|
if (cause < 0) {
|
|
if (!(vma->vm_flags & VM_EXEC))
|
|
goto bad_area;
|
|
} else if (!cause) {
|
|
/* Allow reads even for write-only mappings */
|
|
if (!(vma->vm_flags & (VM_READ | VM_WRITE)))
|
|
goto bad_area;
|
|
} else {
|
|
if (!(vma->vm_flags & VM_WRITE))
|
|
goto bad_area;
|
|
}
|
|
|
|
survive:
|
|
/* If for any reason at all we couldn't handle the fault,
|
|
make sure we exit gracefully rather than endlessly redo
|
|
the fault. */
|
|
fault = handle_mm_fault(mm, vma, address, cause > 0);
|
|
up_read(&mm->mmap_sem);
|
|
if (unlikely(fault & VM_FAULT_ERROR)) {
|
|
if (fault & VM_FAULT_OOM)
|
|
goto out_of_memory;
|
|
else if (fault & VM_FAULT_SIGBUS)
|
|
goto do_sigbus;
|
|
BUG();
|
|
}
|
|
if (fault & VM_FAULT_MAJOR)
|
|
current->maj_flt++;
|
|
else
|
|
current->min_flt++;
|
|
return;
|
|
|
|
/* Something tried to access memory that isn't in our memory map.
|
|
Fix it, but check if it's kernel or user first. */
|
|
bad_area:
|
|
up_read(&mm->mmap_sem);
|
|
|
|
if (user_mode(regs))
|
|
goto do_sigsegv;
|
|
|
|
no_context:
|
|
/* Are we prepared to handle this fault as an exception? */
|
|
if ((fixup = search_exception_tables(regs->pc)) != 0) {
|
|
unsigned long newpc;
|
|
newpc = fixup_exception(dpf_reg, fixup, regs->pc);
|
|
regs->pc = newpc;
|
|
return;
|
|
}
|
|
|
|
/* Oops. The kernel tried to access some bad page. We'll have to
|
|
terminate things with extreme prejudice. */
|
|
printk(KERN_ALERT "Unable to handle kernel paging request at "
|
|
"virtual address %016lx\n", address);
|
|
die_if_kernel("Oops", regs, cause, (unsigned long*)regs - 16);
|
|
do_exit(SIGKILL);
|
|
|
|
/* We ran out of memory, or some other thing happened to us that
|
|
made us unable to handle the page fault gracefully. */
|
|
out_of_memory:
|
|
if (is_init(current)) {
|
|
yield();
|
|
down_read(&mm->mmap_sem);
|
|
goto survive;
|
|
}
|
|
printk(KERN_ALERT "VM: killing process %s(%d)\n",
|
|
current->comm, current->pid);
|
|
if (!user_mode(regs))
|
|
goto no_context;
|
|
do_group_exit(SIGKILL);
|
|
|
|
do_sigbus:
|
|
/* Send a sigbus, regardless of whether we were in kernel
|
|
or user mode. */
|
|
info.si_signo = SIGBUS;
|
|
info.si_errno = 0;
|
|
info.si_code = BUS_ADRERR;
|
|
info.si_addr = (void __user *) address;
|
|
force_sig_info(SIGBUS, &info, current);
|
|
if (!user_mode(regs))
|
|
goto no_context;
|
|
return;
|
|
|
|
do_sigsegv:
|
|
info.si_signo = SIGSEGV;
|
|
info.si_errno = 0;
|
|
info.si_code = si_code;
|
|
info.si_addr = (void __user *) address;
|
|
force_sig_info(SIGSEGV, &info, current);
|
|
return;
|
|
|
|
#ifdef CONFIG_ALPHA_LARGE_VMALLOC
|
|
vmalloc_fault:
|
|
if (user_mode(regs))
|
|
goto do_sigsegv;
|
|
else {
|
|
/* Synchronize this task's top level page-table
|
|
with the "reference" page table from init. */
|
|
long index = pgd_index(address);
|
|
pgd_t *pgd, *pgd_k;
|
|
|
|
pgd = current->active_mm->pgd + index;
|
|
pgd_k = swapper_pg_dir + index;
|
|
if (!pgd_present(*pgd) && pgd_present(*pgd_k)) {
|
|
pgd_val(*pgd) = pgd_val(*pgd_k);
|
|
return;
|
|
}
|
|
goto no_context;
|
|
}
|
|
#endif
|
|
}
|