Commit 51339d99 authored by Alexei Starovoitov's avatar Alexei Starovoitov Committed by Andrew Morton
Browse files

locking/local_lock, mm: replace localtry_ helpers with local_trylock_t type

Partially revert commit 0aaddfb0 ("locking/local_lock: Introduce
localtry_lock_t").  Remove localtry_*() helpers, since localtry_lock()
name might be misinterpreted as "try lock".

Introduce local_trylock[_irqsave]() helpers that only work with newly
introduced local_trylock_t type.  Note that attempt to use
local_trylock[_irqsave]() with local_lock_t will cause compilation
failure.

Usage and behavior in !PREEMPT_RT:

local_lock_t lock;                     // sizeof(lock) == 0
local_lock(&lock);                     // preempt disable
local_lock_irqsave(&lock, ...);        // irq save
if (local_trylock_irqsave(&lock, ...)) // compilation error

local_trylock_t lock;                  // sizeof(lock) == 4
local_lock(&lock);                     // preempt disable, acquired = 1
local_lock_irqsave(&lock, ...);        // irq save, acquired = 1
if (local_trylock(&lock))              // if (!acquired) preempt disable, acquired = 1
if (local_trylock_irqsave(&lock, ...)) // if (!acquired) irq save, acquired = 1

The existing local_lock_*() macros can be used either with local_lock_t or
local_trylock_t.  With local_trylock_t they set acquired = 1 while
local_unlock_*() clears it.

In !PREEMPT_RT local_lock_irqsave(local_lock_t *) disables interrupts to
protect critical section, but it doesn't prevent NMI, so the fully
reentrant code cannot use local_lock_irqsave(local_lock_t *) for exclusive
access.

The local_lock_irqsave(local_trylock_t *) helper disables interrupts and
sets acquired=1, so local_trylock_irqsave(local_trylock_t *) from NMI
attempting to acquire the same lock will return false.

In PREEMPT_RT local_lock_irqsave() maps to preemptible spin_lock().  Map
local_trylock_irqsave() to preemptible spin_trylock().  When in hard IRQ
or NMI return false right away, since spin_trylock() is not safe due to
explicit locking in the underneath rt_spin_trylock() implementation. 
Removing this explicit locking and attempting only "trylock" is undesired
due to PI implications.

The local_trylock() without _irqsave can be used to avoid the cost of
disabling/enabling interrupts by only disabling preemption, so
local_trylock() in an interrupt attempting to acquire the same lock will
return false.

Note there is no need to use local_inc for acquired variable, since it's a
percpu variable with strict nesting scopes.

Note that guard(local_lock)(&lock) works only for "local_lock_t lock".

The patch also makes sure that local_lock_release(l) is called before
WRITE_ONCE(l->acquired, 0).  Though IRQs are disabled at this point the
local_trylock() from NMI will succeed and local_lock_acquire(l) will warn.

Link: https://lkml.kernel.org/r/20250403025514.41186-1-alexei.starovoitov@gmail.com


Fixes: 0aaddfb0 ("locking/local_lock: Introduce localtry_lock_t")
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
Acked-by: default avatarVlastimil Babka <vbabka@suse.cz>
Acked-by: default avatarSebastian Andrzej Siewior <bigeasy@linutronix.de>
Reviewed-by: default avatarShakeel Butt <shakeel.butt@linux.dev>
Cc: Daniel Borkman <daniel@iogearbox.net>
Cc: Linus Torvalds <torvalds@linuxfoundation.org>
Cc: Martin KaFai Lau <martin.lau@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent a30951d0
Loading
Loading
Loading
Loading
+8 −50
Original line number Diff line number Diff line
@@ -52,43 +52,22 @@
	__local_unlock_irqrestore(lock, flags)

/**
 * localtry_lock_init - Runtime initialize a lock instance
 */
#define localtry_lock_init(lock)		__localtry_lock_init(lock)

/**
 * localtry_lock - Acquire a per CPU local lock
 * @lock:	The lock variable
 */
#define localtry_lock(lock)		__localtry_lock(lock)

/**
 * localtry_lock_irq - Acquire a per CPU local lock and disable interrupts
 * @lock:	The lock variable
 */
#define localtry_lock_irq(lock)		__localtry_lock_irq(lock)

/**
 * localtry_lock_irqsave - Acquire a per CPU local lock, save and disable
 *			 interrupts
 * @lock:	The lock variable
 * @flags:	Storage for interrupt flags
 * local_lock_init - Runtime initialize a lock instance
 */
#define localtry_lock_irqsave(lock, flags)				\
	__localtry_lock_irqsave(lock, flags)
#define local_trylock_init(lock)	__local_trylock_init(lock)

/**
 * localtry_trylock - Try to acquire a per CPU local lock.
 * local_trylock - Try to acquire a per CPU local lock
 * @lock:	The lock variable
 *
 * The function can be used in any context such as NMI or HARDIRQ. Due to
 * locking constrains it will _always_ fail to acquire the lock in NMI or
 * HARDIRQ context on PREEMPT_RT.
 */
#define localtry_trylock(lock)		__localtry_trylock(lock)
#define local_trylock(lock)		__local_trylock(lock)

/**
 * localtry_trylock_irqsave - Try to acquire a per CPU local lock, save and disable
 * local_trylock_irqsave - Try to acquire a per CPU local lock, save and disable
 *			   interrupts if acquired
 * @lock:	The lock variable
 * @flags:	Storage for interrupt flags
@@ -97,29 +76,8 @@
 * locking constrains it will _always_ fail to acquire the lock in NMI or
 * HARDIRQ context on PREEMPT_RT.
 */
#define localtry_trylock_irqsave(lock, flags)				\
	__localtry_trylock_irqsave(lock, flags)

/**
 * local_unlock - Release a per CPU local lock
 * @lock:	The lock variable
 */
#define localtry_unlock(lock)		__localtry_unlock(lock)

/**
 * local_unlock_irq - Release a per CPU local lock and enable interrupts
 * @lock:	The lock variable
 */
#define localtry_unlock_irq(lock)		__localtry_unlock_irq(lock)

/**
 * localtry_unlock_irqrestore - Release a per CPU local lock and restore
 *			      interrupt flags
 * @lock:	The lock variable
 * @flags:      Interrupt flags to restore
 */
#define localtry_unlock_irqrestore(lock, flags)			\
	__localtry_unlock_irqrestore(lock, flags)
#define local_trylock_irqsave(lock, flags)			\
	__local_trylock_irqsave(lock, flags)

DEFINE_GUARD(local_lock, local_lock_t __percpu*,
	     local_lock(_T),
+87 −120
Original line number Diff line number Diff line
@@ -15,10 +15,11 @@ typedef struct {
#endif
} local_lock_t;

/* local_trylock() and local_trylock_irqsave() only work with local_trylock_t */
typedef struct {
	local_lock_t	llock;
	unsigned int	acquired;
} localtry_lock_t;
	u8		acquired;
} local_trylock_t;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCAL_LOCK_DEBUG_INIT(lockname)		\
@@ -29,6 +30,9 @@ typedef struct {
	},						\
	.owner = NULL,

# define LOCAL_TRYLOCK_DEBUG_INIT(lockname)		\
	.llock = { LOCAL_LOCK_DEBUG_INIT((lockname).llock) },

static inline void local_lock_acquire(local_lock_t *l)
{
	lock_map_acquire(&l->dep_map);
@@ -56,6 +60,7 @@ static inline void local_lock_debug_init(local_lock_t *l)
}
#else /* CONFIG_DEBUG_LOCK_ALLOC */
# define LOCAL_LOCK_DEBUG_INIT(lockname)
# define LOCAL_TRYLOCK_DEBUG_INIT(lockname)
static inline void local_lock_acquire(local_lock_t *l) { }
static inline void local_trylock_acquire(local_lock_t *l) { }
static inline void local_lock_release(local_lock_t *l) { }
@@ -63,7 +68,7 @@ static inline void local_lock_debug_init(local_lock_t *l) { }
#endif /* !CONFIG_DEBUG_LOCK_ALLOC */

#define INIT_LOCAL_LOCK(lockname)	{ LOCAL_LOCK_DEBUG_INIT(lockname) }
#define INIT_LOCALTRY_LOCK(lockname)	{ .llock = { LOCAL_LOCK_DEBUG_INIT(lockname.llock) }}
#define INIT_LOCAL_TRYLOCK(lockname)	{ LOCAL_TRYLOCK_DEBUG_INIT(lockname) }

#define __local_lock_init(lock)					\
do {								\
@@ -76,6 +81,8 @@ do { \
	local_lock_debug_init(lock);				\
} while (0)

#define __local_trylock_init(lock) __local_lock_init(lock.llock)

#define __spinlock_nested_bh_init(lock)				\
do {								\
	static struct lock_class_key __key;			\
@@ -87,149 +94,117 @@ do { \
	local_lock_debug_init(lock);				\
} while (0)

#define __local_lock(lock)					\
	do {							\
		preempt_disable();				\
		local_lock_acquire(this_cpu_ptr(lock));		\
	} while (0)

#define __local_lock_irq(lock)					\
	do {							\
		local_irq_disable();				\
		local_lock_acquire(this_cpu_ptr(lock));		\
	} while (0)

#define __local_lock_irqsave(lock, flags)			\
	do {							\
		local_irq_save(flags);				\
		local_lock_acquire(this_cpu_ptr(lock));		\
	} while (0)

#define __local_unlock(lock)					\
	do {							\
		local_lock_release(this_cpu_ptr(lock));		\
		preempt_enable();				\
	} while (0)

#define __local_unlock_irq(lock)				\
	do {							\
		local_lock_release(this_cpu_ptr(lock));		\
		local_irq_enable();				\
	} while (0)

#define __local_unlock_irqrestore(lock, flags)			\
	do {							\
		local_lock_release(this_cpu_ptr(lock));		\
		local_irq_restore(flags);			\
	} while (0)

#define __local_lock_nested_bh(lock)				\
	do {							\
		lockdep_assert_in_softirq();			\
		local_lock_acquire(this_cpu_ptr(lock));	\
	} while (0)

#define __local_unlock_nested_bh(lock)				\
	local_lock_release(this_cpu_ptr(lock))

/* localtry_lock_t variants */

#define __localtry_lock_init(lock)				\
#define __local_lock_acquire(lock)					\
	do {								\
	__local_lock_init(&(lock)->llock);			\
	WRITE_ONCE((lock)->acquired, 0);			\
		local_trylock_t *tl;					\
		local_lock_t *l;					\
									\
		l = (local_lock_t *)this_cpu_ptr(lock);			\
		tl = (local_trylock_t *)l;				\
		_Generic((lock),					\
			local_trylock_t *: ({				\
				lockdep_assert(tl->acquired == 0);	\
				WRITE_ONCE(tl->acquired, 1);		\
			}),						\
			default:(void)0);				\
		local_lock_acquire(l);					\
	} while (0)

#define __localtry_lock(lock)					\
#define __local_lock(lock)					\
	do {							\
		localtry_lock_t *lt;				\
		preempt_disable();				\
		lt = this_cpu_ptr(lock);			\
		local_lock_acquire(&lt->llock);			\
		WRITE_ONCE(lt->acquired, 1);			\
		__local_lock_acquire(lock);			\
	} while (0)

#define __localtry_lock_irq(lock)				\
#define __local_lock_irq(lock)					\
	do {							\
		localtry_lock_t *lt;				\
		local_irq_disable();				\
		lt = this_cpu_ptr(lock);			\
		local_lock_acquire(&lt->llock);			\
		WRITE_ONCE(lt->acquired, 1);			\
		__local_lock_acquire(lock);			\
	} while (0)

#define __localtry_lock_irqsave(lock, flags)			\
#define __local_lock_irqsave(lock, flags)			\
	do {							\
		localtry_lock_t *lt;				\
		local_irq_save(flags);				\
		lt = this_cpu_ptr(lock);			\
		local_lock_acquire(&lt->llock);			\
		WRITE_ONCE(lt->acquired, 1);			\
		__local_lock_acquire(lock);			\
	} while (0)

#define __localtry_trylock(lock)				\
#define __local_trylock(lock)					\
	({							\
		localtry_lock_t *lt;				\
		bool _ret;					\
		local_trylock_t *tl;				\
								\
		preempt_disable();				\
		lt = this_cpu_ptr(lock);			\
		if (!READ_ONCE(lt->acquired)) {			\
			WRITE_ONCE(lt->acquired, 1);		\
			local_trylock_acquire(&lt->llock);	\
			_ret = true;				\
		} else {					\
			_ret = false;				\
		tl = this_cpu_ptr(lock);			\
		if (READ_ONCE(tl->acquired)) {			\
			preempt_enable();			\
			tl = NULL;				\
		} else {					\
			WRITE_ONCE(tl->acquired, 1);		\
			local_trylock_acquire(			\
				(local_lock_t *)tl);		\
		}						\
		_ret;						\
		!!tl;						\
	})

#define __localtry_trylock_irqsave(lock, flags)			\
#define __local_trylock_irqsave(lock, flags)			\
	({							\
		localtry_lock_t *lt;				\
		bool _ret;					\
		local_trylock_t *tl;				\
								\
		local_irq_save(flags);				\
		lt = this_cpu_ptr(lock);			\
		if (!READ_ONCE(lt->acquired)) {			\
			WRITE_ONCE(lt->acquired, 1);		\
			local_trylock_acquire(&lt->llock);	\
			_ret = true;				\
		} else {					\
			_ret = false;				\
		tl = this_cpu_ptr(lock);			\
		if (READ_ONCE(tl->acquired)) {			\
			local_irq_restore(flags);		\
			tl = NULL;				\
		} else {					\
			WRITE_ONCE(tl->acquired, 1);		\
			local_trylock_acquire(			\
				(local_lock_t *)tl);		\
		}						\
		_ret;						\
		!!tl;						\
	})

#define __localtry_unlock(lock)					\
#define __local_lock_release(lock)					\
	do {								\
		local_trylock_t *tl;					\
		local_lock_t *l;					\
									\
		l = (local_lock_t *)this_cpu_ptr(lock);			\
		tl = (local_trylock_t *)l;				\
		local_lock_release(l);					\
		_Generic((lock),					\
			local_trylock_t *: ({				\
				lockdep_assert(tl->acquired == 1);	\
				WRITE_ONCE(tl->acquired, 0);		\
			}),						\
			default:(void)0);				\
	} while (0)

#define __local_unlock(lock)					\
	do {							\
		localtry_lock_t *lt;				\
		lt = this_cpu_ptr(lock);			\
		WRITE_ONCE(lt->acquired, 0);			\
		local_lock_release(&lt->llock);			\
		__local_lock_release(lock);			\
		preempt_enable();				\
	} while (0)

#define __localtry_unlock_irq(lock)				\
#define __local_unlock_irq(lock)				\
	do {							\
		localtry_lock_t *lt;				\
		lt = this_cpu_ptr(lock);			\
		WRITE_ONCE(lt->acquired, 0);			\
		local_lock_release(&lt->llock);			\
		__local_lock_release(lock);			\
		local_irq_enable();				\
	} while (0)

#define __localtry_unlock_irqrestore(lock, flags)		\
#define __local_unlock_irqrestore(lock, flags)			\
	do {							\
		localtry_lock_t *lt;				\
		lt = this_cpu_ptr(lock);			\
		WRITE_ONCE(lt->acquired, 0);			\
		local_lock_release(&lt->llock);			\
		__local_lock_release(lock);			\
		local_irq_restore(flags);			\
	} while (0)

#define __local_lock_nested_bh(lock)				\
	do {							\
		lockdep_assert_in_softirq();			\
		local_lock_acquire(this_cpu_ptr(lock));	\
	} while (0)

#define __local_unlock_nested_bh(lock)				\
	local_lock_release(this_cpu_ptr(lock))

#else /* !CONFIG_PREEMPT_RT */

/*
@@ -237,16 +212,18 @@ do { \
 * critical section while staying preemptible.
 */
typedef spinlock_t local_lock_t;
typedef spinlock_t localtry_lock_t;
typedef spinlock_t local_trylock_t;

#define INIT_LOCAL_LOCK(lockname) __LOCAL_SPIN_LOCK_UNLOCKED((lockname))
#define INIT_LOCALTRY_LOCK(lockname) INIT_LOCAL_LOCK(lockname)
#define INIT_LOCAL_TRYLOCK(lockname) __LOCAL_SPIN_LOCK_UNLOCKED((lockname))

#define __local_lock_init(l)					\
	do {							\
		local_spin_lock_init((l));			\
	} while (0)

#define __local_trylock_init(l)			__local_lock_init(l)

#define __local_lock(__lock)					\
	do {							\
		migrate_disable();				\
@@ -283,17 +260,7 @@ do { \
	spin_unlock(this_cpu_ptr((lock)));			\
} while (0)

/* localtry_lock_t variants */

#define __localtry_lock_init(lock)			__local_lock_init(lock)
#define __localtry_lock(lock)				__local_lock(lock)
#define __localtry_lock_irq(lock)			__local_lock(lock)
#define __localtry_lock_irqsave(lock, flags)		__local_lock_irqsave(lock, flags)
#define __localtry_unlock(lock)				__local_unlock(lock)
#define __localtry_unlock_irq(lock)			__local_unlock(lock)
#define __localtry_unlock_irqrestore(lock, flags)	__local_unlock_irqrestore(lock, flags)

#define __localtry_trylock(lock)				\
#define __local_trylock(lock)					\
	({							\
		int __locked;					\
								\
@@ -308,11 +275,11 @@ do { \
		__locked;					\
	})

#define __localtry_trylock_irqsave(lock, flags)			\
#define __local_trylock_irqsave(lock, flags)			\
	({							\
		typecheck(unsigned long, flags);		\
		flags = 0;					\
		__localtry_trylock(lock);			\
		__local_trylock(lock);				\
	})

#endif /* CONFIG_PREEMPT_RT */
+19 −20
Original line number Diff line number Diff line
@@ -1759,7 +1759,7 @@ void mem_cgroup_print_oom_group(struct mem_cgroup *memcg)
}

struct memcg_stock_pcp {
	localtry_lock_t stock_lock;
	local_trylock_t stock_lock;
	struct mem_cgroup *cached; /* this never be root cgroup */
	unsigned int nr_pages;

@@ -1774,7 +1774,7 @@ struct memcg_stock_pcp {
#define FLUSHING_CACHED_CHARGE	0
};
static DEFINE_PER_CPU(struct memcg_stock_pcp, memcg_stock) = {
	.stock_lock = INIT_LOCALTRY_LOCK(stock_lock),
	.stock_lock = INIT_LOCAL_TRYLOCK(stock_lock),
};
static DEFINE_MUTEX(percpu_charge_mutex);

@@ -1805,11 +1805,10 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages,
	if (nr_pages > MEMCG_CHARGE_BATCH)
		return ret;

	if (!localtry_trylock_irqsave(&memcg_stock.stock_lock, flags)) {
		if (!gfpflags_allow_spinning(gfp_mask))
	if (gfpflags_allow_spinning(gfp_mask))
		local_lock_irqsave(&memcg_stock.stock_lock, flags);
	else if (!local_trylock_irqsave(&memcg_stock.stock_lock, flags))
		return ret;
		localtry_lock_irqsave(&memcg_stock.stock_lock, flags);
	}

	stock = this_cpu_ptr(&memcg_stock);
	stock_pages = READ_ONCE(stock->nr_pages);
@@ -1818,7 +1817,7 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages,
		ret = true;
	}

	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);

	return ret;
}
@@ -1857,14 +1856,14 @@ static void drain_local_stock(struct work_struct *dummy)
	 * drain_stock races is that we always operate on local CPU stock
	 * here with IRQ disabled
	 */
	localtry_lock_irqsave(&memcg_stock.stock_lock, flags);
	local_lock_irqsave(&memcg_stock.stock_lock, flags);

	stock = this_cpu_ptr(&memcg_stock);
	old = drain_obj_stock(stock);
	drain_stock(stock);
	clear_bit(FLUSHING_CACHED_CHARGE, &stock->flags);

	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	obj_cgroup_put(old);
}

@@ -1894,7 +1893,7 @@ static void refill_stock(struct mem_cgroup *memcg, unsigned int nr_pages)
{
	unsigned long flags;

	if (!localtry_trylock_irqsave(&memcg_stock.stock_lock, flags)) {
	if (!local_trylock_irqsave(&memcg_stock.stock_lock, flags)) {
		/*
		 * In case of unlikely failure to lock percpu stock_lock
		 * uncharge memcg directly.
@@ -1907,7 +1906,7 @@ static void refill_stock(struct mem_cgroup *memcg, unsigned int nr_pages)
		return;
	}
	__refill_stock(memcg, nr_pages);
	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);
}

/*
@@ -1964,9 +1963,9 @@ static int memcg_hotplug_cpu_dead(unsigned int cpu)
	stock = &per_cpu(memcg_stock, cpu);

	/* drain_obj_stock requires stock_lock */
	localtry_lock_irqsave(&memcg_stock.stock_lock, flags);
	local_lock_irqsave(&memcg_stock.stock_lock, flags);
	old = drain_obj_stock(stock);
	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);

	drain_stock(stock);
	obj_cgroup_put(old);
@@ -2787,7 +2786,7 @@ static void mod_objcg_state(struct obj_cgroup *objcg, struct pglist_data *pgdat,
	unsigned long flags;
	int *bytes;

	localtry_lock_irqsave(&memcg_stock.stock_lock, flags);
	local_lock_irqsave(&memcg_stock.stock_lock, flags);
	stock = this_cpu_ptr(&memcg_stock);

	/*
@@ -2836,7 +2835,7 @@ static void mod_objcg_state(struct obj_cgroup *objcg, struct pglist_data *pgdat,
	if (nr)
		__mod_objcg_mlstate(objcg, pgdat, idx, nr);

	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	obj_cgroup_put(old);
}

@@ -2846,7 +2845,7 @@ static bool consume_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes)
	unsigned long flags;
	bool ret = false;

	localtry_lock_irqsave(&memcg_stock.stock_lock, flags);
	local_lock_irqsave(&memcg_stock.stock_lock, flags);

	stock = this_cpu_ptr(&memcg_stock);
	if (objcg == READ_ONCE(stock->cached_objcg) && stock->nr_bytes >= nr_bytes) {
@@ -2854,7 +2853,7 @@ static bool consume_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes)
		ret = true;
	}

	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);

	return ret;
}
@@ -2946,7 +2945,7 @@ static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,
	unsigned long flags;
	unsigned int nr_pages = 0;

	localtry_lock_irqsave(&memcg_stock.stock_lock, flags);
	local_lock_irqsave(&memcg_stock.stock_lock, flags);

	stock = this_cpu_ptr(&memcg_stock);
	if (READ_ONCE(stock->cached_objcg) != objcg) { /* reset if necessary */
@@ -2960,7 +2959,7 @@ static void refill_obj_stock(struct obj_cgroup *objcg, unsigned int nr_bytes,
		stock->nr_bytes &= (PAGE_SIZE - 1);
	}

	localtry_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	local_unlock_irqrestore(&memcg_stock.stock_lock, flags);
	obj_cgroup_put(old);

	if (nr_pages)