> Date: Mon,  7 Feb 2022 11:12:50 +0000
> From: Klemens Nanni <k...@openbsd.org>
> 
> On Thu, Jan 27, 2022 at 11:11:48AM +0000, Klemens Nanni wrote:
> > On Tue, Jan 25, 2022 at 07:54:52AM +0000, Klemens Nanni wrote:
> > > > Could we use a vm_map_assert_locked() or something similar that reflect
> > > > the exclusive or shared (read) lock comments?  I don't trust comments.
> > > > It's too easy to miss a lock in a code path.
> > > 
> > > This survives desktop usage, running regress and building kernels on
> > > amd64.
> > > 
> > > I'll throw it at sparc64 soon.
> > > 
> > > > 
> > > > > So annotate functions using `size' wrt. the required lock.
> > 
> > Here's a better diff that asserts for read, write or any type of vm_map
> > lock.
> > 
> > vm_map_lock() and vm_map_lock_read() are used for exclusive and shared
> > access respectively;  unless I missed something, no code path is purely
> > protected by vm_map_lock_read() alone, i.e. functions called with a read
> > lock held by the callee are also called with a write lock elsewhere.
> > 
> > That means my new vm_map_assert_locked_read() is currently unused, but
> > vm_map_assert_locked_any() and vm_map_assert_locked_write() are now used
> > to validate existing function comments as well as a few new places.
> > 
> > amd64 and sparc64 are happy with this, both daily drivers as well as
> > build/regress machines.
> 
> amd64 package bulk (thanks naddy), amd64 regress (thanks bluhm) and
> sparc64 base builds/regress are happy with the diff below, the
> "uvm_unmap_kill_entry(): unwire with map lock held" locking diff on
> tech@ and all three of mmap, munmap, mprotect unlocked.
> 
> > 
> > I'd appreciate more tests and reports about running with this diff and
> > mmap(2) unlocked.
> 
> Here's that tested diff which a) follows rwlock(9)'s naming pattern for
> the assert functions and b) syncs lock related comments with NetBSD.
> 
> It only adds and (updates) comments;  no locking or logic changes.
> 
> I'd like to get these asserts committed soon (after the required
> uvm_unmap_kill_entry() vm_map lock diff) before moving forward with
> other (un)locking diffs.

So there is the existing UVM_MAP_REQ_WRITE().  Compared to
vm_map_assert_wrlock() it checks the reference count of the map.  I
think that's questionable, so these should probably be replaced by
vm_map_assert_wrlock().

Also...

> Index: uvm_map.c
> ===================================================================
> RCS file: /cvs/src/sys/uvm/uvm_map.c,v
> retrieving revision 1.282
> diff -u -p -r1.282 uvm_map.c
> --- uvm_map.c 21 Dec 2021 22:21:32 -0000      1.282
> +++ uvm_map.c 3 Feb 2022 15:46:44 -0000
> @@ -498,6 +498,7 @@ uvm_mapent_addr_remove(struct vm_map *ma
>  void
>  uvm_map_reference(struct vm_map *map)
>  {
> +     vm_map_assert_unlocked(map);

I don't understand this one.  Grabbing a reference should be safe at
any time as long as you already hold a reference.

>       atomic_inc_int(&map->ref_count);
>  }
>  
> @@ -805,6 +806,8 @@ uvm_map_sel_limits(vaddr_t *min, vaddr_t
>   * Fills in *start_ptr and *end_ptr to be the first and last entry describing
>   * the space.
>   * If called with prefilled *start_ptr and *end_ptr, they are to be correct.
> + *
> + * map must be at least read-locked.
>   */
>  int
>  uvm_map_isavail(struct vm_map *map, struct uvm_addr_state *uaddr,
> @@ -815,6 +818,8 @@ uvm_map_isavail(struct vm_map *map, stru
>       struct uvm_map_addr *atree;
>       struct vm_map_entry *i, *i_end;
>  
> +     vm_map_assert_anylock(map);
> +
>       if (addr + sz < addr)
>               return 0;
>  
> @@ -892,6 +897,8 @@ uvm_map_isavail(struct vm_map *map, stru
>  /*
>   * Invoke each address selector until an address is found.
>   * Will not invoke uaddr_exe.
> + *
> + * => caller must at least have read-locked map
>   */
>  int
>  uvm_map_findspace(struct vm_map *map, struct vm_map_entry**first,
> @@ -901,6 +908,8 @@ uvm_map_findspace(struct vm_map *map, st
>       struct uvm_addr_state *uaddr;
>       int i;
>  
> +     vm_map_assert_anylock(map);
> +
>       /*
>        * Allocation for sz bytes at any address,
>        * using the addr selectors in order.
> @@ -1160,7 +1169,7 @@ unlock:
>   * uvm_map: establish a valid mapping in map
>   *
>   * => *addr and sz must be a multiple of PAGE_SIZE.
> - * => map must be unlocked.
> + * => map must be unlocked (we will lock it)
>   * => <uobj,uoffset> value meanings (4 cases):
>   *   [1] <NULL,uoffset>              == uoffset is a hint for PMAP_PREFER
>   *   [2] <NULL,UVM_UNKNOWN_OFFSET>   == don't PMAP_PREFER
> @@ -1821,6 +1830,8 @@ boolean_t
>  uvm_map_lookup_entry(struct vm_map *map, vaddr_t address,
>      struct vm_map_entry **entry)
>  {
> +     vm_map_assert_anylock(map);
> +
>       *entry = uvm_map_entrybyaddr(&map->addr, address);
>       return *entry != NULL && !UVM_ET_ISHOLE(*entry) &&
>           (*entry)->start <= address && (*entry)->end > address;
> @@ -2206,6 +2217,8 @@ uvm_unmap_kill_entry(struct vm_map *map,
>   * If remove_holes, then remove ET_HOLE entries as well.
>   * If markfree, entry will be properly marked free, otherwise, no replacement
>   * entry will be put in the tree (corrupting the tree).
> + *
> + * => map must be locked by caller
>   */
>  void
>  uvm_unmap_remove(struct vm_map *map, vaddr_t start, vaddr_t end,
> @@ -2214,6 +2227,8 @@ uvm_unmap_remove(struct vm_map *map, vad
>  {
>       struct vm_map_entry *prev_hint, *next, *entry;
>  
> +     vm_map_assert_wrlock(map);
> +
>       start = MAX(start, map->min_offset);
>       end = MIN(end, map->max_offset);
>       if (start >= end)
> @@ -2304,6 +2319,8 @@ uvm_map_pageable_pgon(struct vm_map *map
>  {
>       struct vm_map_entry *iter;
>  
> +     vm_map_assert_wrlock(map);
> +
>       for (iter = first; iter != end;
>           iter = RBT_NEXT(uvm_map_addr, iter)) {
>               KDASSERT(iter->start >= start_addr && iter->end <= end_addr);
> @@ -2457,9 +2474,9 @@ uvm_map_pageable_wire(struct vm_map *map
>  /*
>   * uvm_map_pageable: set pageability of a range in a map.
>   *
> - * Flags:
> - * UVM_LK_ENTER: map is already locked by caller
> - * UVM_LK_EXIT:  don't unlock map on exit
> + * => map must never be read-locked
> + * => if lockflags has UVM_LK_ENTER set, map is already write-locked
> + * => if lockflags has UVM_LK_EXIT  set, don't unlock map on exit
>   *
>   * The full range must be in use (entries may not have fspace != 0).
>   * UVM_ET_HOLE counts as unmapped.
> @@ -2586,8 +2603,8 @@ out:
>   * uvm_map_pageable_all: special case of uvm_map_pageable - affects
>   * all mapped regions.
>   *
> - * Map must not be locked.
> - * If no flags are specified, all ragions are unwired.
> + * => map must not be locked.
> + * => if no flags are specified, all ragions are unwired.
>   */
>  int
>  uvm_map_pageable_all(struct vm_map *map, int flags, vsize_t limit)
> @@ -2976,12 +2993,17 @@ uvm_tree_sanity(struct vm_map *map, char
>       UVM_ASSERT(map, addr == vm_map_max(map), file, line);
>  }
>  
> +/*
> + * map must be at least read-locked.
> + */
>  void
>  uvm_tree_size_chk(struct vm_map *map, char *file, int line)
>  {
>       struct vm_map_entry *iter;
>       vsize_t size;
>  
> +     vm_map_assert_anylock(map);
> +
>       size = 0;
>       RBT_FOREACH(iter, uvm_map_addr, &map->addr) {
>               if (!UVM_ET_ISHOLE(iter))
> @@ -4268,7 +4290,7 @@ uvm_map_submap(struct vm_map *map, vaddr
>   * uvm_map_checkprot: check protection in map
>   *
>   * => must allow specific protection in a fully allocated region.
> - * => map mut be read or write locked by caller.
> + * => map must be read or write locked by caller.
>   */
>  boolean_t
>  uvm_map_checkprot(struct vm_map *map, vaddr_t start, vaddr_t end,
> @@ -4276,6 +4298,8 @@ uvm_map_checkprot(struct vm_map *map, va
>  {
>       struct vm_map_entry *entry;
>  
> +     vm_map_assert_anylock(map);
> +
>       if (start < map->min_offset || end > map->max_offset || start > end)
>               return FALSE;
>       if (start == end)
> @@ -4802,12 +4825,18 @@ flush_object:
>  
>  /*
>   * UVM_MAP_CLIP_END implementation
> + *
> + * => caller should use UVM_MAP_CLIP_END macro rather than calling
> + *    this directly
> + * => map must be locked by caller
>   */
>  void
>  uvm_map_clip_end(struct vm_map *map, struct vm_map_entry *entry, vaddr_t 
> addr)
>  {
>       struct vm_map_entry *tmp;
>  
> +     vm_map_assert_wrlock(map);
> +
>       KASSERT(entry->start < addr && VMMAP_FREE_END(entry) > addr);
>       tmp = uvm_mapent_alloc(map, 0);
>  
> @@ -4823,6 +4852,10 @@ uvm_map_clip_end(struct vm_map *map, str
>   * Since uvm_map_splitentry turns the original entry into the lowest
>   * entry (address wise) we do a swap between the new entry and the original
>   * entry, prior to calling uvm_map_splitentry.
> + *
> + * => caller should use UVM_MAP_CLIP_START macro rather than calling
> + *    this directly
> + * => map must be locked by caller
>   */
>  void
>  uvm_map_clip_start(struct vm_map *map, struct vm_map_entry *entry, vaddr_t 
> addr)
> @@ -4830,6 +4863,8 @@ uvm_map_clip_start(struct vm_map *map, s
>       struct vm_map_entry *tmp;
>       struct uvm_addr_state *free;
>  
> +     vm_map_assert_wrlock(map);
> +
>       /* Unlink original. */
>       free = uvm_map_uaddr_e(map, entry);
>       uvm_mapent_free_remove(map, free, entry);
> @@ -5557,6 +5592,46 @@ vm_map_unbusy_ln(struct vm_map *map, cha
>       mtx_leave(&map->flags_lock);
>       if (oflags & VM_MAP_WANTLOCK)
>               wakeup(&map->flags);
> +}
> +
> +void
> +vm_map_assert_anylock_ln(struct vm_map *map, char *file, int line)
> +{
> +     LPRINTF(("map assert read or write locked: %p (at %s %d)\n", map, file, 
> line));
> +     if ((map->flags & VM_MAP_INTRSAFE) == 0)
> +             rw_assert_anylock(&map->lock);
> +     else
> +             MUTEX_ASSERT_LOCKED(&map->mtx);
> +}
> +
> +void
> +vm_map_assert_rdlock_ln(struct vm_map *map, char *file, int line)
> +{
> +     LPRINTF(("map assert read locked: %p (at %s %d)\n", map, file, line));
> +     if ((map->flags & VM_MAP_INTRSAFE) == 0)
> +             rw_assert_rdlock(&map->lock);
> +     else
> +             MUTEX_ASSERT_LOCKED(&map->mtx);
> +}
> +
> +void
> +vm_map_assert_wrlock_ln(struct vm_map *map, char *file, int line)
> +{
> +     LPRINTF(("map assert write locked: %p (at %s %d)\n", map, file, line));
> +     if ((map->flags & VM_MAP_INTRSAFE) == 0)
> +             rw_assert_wrlock(&map->lock);
> +     else
> +             MUTEX_ASSERT_LOCKED(&map->mtx);
> +}
> +
> +void
> +vm_map_assert_unlocked_ln(struct vm_map *map, char *file, int line)
> +{
> +     LPRINTF(("map assert unlocked: %p (at %s %d)\n", map, file, line));
> +     if ((map->flags & VM_MAP_INTRSAFE) == 0)
> +             rw_assert_unlocked(&map->lock);
> +     else
> +             MUTEX_ASSERT_UNLOCKED(&map->mtx);
>  }
>  
>  #ifndef SMALL_KERNEL
> Index: uvm_map.h
> ===================================================================
> RCS file: /cvs/src/sys/uvm/uvm_map.h,v
> retrieving revision 1.71
> diff -u -p -r1.71 uvm_map.h
> --- uvm_map.h 15 Dec 2021 12:53:53 -0000      1.71
> +++ uvm_map.h 3 Feb 2022 11:38:22 -0000
> @@ -419,6 +420,10 @@ void             vm_map_downgrade_ln(struct vm_map*
>  void         vm_map_upgrade_ln(struct vm_map*, char*, int);
>  void         vm_map_busy_ln(struct vm_map*, char*, int);
>  void         vm_map_unbusy_ln(struct vm_map*, char*, int);
> +void         vm_map_assert_anylock_ln(struct vm_map*, char*, int);
> +void         vm_map_assert_rdlock_ln(struct vm_map*, char*, int);
> +void         vm_map_assert_wrlock_ln(struct vm_map*, char*, int);
> +void         vm_map_assert_unlocked_ln(struct vm_map*, char*, int);
>  
>  #ifdef DIAGNOSTIC
>  #define vm_map_lock_try(map) vm_map_lock_try_ln(map, __FILE__, __LINE__)
> @@ -430,6 +435,10 @@ void             vm_map_unbusy_ln(struct vm_map*, c
>  #define vm_map_upgrade(map)  vm_map_upgrade_ln(map, __FILE__, __LINE__)
>  #define vm_map_busy(map)     vm_map_busy_ln(map, __FILE__, __LINE__)
>  #define vm_map_unbusy(map)   vm_map_unbusy_ln(map, __FILE__, __LINE__)
> +#define vm_map_assert_anylock(map)   vm_map_assert_anylock_ln(map, __FILE__, 
> __LINE__)
> +#define vm_map_assert_rdlock(map)    vm_map_assert_rdlock_ln(map, __FILE__, 
> __LINE__)
> +#define vm_map_assert_wrlock(map)    vm_map_assert_wrlock_ln(map, __FILE__, 
> __LINE__)
> +#define vm_map_assert_unlocked(map)  vm_map_assert_unlocked_ln(map, 
> __FILE__, __LINE__)
>  #else
>  #define vm_map_lock_try(map) vm_map_lock_try_ln(map, NULL, 0)
>  #define vm_map_lock(map)     vm_map_lock_ln(map, NULL, 0)
> @@ -440,6 +449,10 @@ void             vm_map_unbusy_ln(struct vm_map*, c
>  #define vm_map_upgrade(map)  vm_map_upgrade_ln(map, NULL, 0)
>  #define vm_map_busy(map)     vm_map_busy_ln(map, NULL, 0)
>  #define vm_map_unbusy(map)   vm_map_unbusy_ln(map, NULL, 0)
> +#define vm_map_assert_anylock(map)   vm_map_assert_anylock_ln(map, NULL, 0)
> +#define vm_map_assert_rdlock(map)    vm_map_assert_rdlock_ln(map, NULL, 0)
> +#define vm_map_assert_wrlock(map)    vm_map_assert_wrlock_ln(map, NULL, 0)
> +#define vm_map_assert_unlocked(map)  vm_map_assert_unlocked_ln(map, NULL, 0)
>  #endif
>  
>  void         uvm_map_lock_entry(struct vm_map_entry *);
> Index: uvm_fault.c
> ===================================================================
> RCS file: /cvs/src/sys/uvm/uvm_fault.c,v
> retrieving revision 1.125
> diff -u -p -r1.125 uvm_fault.c
> --- uvm_fault.c       1 Feb 2022 08:38:53 -0000       1.125
> +++ uvm_fault.c       3 Feb 2022 11:40:22 -0000
> @@ -1614,6 +1614,7 @@ uvm_fault_unwire_locked(vm_map_t map, va
>       struct vm_page *pg;
>  
>       KASSERT((map->flags & VM_MAP_INTRSAFE) == 0);
> +     vm_map_assert_anylock(map);
>  
>       /*
>        * we assume that the area we are unwiring has actually been wired
> 
> 

Reply via email to