Commit 502bff06 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] strict overcommit

Alan's overcommit patch, brought to 2.5 by Robert Love.

Can't say I've tested its functionality at all, but it doesn't crash,
it has been in -ac and RH kernels for some time and I haven't observed
any of its functions on profiles.

"So what is strict VM overcommit?  We introduce new overcommit
 policies that attempt to never succeed an allocation that can not be
 fulfilled by the backing store and consequently never OOM.  This is
 achieved through strict accounting of the committed address space and
 a policy to allow/refuse allocations based on that accounting.

 In the strictest of modes, it should be impossible to allocate more
 memory than available and impossible to OOM.  All memory failures
 should be pushed down to the allocation routines -- malloc, mmap, etc.

 The new modes are available via sysctl (same as before).  See
 Documentation/vm/overcommit-accounting for more information."
parent a4b065fa
...@@ -78,7 +78,21 @@ This feature can be very useful because there are a lot of ...@@ -78,7 +78,21 @@ This feature can be very useful because there are a lot of
programs that malloc() huge amounts of memory "just-in-case" programs that malloc() huge amounts of memory "just-in-case"
and don't use much of it. and don't use much of it.
Look at: mm/mmap.c::vm_enough_memory() for more information. A value of 2 introduces a new "strict overcommit" policy
that attempts to prevent any overcommit of memory.
The default value is 0.
See Documentation/vm/overcommit-accounting and
mm/mmap.c::vm_enough_memory() for more information.
==============================================================
overcommit_ratio:
When overcommit_memory is set to 2, the committed address
space is not permitted to exceed swap plus this percentage
of physical RAM. See above.
============================================================== ==============================================================
......
The Linux kernel supports four overcommit handling modes
0 - Heuristic overcommit handling. Obvious overcommits of
address space are refused. Used for a typical system. It
ensures a seriously wild allocation fails while allowing
overcommit to reduce swap usage. This is the default.
1 - No overcommit handling. Appropriate for some scientific
applications.
2 - (NEW) strict overcommit. The total address space commit
for the system is not permitted to exceed swap + a
configurable percentage (default is 50) of physical RAM.
Depending on the percentage you use, in most situations
this means a process will not be killed while accessing
pages but will receive errors on memory allocation as
appropriate.
The overcommit policy is set via the sysctl `vm.overcommit_memory'.
The overcommit percentage is set via `vm.overcommit_ratio'.
Gotchas
-------
The C language stack growth does an implicit mremap. If you want absolute
guarantees and run close to the edge you MUST mmap your stack for the
largest size you think you will need. For typical stack usage is does
not matter much but its a corner case if you really really care
In mode 2 the MAP_NORESERVE flag is ignored.
How It Works
------------
The overcommit is based on the following rules
For a file backed map
SHARED or READ only - 0 cost (the file is the map not swap)
WRITABLE SHARED - size of mapping per instance
For a direct map
SHARED or READ only - size of mapping
PRIVATE WRITEABLE - size of mapping per instance
Additional accounting
Pages made writable copies by mmap
shmfs memory drawn from the same pool
Status
------
o We account mmap memory mappings
o We account mprotect changes in commit
o We account mremap changes in size
o We account brk
o We account munmap
o We report the commit status in /proc
o Account and check on fork
o Review stack handling/building on exec
o SHMfs accounting
o Implement actual limit enforcement
To Do
-----
o Account ptrace pages (this is hard)
o Account for shared anonymous mappings properly
- right now we account them per instance
...@@ -313,8 +313,13 @@ int setup_arg_pages(struct linux_binprm *bprm) ...@@ -313,8 +313,13 @@ int setup_arg_pages(struct linux_binprm *bprm)
mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
if (!mpnt) if (!mpnt)
return -ENOMEM; return -ENOMEM;
if (!vm_enough_memory((STACK_TOP - (PAGE_MASK & (unsigned long) bprm->p))>>PAGE_SHIFT)) {
kmem_cache_free(vm_area_cachep, mpnt);
return -ENOMEM;
}
down_write(&current->mm->mmap_sem); down_write(&current->mm->mmap_sem);
{ {
mpnt->vm_mm = current->mm; mpnt->vm_mm = current->mm;
......
...@@ -126,11 +126,13 @@ static int uptime_read_proc(char *page, char **start, off_t off, ...@@ -126,11 +126,13 @@ static int uptime_read_proc(char *page, char **start, off_t off,
return proc_calc_metrics(page, start, off, count, eof, len); return proc_calc_metrics(page, start, off, count, eof, len);
} }
extern atomic_t vm_committed_space;
static int meminfo_read_proc(char *page, char **start, off_t off, static int meminfo_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data) int count, int *eof, void *data)
{ {
struct sysinfo i; struct sysinfo i;
int len; int len, committed;
struct page_state ps; struct page_state ps;
get_page_state(&ps); get_page_state(&ps);
...@@ -140,6 +142,7 @@ static int meminfo_read_proc(char *page, char **start, off_t off, ...@@ -140,6 +142,7 @@ static int meminfo_read_proc(char *page, char **start, off_t off,
#define K(x) ((x) << (PAGE_SHIFT - 10)) #define K(x) ((x) << (PAGE_SHIFT - 10))
si_meminfo(&i); si_meminfo(&i);
si_swapinfo(&i); si_swapinfo(&i);
committed = atomic_read(&vm_committed_space);
/* /*
* Tagged format, for easy grepping and expansion. * Tagged format, for easy grepping and expansion.
...@@ -160,6 +163,7 @@ static int meminfo_read_proc(char *page, char **start, off_t off, ...@@ -160,6 +163,7 @@ static int meminfo_read_proc(char *page, char **start, off_t off,
"SwapFree: %8lu kB\n" "SwapFree: %8lu kB\n"
"Dirty: %8lu kB\n" "Dirty: %8lu kB\n"
"Writeback: %8lu kB\n" "Writeback: %8lu kB\n"
"Committed_AS: %8u kB\n"
"PageTables: %8lu kB\n" "PageTables: %8lu kB\n"
"ReverseMaps: %8lu\n", "ReverseMaps: %8lu\n",
K(i.totalram), K(i.totalram),
...@@ -177,6 +181,7 @@ static int meminfo_read_proc(char *page, char **start, off_t off, ...@@ -177,6 +181,7 @@ static int meminfo_read_proc(char *page, char **start, off_t off,
K(i.freeswap), K(i.freeswap),
K(ps.nr_dirty), K(ps.nr_dirty),
K(ps.nr_writeback), K(ps.nr_writeback),
K(committed),
K(ps.nr_page_table_pages), K(ps.nr_page_table_pages),
ps.nr_reverse_maps ps.nr_reverse_maps
); );
......
...@@ -103,8 +103,9 @@ struct vm_area_struct { ...@@ -103,8 +103,9 @@ struct vm_area_struct {
#define VM_DONTCOPY 0x00020000 /* Do not copy this vma on fork */ #define VM_DONTCOPY 0x00020000 /* Do not copy this vma on fork */
#define VM_DONTEXPAND 0x00040000 /* Cannot expand with mremap() */ #define VM_DONTEXPAND 0x00040000 /* Cannot expand with mremap() */
#define VM_RESERVED 0x00080000 /* Don't unmap it from swap_out */ #define VM_RESERVED 0x00080000 /* Don't unmap it from swap_out */
#define VM_ACCOUNT 0x00100000 /* Is a VM accounted object */
#define VM_STACK_FLAGS (0x00000100 | VM_DATA_DEFAULT_FLAGS) #define VM_STACK_FLAGS (0x00000100 | VM_DATA_DEFAULT_FLAGS | VM_ACCOUNT)
#define VM_READHINTMASK (VM_SEQ_READ | VM_RAND_READ) #define VM_READHINTMASK (VM_SEQ_READ | VM_RAND_READ)
#define VM_ClearReadHint(v) (v)->vm_flags &= ~VM_READHINTMASK #define VM_ClearReadHint(v) (v)->vm_flags &= ~VM_READHINTMASK
...@@ -429,7 +430,7 @@ static inline unsigned long do_mmap(struct file *file, unsigned long addr, ...@@ -429,7 +430,7 @@ static inline unsigned long do_mmap(struct file *file, unsigned long addr,
return ret; return ret;
} }
extern int do_munmap(struct mm_struct *, unsigned long, size_t); extern int do_munmap(struct mm_struct *, unsigned long, size_t, int);
extern unsigned long do_brk(unsigned long, unsigned long); extern unsigned long do_brk(unsigned long, unsigned long);
...@@ -471,31 +472,8 @@ void page_cache_readahead(struct file *file, unsigned long offset); ...@@ -471,31 +472,8 @@ void page_cache_readahead(struct file *file, unsigned long offset);
void page_cache_readaround(struct file *file, unsigned long offset); void page_cache_readaround(struct file *file, unsigned long offset);
void handle_ra_miss(struct file *file); void handle_ra_miss(struct file *file);
/* vma is the first one with address < vma->vm_end, /* Do stack extension */
* and even address < vma->vm_start. Have to extend vma. */ extern int expand_stack(struct vm_area_struct * vma, unsigned long address);
static inline int expand_stack(struct vm_area_struct * vma, unsigned long address)
{
unsigned long grow;
/*
* vma->vm_start/vm_end cannot change under us because the caller is required
* to hold the mmap_sem in write mode. We need to get the spinlock only
* before relocating the vma range ourself.
*/
address &= PAGE_MASK;
grow = (vma->vm_start - address) >> PAGE_SHIFT;
if (vma->vm_end - address > current->rlim[RLIMIT_STACK].rlim_cur ||
((vma->vm_mm->total_vm + grow) << PAGE_SHIFT) > current->rlim[RLIMIT_AS].rlim_cur)
return -ENOMEM;
spin_lock(&vma->vm_mm->page_table_lock);
vma->vm_start = address;
vma->vm_pgoff -= grow;
vma->vm_mm->total_vm += grow;
if (vma->vm_flags & VM_LOCKED)
vma->vm_mm->locked_vm += grow;
spin_unlock(&vma->vm_mm->page_table_lock);
return 0;
}
/* Look up the first VMA which satisfies addr < vm_end, NULL if none. */ /* Look up the first VMA which satisfies addr < vm_end, NULL if none. */
extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr); extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);
......
...@@ -6,4 +6,7 @@ ...@@ -6,4 +6,7 @@
#define MREMAP_MAYMOVE 1 #define MREMAP_MAYMOVE 1
#define MREMAP_FIXED 2 #define MREMAP_FIXED 2
extern int vm_enough_memory(long pages);
extern void vm_unacct_memory(long pages);
#endif /* _LINUX_MMAN_H */ #endif /* _LINUX_MMAN_H */
...@@ -149,6 +149,7 @@ enum ...@@ -149,6 +149,7 @@ enum
VM_DIRTY_WB_CS=14, /* dirty_writeback_centisecs */ VM_DIRTY_WB_CS=14, /* dirty_writeback_centisecs */
VM_DIRTY_EXPIRE_CS=15, /* dirty_expire_centisecs */ VM_DIRTY_EXPIRE_CS=15, /* dirty_expire_centisecs */
VM_NR_PDFLUSH_THREADS=16, /* nr_pdflush_threads */ VM_NR_PDFLUSH_THREADS=16, /* nr_pdflush_threads */
VM_OVERCOMMIT_RATIO=17, /* percent of RAM to allow overcommit in */
}; };
......
...@@ -671,7 +671,7 @@ asmlinkage long sys_shmdt (char *shmaddr) ...@@ -671,7 +671,7 @@ asmlinkage long sys_shmdt (char *shmaddr)
shmdnext = shmd->vm_next; shmdnext = shmd->vm_next;
if (shmd->vm_ops == &shm_vm_ops if (shmd->vm_ops == &shm_vm_ops
&& shmd->vm_start - (shmd->vm_pgoff << PAGE_SHIFT) == (ulong) shmaddr) { && shmd->vm_start - (shmd->vm_pgoff << PAGE_SHIFT) == (ulong) shmaddr) {
do_munmap(mm, shmd->vm_start, shmd->vm_end - shmd->vm_start); do_munmap(mm, shmd->vm_start, shmd->vm_end - shmd->vm_start, 1);
retval = 0; retval = 0;
} }
} }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/personality.h> #include <linux/personality.h>
#include <linux/file.h> #include <linux/file.h>
#include <linux/binfmts.h> #include <linux/binfmts.h>
#include <linux/mman.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/security.h> #include <linux/security.h>
...@@ -181,6 +182,7 @@ static inline int dup_mmap(struct mm_struct * mm) ...@@ -181,6 +182,7 @@ static inline int dup_mmap(struct mm_struct * mm)
{ {
struct vm_area_struct * mpnt, *tmp, **pprev; struct vm_area_struct * mpnt, *tmp, **pprev;
int retval; int retval;
unsigned long charge = 0;
flush_cache_mm(current->mm); flush_cache_mm(current->mm);
mm->locked_vm = 0; mm->locked_vm = 0;
...@@ -208,6 +210,17 @@ static inline int dup_mmap(struct mm_struct * mm) ...@@ -208,6 +210,17 @@ static inline int dup_mmap(struct mm_struct * mm)
retval = -ENOMEM; retval = -ENOMEM;
if(mpnt->vm_flags & VM_DONTCOPY) if(mpnt->vm_flags & VM_DONTCOPY)
continue; continue;
/*
* FIXME: shared writable map accounting should be one off
*/
if (mpnt->vm_flags & VM_ACCOUNT) {
unsigned int len = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
if (!vm_enough_memory(len))
goto fail_nomem;
charge += len;
}
tmp = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); tmp = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
if (!tmp) if (!tmp)
goto fail_nomem; goto fail_nomem;
...@@ -248,9 +261,12 @@ static inline int dup_mmap(struct mm_struct * mm) ...@@ -248,9 +261,12 @@ static inline int dup_mmap(struct mm_struct * mm)
retval = 0; retval = 0;
build_mmap_rb(mm); build_mmap_rb(mm);
fail_nomem: out:
flush_tlb_mm(current->mm); flush_tlb_mm(current->mm);
return retval; return retval;
fail_nomem:
vm_unacct_memory(charge);
goto out;
} }
spinlock_t mmlist_lock __cacheline_aligned_in_smp = SPIN_LOCK_UNLOCKED; spinlock_t mmlist_lock __cacheline_aligned_in_smp = SPIN_LOCK_UNLOCKED;
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
extern int panic_timeout; extern int panic_timeout;
extern int C_A_D; extern int C_A_D;
extern int sysctl_overcommit_memory; extern int sysctl_overcommit_memory;
extern int sysctl_overcommit_ratio;
extern int max_threads; extern int max_threads;
extern atomic_t nr_queued_signals; extern atomic_t nr_queued_signals;
extern int max_queued_signals; extern int max_queued_signals;
...@@ -268,6 +269,9 @@ static int one_hundred = 100; ...@@ -268,6 +269,9 @@ static int one_hundred = 100;
static ctl_table vm_table[] = { static ctl_table vm_table[] = {
{VM_OVERCOMMIT_MEMORY, "overcommit_memory", &sysctl_overcommit_memory, {VM_OVERCOMMIT_MEMORY, "overcommit_memory", &sysctl_overcommit_memory,
sizeof(sysctl_overcommit_memory), 0644, NULL, &proc_dointvec}, sizeof(sysctl_overcommit_memory), 0644, NULL, &proc_dointvec},
{VM_OVERCOMMIT_RATIO, "overcommit_ratio",
&sysctl_overcommit_ratio, sizeof(sysctl_overcommit_ratio), 0644,
NULL, &proc_dointvec},
{VM_PAGERDAEMON, "kswapd", {VM_PAGERDAEMON, "kswapd",
&pager_daemon, sizeof(pager_daemon_t), 0644, NULL, &proc_dointvec}, &pager_daemon, sizeof(pager_daemon_t), 0644, NULL, &proc_dointvec},
{VM_PAGE_CLUSTER, "page-cluster", {VM_PAGE_CLUSTER, "page-cluster",
......
This diff is collapsed.
/* /*
* linux/mm/mprotect.c * mm/mprotect.c
* *
* (C) Copyright 1994 Linus Torvalds * (C) Copyright 1994 Linus Torvalds
*
* Address space accounting code <alan@redhat.com>
* (C) Copyright 2002 Red Hat Inc, All Rights Reserved
*/ */
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -248,6 +251,7 @@ static int mprotect_fixup(struct vm_area_struct * vma, struct vm_area_struct ** ...@@ -248,6 +251,7 @@ static int mprotect_fixup(struct vm_area_struct * vma, struct vm_area_struct **
{ {
pgprot_t newprot; pgprot_t newprot;
int error; int error;
unsigned long charged = 0;
if (newflags == vma->vm_flags) { if (newflags == vma->vm_flags) {
*pprev = vma; *pprev = vma;
...@@ -264,9 +268,18 @@ static int mprotect_fixup(struct vm_area_struct * vma, struct vm_area_struct ** ...@@ -264,9 +268,18 @@ static int mprotect_fixup(struct vm_area_struct * vma, struct vm_area_struct **
else else
error = mprotect_fixup_middle(vma, pprev, start, end, newflags, newprot); error = mprotect_fixup_middle(vma, pprev, start, end, newflags, newprot);
if (error) if (error) {
if (newflags & PROT_WRITE)
vm_unacct_memory(charged);
return error; return error;
}
/*
* Delayed accounting for reduction of memory use - done last to
* avoid allocation races
*/
if (charged && !(newflags & PROT_WRITE))
vm_unacct_memory(charged);
change_protection(vma, start, end, newprot); change_protection(vma, start, end, newprot);
return 0; return 0;
} }
......
/* /*
* linux/mm/remap.c * mm/remap.c
* *
* (C) Copyright 1996 Linus Torvalds * (C) Copyright 1996 Linus Torvalds
*
* Address space accounting code <alan@redhat.com>
* (C) Copyright 2002 Red Hat Inc, All Rights Reserved
*/ */
#include <linux/mm.h> #include <linux/mm.h>
...@@ -18,8 +21,6 @@ ...@@ -18,8 +21,6 @@
#include <asm/cacheflush.h> #include <asm/cacheflush.h>
#include <asm/tlbflush.h> #include <asm/tlbflush.h>
extern int vm_enough_memory(long pages);
static inline pte_t *get_one_pte_map_nested(struct mm_struct *mm, unsigned long addr) static inline pte_t *get_one_pte_map_nested(struct mm_struct *mm, unsigned long addr)
{ {
pgd_t * pgd; pgd_t * pgd;
...@@ -209,7 +210,11 @@ static inline unsigned long move_vma(struct vm_area_struct * vma, ...@@ -209,7 +210,11 @@ static inline unsigned long move_vma(struct vm_area_struct * vma,
new_vma->vm_ops->open(new_vma); new_vma->vm_ops->open(new_vma);
insert_vm_struct(current->mm, new_vma); insert_vm_struct(current->mm, new_vma);
} }
do_munmap(current->mm, addr, old_len); /*
* The old VMA has been accounted for,
* don't double account
*/
do_munmap(current->mm, addr, old_len, 0);
current->mm->total_vm += new_len >> PAGE_SHIFT; current->mm->total_vm += new_len >> PAGE_SHIFT;
if (new_vma->vm_flags & VM_LOCKED) { if (new_vma->vm_flags & VM_LOCKED) {
current->mm->locked_vm += new_len >> PAGE_SHIFT; current->mm->locked_vm += new_len >> PAGE_SHIFT;
...@@ -224,6 +229,8 @@ static inline unsigned long move_vma(struct vm_area_struct * vma, ...@@ -224,6 +229,8 @@ static inline unsigned long move_vma(struct vm_area_struct * vma,
return -ENOMEM; return -ENOMEM;
} }
extern int sysctl_overcommit_memory; /* FIXME!! */
/* /*
* Expand (or shrink) an existing mapping, potentially moving it at the * Expand (or shrink) an existing mapping, potentially moving it at the
* same time (controlled by the MREMAP_MAYMOVE flag and available VM space) * same time (controlled by the MREMAP_MAYMOVE flag and available VM space)
...@@ -237,6 +244,7 @@ unsigned long do_mremap(unsigned long addr, ...@@ -237,6 +244,7 @@ unsigned long do_mremap(unsigned long addr,
{ {
struct vm_area_struct *vma; struct vm_area_struct *vma;
unsigned long ret = -EINVAL; unsigned long ret = -EINVAL;
unsigned long charged = 0;
if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE)) if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE))
goto out; goto out;
...@@ -266,16 +274,17 @@ unsigned long do_mremap(unsigned long addr, ...@@ -266,16 +274,17 @@ unsigned long do_mremap(unsigned long addr,
if ((addr <= new_addr) && (addr+old_len) > new_addr) if ((addr <= new_addr) && (addr+old_len) > new_addr)
goto out; goto out;
do_munmap(current->mm, new_addr, new_len); do_munmap(current->mm, new_addr, new_len, 1);
} }
/* /*
* Always allow a shrinking remap: that just unmaps * Always allow a shrinking remap: that just unmaps
* the unnecessary pages.. * the unnecessary pages..
* do_munmap does all the needed commit accounting
*/ */
ret = addr; ret = addr;
if (old_len >= new_len) { if (old_len >= new_len) {
do_munmap(current->mm, addr+new_len, old_len - new_len); do_munmap(current->mm, addr+new_len, old_len - new_len, 1);
if (!(flags & MREMAP_FIXED) || (new_addr == addr)) if (!(flags & MREMAP_FIXED) || (new_addr == addr))
goto out; goto out;
} }
...@@ -305,11 +314,14 @@ unsigned long do_mremap(unsigned long addr, ...@@ -305,11 +314,14 @@ unsigned long do_mremap(unsigned long addr,
if ((current->mm->total_vm << PAGE_SHIFT) + (new_len - old_len) if ((current->mm->total_vm << PAGE_SHIFT) + (new_len - old_len)
> current->rlim[RLIMIT_AS].rlim_cur) > current->rlim[RLIMIT_AS].rlim_cur)
goto out; goto out;
/* Private writable mapping? Check memory availability.. */
if ((vma->vm_flags & (VM_SHARED | VM_WRITE)) == VM_WRITE && if (sysctl_overcommit_memory > 1)
!(flags & MAP_NORESERVE) && flags &= ~MAP_NORESERVE;
!vm_enough_memory((new_len - old_len) >> PAGE_SHIFT)) if (vma->vm_flags & VM_ACCOUNT) {
goto out; charged = (new_len - old_len) >> PAGE_SHIFT;
if (!vm_enough_memory(charged))
goto out_nc;
}
/* old_len exactly to the end of the area.. /* old_len exactly to the end of the area..
* And we're not relocating the area. * And we're not relocating the area.
...@@ -356,6 +368,9 @@ unsigned long do_mremap(unsigned long addr, ...@@ -356,6 +368,9 @@ unsigned long do_mremap(unsigned long addr,
ret = move_vma(vma, addr, old_len, new_len, new_addr); ret = move_vma(vma, addr, old_len, new_len, new_addr);
} }
out: out:
if (ret & ~PAGE_MASK)
vm_unacct_memory(charged);
out_nc:
return ret; return ret;
} }
......
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
* 2000 Transmeta Corp. * 2000 Transmeta Corp.
* 2000-2001 Christoph Rohland * 2000-2001 Christoph Rohland
* 2000-2001 SAP AG * 2000-2001 SAP AG
* * 2002 Red Hat Inc.
*
* This file is released under the GPL. * This file is released under the GPL.
*/ */
...@@ -21,6 +22,7 @@ ...@@ -21,6 +22,7 @@
#include <linux/devfs_fs_kernel.h> #include <linux/devfs_fs_kernel.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/mman.h>
#include <linux/file.h> #include <linux/file.h>
#include <linux/swap.h> #include <linux/swap.h>
#include <linux/pagemap.h> #include <linux/pagemap.h>
...@@ -358,10 +360,41 @@ static void shmem_truncate (struct inode * inode) ...@@ -358,10 +360,41 @@ static void shmem_truncate (struct inode * inode)
up(&info->sem); up(&info->sem);
} }
static int shmem_notify_change(struct dentry * dentry, struct iattr *attr)
{
struct inode *inode = dentry->d_inode;
int error;
if (attr->ia_valid & ATTR_SIZE) {
/*
* Account swap file usage based on new file size
*/
long change;
change = ((attr->ia_size + PAGE_SIZE - 1) >> PAGE_SHIFT) -
((inode->i_size + PAGE_SIZE - 1 ) >> PAGE_SHIFT);
if (attr->ia_size > inode->i_size) {
if (!vm_enough_memory(change))
return -ENOMEM;
} else
vm_unacct_memory(-change);
}
error = inode_change_ok(inode, attr);
if (!error)
error = inode_setattr(inode, attr);
return error;
}
static void shmem_delete_inode(struct inode * inode) static void shmem_delete_inode(struct inode * inode)
{ {
struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
vm_unacct_memory((inode->i_size) >> PAGE_SHIFT);
inode->i_size = 0; inode->i_size = 0;
if (inode->i_op->truncate == shmem_truncate){ if (inode->i_op->truncate == shmem_truncate){
spin_lock (&shmem_ilock); spin_lock (&shmem_ilock);
...@@ -824,6 +857,7 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos) ...@@ -824,6 +857,7 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
unsigned long written; unsigned long written;
long status; long status;
int err; int err;
loff_t maxpos;
if ((ssize_t) count < 0) if ((ssize_t) count < 0)
return -EINVAL; return -EINVAL;
...@@ -836,12 +870,12 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos) ...@@ -836,12 +870,12 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
pos = *ppos; pos = *ppos;
err = -EINVAL; err = -EINVAL;
if (pos < 0) if (pos < 0)
goto out; goto out_nc;
err = file->f_error; err = file->f_error;
if (err) { if (err) {
file->f_error = 0; file->f_error = 0;
goto out; goto out_nc;
} }
written = 0; written = 0;
...@@ -849,6 +883,15 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos) ...@@ -849,6 +883,15 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
if (file->f_flags & O_APPEND) if (file->f_flags & O_APPEND)
pos = inode->i_size; pos = inode->i_size;
maxpos = inode->i_size;
if (pos + count > inode->i_size) {
maxpos = pos + count;
if (!vm_enough_memory((maxpos - inode->i_size) >> PAGE_SHIFT)) {
err = -ENOMEM;
goto out_nc;
}
}
/* /*
* Check whether we've reached the file size limit. * Check whether we've reached the file size limit.
*/ */
...@@ -938,6 +981,10 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos) ...@@ -938,6 +981,10 @@ shmem_file_write(struct file *file,const char *buf,size_t count,loff_t *ppos)
err = written ? written : status; err = written ? written : status;
out: out:
/* Short writes give back address space */
if (inode->i_size != maxpos)
vm_unacct_memory((maxpos - inode->i_size) >> PAGE_SHIFT);
out_nc:
up(&inode->i_sem); up(&inode->i_sem);
return err; return err;
fail_write: fail_write:
...@@ -1477,6 +1524,7 @@ static struct file_operations shmem_file_operations = { ...@@ -1477,6 +1524,7 @@ static struct file_operations shmem_file_operations = {
static struct inode_operations shmem_inode_operations = { static struct inode_operations shmem_inode_operations = {
truncate: shmem_truncate, truncate: shmem_truncate,
setattr: shmem_notify_change,
}; };
static struct inode_operations shmem_dir_inode_operations = { static struct inode_operations shmem_dir_inode_operations = {
...@@ -1600,12 +1648,11 @@ module_exit(exit_shmem_fs) ...@@ -1600,12 +1648,11 @@ module_exit(exit_shmem_fs)
*/ */
struct file *shmem_file_setup(char * name, loff_t size) struct file *shmem_file_setup(char * name, loff_t size)
{ {
int error; int error = -ENOMEM;
struct file *file; struct file *file;
struct inode * inode; struct inode * inode;
struct dentry *dentry, *root; struct dentry *dentry, *root;
struct qstr this; struct qstr this;
int vm_enough_memory(long pages);
if (size > (unsigned long long) SHMEM_MAX_BLOCKS << PAGE_CACHE_SHIFT) if (size > (unsigned long long) SHMEM_MAX_BLOCKS << PAGE_CACHE_SHIFT)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
...@@ -1619,7 +1666,7 @@ struct file *shmem_file_setup(char * name, loff_t size) ...@@ -1619,7 +1666,7 @@ struct file *shmem_file_setup(char * name, loff_t size)
root = shm_mnt->mnt_root; root = shm_mnt->mnt_root;
dentry = d_alloc(root, &this); dentry = d_alloc(root, &this);
if (!dentry) if (!dentry)
return ERR_PTR(-ENOMEM); goto put_memory;
error = -ENFILE; error = -ENFILE;
file = get_empty_filp(); file = get_empty_filp();
...@@ -1645,6 +1692,8 @@ struct file *shmem_file_setup(char * name, loff_t size) ...@@ -1645,6 +1692,8 @@ struct file *shmem_file_setup(char * name, loff_t size)
put_filp(file); put_filp(file);
put_dentry: put_dentry:
dput (dentry); dput (dentry);
put_memory:
vm_unacct_memory((size) >> PAGE_CACHE_SHIFT);
return ERR_PTR(error); return ERR_PTR(error);
} }
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment