From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) by smtp.lore.kernel.org (Postfix) with ESMTP id 42FF6D1813A for ; Mon, 14 Oct 2024 18:49:35 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id D035F6B007B; Mon, 14 Oct 2024 14:49:34 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id CB3166B0088; Mon, 14 Oct 2024 14:49:34 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id B2D256B0089; Mon, 14 Oct 2024 14:49:34 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0016.hostedemail.com [216.40.44.16]) by kanga.kvack.org (Postfix) with ESMTP id 9017A6B007B for ; Mon, 14 Oct 2024 14:49:34 -0400 (EDT) Received: from smtpin07.hostedemail.com (a10.router.float.18 [10.200.18.1]) by unirelay09.hostedemail.com (Postfix) with ESMTP id 605E08125D for ; Mon, 14 Oct 2024 18:49:27 +0000 (UTC) X-FDA: 82673095974.07.3A8D2D6 Received: from sender4-pp-f112.zoho.com (sender4-pp-f112.zoho.com [136.143.188.112]) by imf05.hostedemail.com (Postfix) with ESMTP id 1348D10000D for ; Mon, 14 Oct 2024 18:49:19 +0000 (UTC) Authentication-Results: imf05.hostedemail.com; dkim=pass header.d=collabora.com header.s=zohomail header.b=EcVcI5vU; spf=pass (imf05.hostedemail.com: domain of daniel.almeida@collabora.com designates 136.143.188.112 as permitted sender) smtp.mailfrom=daniel.almeida@collabora.com; dmarc=pass (policy=none) header.from=collabora.com; arc=pass ("zohomail.com:s=zohoarc:i=1") ARC-Seal: i=2; s=arc-20220608; d=hostedemail.com; t=1728931699; a=rsa-sha256; cv=pass; b=lsdfRLpwdn4JC32L5OOtLO0CvgyUc2faJ5WDnxx2qoipgZm2nTHrAS7vybtf4n4H24mVKF 9T6qCT/82sJPqIZS5rhmsicf9wiHTvzA8T8bQuRv6Xmtf+qBJk6aqXE1cV7LnGK4DUWOKo bm7xQ2ly1Ss6298NCn2ZsouHXe/FlEU= ARC-Authentication-Results: i=2; imf05.hostedemail.com; dkim=pass header.d=collabora.com header.s=zohomail header.b=EcVcI5vU; spf=pass (imf05.hostedemail.com: domain of daniel.almeida@collabora.com designates 136.143.188.112 as permitted sender) smtp.mailfrom=daniel.almeida@collabora.com; dmarc=pass (policy=none) header.from=collabora.com; arc=pass ("zohomail.com:s=zohoarc:i=1") ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1728931699; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:dkim-signature; bh=JjuTfG9pTSaOz9k2Eg+nDwIwmOylBHSqVWst+2ot0+8=; b=V+fPGfYZCCu9rUx2v2lIr3h3uvVoTdEaIKgzRaRJPdmBnAyhZqm+VfWQvjAEDenJEj6fYG tQyun/ay3OerzdO/ocVfvStss5oOloK1LwGLjpIIZ5X6qNyQgwOA0FVkKO2LLGRt5GklaC yHw87kxyrv3VqCirBURJ34J1x/89bUs= ARC-Seal: i=1; a=rsa-sha256; t=1728931755; cv=none; d=zohomail.com; s=zohoarc; b=DYESuGYeg8puK8oMR2cjcxiRKQBpyyjcPD9g5Q5eZj7dwKAYBEbQDdxROy04LBJw6NhcYVPMPLNSHf3dwHPjWhFvu91zaUTjfX6kQs2tBVNnAXmcB5FcQ/lxQlHq6QSPHrVRQ4vJwUHg34KUMiKZYoLaDToxt+cDZEIR41qAjyE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1728931755; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=JjuTfG9pTSaOz9k2Eg+nDwIwmOylBHSqVWst+2ot0+8=; b=UhEHXSQFPGO379FBmmzcbWO68rmLkLE2ao4mOG7e1H4hzZQJVTjPj+BCBmJQmwKTEFuM7W26s2P3051EnMT27LFac0A/wXr0q2TkjG2sBMyQ4Fmi3+Z3RwEDl8Q6RTnHtL+WoMOVnkdvvVSgwwnCLXL17Yy+/WCe9w2hPfA1ZWc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=collabora.com; spf=pass smtp.mailfrom=daniel.almeida@collabora.com; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1728931755; s=zohomail; d=collabora.com; i=daniel.almeida@collabora.com; h=Content-Type:Mime-Version:Subject:Subject:From:From:In-Reply-To:Date:Date:Cc:Cc:Content-Transfer-Encoding:Message-Id:Message-Id:References:To:To:Reply-To; bh=JjuTfG9pTSaOz9k2Eg+nDwIwmOylBHSqVWst+2ot0+8=; b=EcVcI5vUUp7posXfBXG29ES282eDKUVKls8j8Eqm4ZPTUvY29pE6IPrs284KYLGb EsXNi1QMsoTFbIJVrYGI+LEisvpw/jFpTMLdO0Em4g0XMI/fYCFQ9C7Gi+U1T1mba38 80rbSSKHagwuw9gcPGa2H7CJlTPf3wOAbnke5/4A= Received: by mx.zohomail.com with SMTPS id 1728931753828957.5452468694128; Mon, 14 Oct 2024 11:49:13 -0700 (PDT) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3818.100.11.1.3\)) Subject: Re: [PATCH v2] rust: shrinker: add shrinker abstraction From: Daniel Almeida In-Reply-To: <20241014-shrinker-v2-1-04719efd2342@google.com> Date: Mon, 14 Oct 2024 15:48:56 -0300 Cc: Miguel Ojeda , Andrew Morton , Dave Chinner , Qi Zheng , Roman Gushchin , Muchun Song , Boqun Feng , Gary Guo , =?utf-8?Q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , linux-kernel@vger.kernel.org, linux-mm@kvack.org, rust-for-linux@vger.kernel.org Content-Transfer-Encoding: quoted-printable Message-Id: References: <20241014-shrinker-v2-1-04719efd2342@google.com> To: Alice Ryhl X-Mailer: Apple Mail (2.3818.100.11.1.3) X-ZohoMailClient: External X-Stat-Signature: btphjp9acrw1z54dsqhwqq7rbsdeubsx X-Rspamd-Queue-Id: 1348D10000D X-Rspam-User: X-Rspamd-Server: rspam10 X-HE-Tag: 1728931759-237615 X-HE-Meta: U2FsdGVkX1/d473Ur1pdWkUqxIC7Hz1RvU31taqfs8gZACiTNFP2mtKfOYEtqtes5i73OMNjj174RtM/5Adussf4re9oZSmoufx11GorWYQXsYobhU6B97tVVd+4dZcZPf7dXwaeAGbrWGmw2EyM4hhnFxDb/7EG6DwkhVsjN4YDZSLAA6v1qCNhFJ3Ypw+StSYywJdnkSnKmxcS8rf5gc2M4LAIAHa8rvt0C8sf+OSmH9JaztNVmZ0VLFL9aekA/aKq69hAkuqAGtVaoApkpfVmj13YhiM7v8H9lV8OokQ5PuSJ1LgKcIHIBN7VQi5qn6PjHdPlz2Dt2wA/yYjT3Vx9aAewu2H4G/llNRXQMkJKZbm7dtvLilvE8MlwWIfQPeJmW3yPx5P3raNBihAFfd7UUDzs1GIlO3+Nb8/CI/X2Owcv/OUN2Zjwakht3/1U+53pUryrWlKEPJ55zPK0iAbtzqO4sNfsTbsMZRc45LknpaGT5D4UlEF2U98m64B7nTT/X1dgTBtIx6OSaXxc3objPTDKMvEZ6SFxuUnmPyDslvXXVcDG1D89gtunUQei2u52dUTnUnzxgq7SGpBOJ4PKo1NvMXB7wpjZJHpYI6AXLATqXEXK9XeLWYHYNcgv811pBrqN7iwBZDBpd4jGuXeDks2UUL5R8raF8M3F74qtUpkq33hJERpjVJZjNkgqsKJHeapFAhiGmxLeD8HJOAPwiKxN11FZVDo+hZPdpp3fIIHZW6X4golZ8MxhHB+xahNbayx3arRInsXCjpevYAaesBzcZmaEvKGSwYcIg9et+vJr0vAdbbqiUzbIjxtCdl9ZYCODYY2FTCNvB9MsMMwYOfTH6tA65e2F0p8go+dEaa7CKre2U4LhXQmM044Fp2ZFQ+2KOkpOWzzIoECbP195YgufGyGUvuMARZr0znPTLLoTjA/ZZnn3+dg/Tnq9XpoQ1SpcgZgNjRWFzQ1 Uk5tq1me S7W57jFPvFZr/Q3e+gnekcfyGHIVATyFanSdK4Y+VLu2AXtwugI8wFXynxubNBI6eQhjnPYTAJsVoW0+xHR7fW2YXGtl0nvMvVd1vVp1UODmWi+EM3rB/kDgO058Fu6KW1P2C3ny1yxx28V7ZUHJRchSGoccg2nWzuSe6ALNVd/pnUTnzPwg1xLfe7T7Jlct6Uty+oyLO9+HY5NXOlU09xrF8ws3hyJRqQc5Bq/ufqcZbe1VoD4NrsU4VoKbRbvYyj9ddyTImp1vuX7uhYV3o6cOj/4PQKaY/Vc7NTflE0mZ6sk7Mi57PxSTaQQfvnAi/FKsocb5TxAwGWx5CCjaAwQ4K1ZTM7vEWykYtquKMEwU3YBdtQ+5XQENJLxgy8vtl/AcTrwTK2B6rrNWd02zliavFGmd6eJuWRW1d X-Bogosity: Ham, tests=bogofilter, spamicity=0.000000, version=1.2.4 Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: Hi Alice, > On 14 Oct 2024, at 13:00, Alice Ryhl wrote: >=20 > Rust Binder holds incoming transactions in a read-only mmap'd region > where it manually manages the pages. These pages are only in use until > the incoming transaction is consumed by userspace, but the kernel will > keep the pages around for future transactions. Rust Binder registers a > shrinker with the kernel so that it can give back these pages if the > system comes under memory pressure. >=20 > Creating a shrinker is done via the ShrinkerBuilder type. Using a > builder means that some options (seeks, batch) can be optional, while > other options (name, private data) can be mandatory. Unlike = seeks/batch, > the private data is not set using a `set_private_data` method, as this > makes it mandatory to provide a private data pointer. >=20 > The user specifies the callbacks in use by implementing the Shrinker > trait for the type used for the private data. This requires specifying > three things: implementations for count_objects and scan_objects, and > the pointer type that the private data will be wrapped in. >=20 > The return values of count_objects and scan_objects are provided using > newtypes called CountObjects and ScanObjects respectively. These types > prevent the user from e.g. returning SHRINK_STOP from count_objects or > returning SHRINK_EMPTY from scan_objects. >=20 > The CountObjects newtype treats CountObjects::new(0) as "the count is > unknown" instead of "the count is zero" for consistency with the way > that the C code works today. >=20 > ShrinkControl is not using Opaque because we need mutable access. >=20 > Support for numa/memcg aware shrinkers is not included here, as they > cannot usefully be used without having list_lru bindings. Support for > that will happen as a follow-up. >=20 > Signed-off-by: Alice Ryhl > --- > Dave, I still had a few outstanding questions in the thread on the = last > version. Most prominently the question on what to do with the builder > instead. I didn't change it in this version as I'm not sure which > alternative you prefer. > --- > Changes in v2: > - Rename to ShrinkerBuilder / ShrinkerRegistration. > - Rename `alloc` to `new`. > - Update CountOjects to match the way C does it. > - Change ScanObjects::MAX to SHRINK_STOP-1. > - Rename gfp_fs to reclaim_fs_allowed, and add reclaim_io_allowed. > - Remove max check in set_nr_scanned. > - Comment that numa/memcg aware shrinkers aren't supported yet. > - Link to v1: = https://lore.kernel.org/r/20240912-shrinker-v1-1-18b7f1253553@google.com > --- > rust/bindings/bindings_helper.h | 3 + > rust/kernel/lib.rs | 1 + > rust/kernel/shrinker.rs | 335 = ++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 339 insertions(+) >=20 > diff --git a/rust/bindings/bindings_helper.h = b/rust/bindings/bindings_helper.h > index ae82e9c941af..fd6d15f5dde1 100644 > --- a/rust/bindings/bindings_helper.h > +++ b/rust/bindings/bindings_helper.h > @@ -18,6 +18,7 @@ > #include > #include > #include > +#include > #include > #include > #include > @@ -31,4 +32,6 @@ const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT =3D = GFP_KERNEL_ACCOUNT; > const gfp_t RUST_CONST_HELPER_GFP_NOWAIT =3D GFP_NOWAIT; > const gfp_t RUST_CONST_HELPER___GFP_ZERO =3D __GFP_ZERO; > const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM =3D ___GFP_HIGHMEM; > +const gfp_t RUST_CONST_HELPER___GFP_FS =3D ___GFP_FS; > +const gfp_t RUST_CONST_HELPER___GFP_IO =3D ___GFP_IO; > const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL =3D = BLK_FEAT_ROTATIONAL; > diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs > index b5f4b3ce6b48..2e9ca7d413c4 100644 > --- a/rust/kernel/lib.rs > +++ b/rust/kernel/lib.rs > @@ -45,6 +45,7 @@ > pub mod prelude; > pub mod print; > pub mod rbtree; > +pub mod shrinker; > pub mod sizes; > mod static_assert; > #[doc(hidden)] > diff --git a/rust/kernel/shrinker.rs b/rust/kernel/shrinker.rs > new file mode 100644 > index 000000000000..d87ad08e917c > --- /dev/null > +++ b/rust/kernel/shrinker.rs > @@ -0,0 +1,335 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +// Copyright (C) 2024 Google LLC. > + > +//! Shrinker for handling memory pressure. > +//! > +//! C header: = [`include/linux/shrinker.h`](srctree/include/linux/shrinker.h) > + > +use crate::{alloc::AllocError, bindings, c_str, str::CStr, = types::ForeignOwnable}; > + > +use core::{ > + ffi::{c_int, c_long, c_ulong, c_void}, > + marker::PhantomData, > + ptr::NonNull, > +}; > + > +const SHRINK_STOP: c_ulong =3D bindings::SHRINK_STOP as c_ulong; > +const SHRINK_EMPTY: c_ulong =3D bindings::SHRINK_EMPTY as c_ulong; > + > +/// The default value for the number of seeks needed to recreate an = object. > +pub const DEFAULT_SEEKS: u32 =3D bindings::DEFAULT_SEEKS; > + > +/// An unregistered shrinker. > +/// > +/// This type can be used to modify the settings of the shrinker = before it is registered. > +/// > +/// # Invariants > +/// > +/// The `shrinker` pointer references an unregistered shrinker. > +pub struct ShrinkerBuilder { > + shrinker: NonNull, > +} > + > +// SAFETY: Moving an unregistered shrinker between threads is okay. > +unsafe impl Send for ShrinkerBuilder {} > +// SAFETY: An unregistered shrinker is thread safe. > +unsafe impl Sync for ShrinkerBuilder {} > + > +impl ShrinkerBuilder { > + /// Create a new shrinker. > + pub fn new(name: &CStr) -> Result { > + // TODO: Support numa/memcg aware shrinkers once list_lru is = available. > + let flags =3D 0; > + > + // SAFETY: Passing `0` as flags is okay. Using `%s` as the = format string is okay when we > + // pass a nul-terminated string as the string for `%s` to = print. > + let ptr =3D unsafe { > + bindings::shrinker_alloc(flags, = c_str!("%s").as_char_ptr(), name.as_char_ptr()) > + }; > + > + let shrinker =3D NonNull::new(ptr).ok_or(AllocError)?; > + > + // INVARIANT: The allocated shrinker is unregistered. > + Ok(Self { shrinker }) > + } > + > + /// Create a new shrinker using format arguments for the name. > + pub fn new_fmt(name: core::fmt::Arguments<'_>) -> Result { > + // TODO: Support numa/memcg aware shrinkers once list_lru is = available. > + let flags =3D 0; > + > + // SAFETY: Passing `0` as flags is okay. Using `%pA` as the = format string is okay when we > + // pass a `fmt::Arguments` as the value to print. > + let ptr =3D unsafe { > + bindings::shrinker_alloc( > + flags, > + c_str!("%pA").as_char_ptr(), > + &name as *const _ as *const c_void, > + ) > + }; > + > + let shrinker =3D NonNull::new(ptr).ok_or(AllocError)?; > + > + // INVARIANT: The allocated shrinker is unregistered. > + Ok(Self { shrinker }) > + } > + > + /// Set the number of seeks needed to recreate an object. > + pub fn set_seeks(&mut self, seeks: u32) { > + unsafe { (*self.shrinker.as_ptr()).seeks =3D seeks as c_int = }; > + } > + > + /// Set the batch size for reclaiming on this shrinker. > + pub fn set_batch(&mut self, batch: usize) { > + unsafe { (*self.shrinker.as_ptr()).batch =3D batch as c_long = }; > + } > + > + /// Register the shrinker. > + /// > + /// The provided pointer is used as the private data, and the = type `T` determines the callbacks > + /// that the shrinker will use. > + pub fn register(self, private_data: T::Ptr) -> = ShrinkerRegistration { > + let shrinker =3D self.shrinker; > + let ptr =3D shrinker.as_ptr(); > + > + // The destructor of `self` calls `shrinker_free`, so skip = the destructor. > + core::mem::forget(self); > + > + let private_data_ptr =3D ::into_foreign(private_data); > + I don=E2=80=99t usually comment on style because there's a great deal of = personal taste involved. OTOH, I find this a bit hard to read. Maybe I am not the only one? A = line-break per SAFETY would improve legibility IMHO. > + // SAFETY: We own the private data, so we can assign to it. > + unsafe { (*ptr).private_data =3D private_data_ptr.cast_mut() = }; > + // SAFETY: The shrinker is not yet registered, so we can = update this field. > + unsafe { (*ptr).count_objects =3D = Some(rust_count_objects::) }; > + // SAFETY: The shrinker is not yet registered, so we can = update this field. > + unsafe { (*ptr).scan_objects =3D Some(rust_scan_objects::) = }; > + > + // SAFETY: The shrinker is unregistered, so it's safe to = register it. > + unsafe { bindings::shrinker_register(ptr) }; > + > + ShrinkerRegistration { > + shrinker, > + _phantom: PhantomData, > + } > + } > +} > + > +impl Drop for ShrinkerBuilder { > + fn drop(&mut self) { > + // SAFETY: The shrinker is a valid but unregistered shrinker, = and we will not use it > + // anymore. > + unsafe { bindings::shrinker_free(self.shrinker.as_ptr()) }; > + } > +} > + > +/// A shrinker that is registered with the kernel. > +/// > +/// # Invariants > +/// > +/// The `shrinker` pointer refers to a registered shrinker using `T` = as the private data. > +pub struct ShrinkerRegistration { > + shrinker: NonNull, > + _phantom: PhantomData, > +} > + > +// SAFETY: This allows you to deregister the shrinker from a = different thread, which means that > +// private data could be dropped from any thread. > +unsafe impl Send for ShrinkerRegistration where = T::Ptr: Send {} > +// SAFETY: The only thing you can do with an immutable reference is = access the private data, which > +// is okay to access in parallel as the `Shrinker` trait requires the = private data to be `Sync`. > +unsafe impl Sync for ShrinkerRegistration {} > + > +impl ShrinkerRegistration { > + /// Access the private data in this shrinker. > + pub fn private_data(&self) -> ::Borrowed<'_> { > + // SAFETY: We own the private data, so we can access it. > + let private =3D unsafe { = (*self.shrinker.as_ptr()).private_data }; > + // SAFETY: By the type invariants, the private data is `T`. = This access could happen in > + // parallel with a shrinker callback, but that's okay as the = `Shrinker` trait ensures that > + // `T::Ptr` is `Sync`. > + unsafe { ::borrow(private) } > + } > +} > + > +impl Drop for ShrinkerRegistration { > + fn drop(&mut self) { > + // SAFETY: We own the private data, so we can access it. > + let private =3D unsafe { = (*self.shrinker.as_ptr()).private_data }; > + // SAFETY: We will not access the shrinker after this call. > + unsafe { bindings::shrinker_free(self.shrinker.as_ptr()) }; > + // SAFETY: The above call blocked until the completion of any = shrinker callbacks, so there > + // are no longer any users of the private data. > + drop(unsafe { ::from_foreign(private) }); > + } > +} > + > +/// Callbacks for a shrinker. > +pub trait Shrinker { > + /// The pointer type used to store the private data of the = shrinker. > + /// > + /// Needs to be `Sync` because the shrinker callback could access = this value immutably from > + /// several thread in parallel. > + type Ptr: ForeignOwnable + Sync; > + > + /// Count the number of freeable items in the cache. > + fn count_objects( > + me: ::Borrowed<'_>, > + sc: ShrinkControl<'_>, > + ) -> CountObjects; > + > + /// Free some objects in this cache. > + fn scan_objects( > + me: ::Borrowed<'_>, > + sc: ShrinkControl<'_>, > + ) -> ScanObjects; > +} > + > +/// How many objects are there in the cache? > +/// > +/// This is used as the return value of [`Shrinker::count_objects`]. > +pub struct CountObjects { > + inner: c_ulong, > +} > + > +impl CountObjects { > + /// Indicates that the number of objects is zero. > + pub const EMPTY: Self =3D Self { > + inner: SHRINK_EMPTY, > + }; > + > + /// The maximum possible number of freeable objects. > + pub const MAX: Self =3D Self { > + // The shrinker code assumes that it can multiply this value = by two without overflow. > + inner: c_ulong::MAX / 2, > + }; > + > + /// Creates a new `CountObjects` with the given value. > + /// > + /// This should be the number of objects that were actually = freed. Objects that were scanned > + /// but not freed should be counted in `nr_scanned` but not here. This is apparently wrong? The count_objects callback computes how many = objects *can* be freed. > + /// > + /// If `count` is zero, then this indicates that the real count = is unknown. Use > + /// `CountObjects::EMPTY` to indicate that the shrinker is empty. > + pub fn new(count: usize) -> Self { > + if count > Self::MAX.inner as usize { > + return Self::MAX; > + } nit: maybe a comment explaining why silently truncating is preferable? =20 > + > + Self { > + inner: count as c_ulong, > + } > + } > +} > + > +/// How many objects were freed? > +/// > +/// This is used as the return value of [`Shrinker::scan_objects`]. > +pub struct ScanObjects { > + inner: c_ulong, > +} > + > +impl ScanObjects { > + /// Indicates that the shrinker should stop trying to free = objects from this cache due to > + /// potential deadlocks. > + pub const STOP: Self =3D Self { inner: SHRINK_STOP }; > + > + /// The maximum possible number of freeable objects. > + pub const MAX: Self =3D Self { > + inner: SHRINK_STOP - 1, > + }; > + > + /// Creates a new `CountObjects` with the given value. > + pub fn from_count(count: usize) -> Self { > + if count > Self::MAX.inner as usize { > + return Self::MAX; > + } > + > + Self { > + inner: count as c_ulong, > + } > + } > +} > + > +/// This struct is used to pass information from page reclaim to the = shrinkers. > +/// > +/// # Invariants > +/// > +/// `ptr` has exclusive access to a valid `struct shrink_control`. > +pub struct ShrinkControl<'a> { > + ptr: NonNull, > + _phantom: PhantomData<&'a bindings::shrink_control>, > +} > + > +impl<'a> ShrinkControl<'a> { > + /// Create a `ShrinkControl` from a raw pointer. > + /// > + /// # Safety > + /// > + /// The pointer should point at a valid `shrink_control` for the = duration of 'a. > + pub unsafe fn from_raw(ptr: *mut bindings::shrink_control) -> = Self { > + Self { > + // SAFETY: Caller promises that this pointer is valid. > + ptr: unsafe { NonNull::new_unchecked(ptr) }, > + _phantom: PhantomData, > + } > + } > + > + /// Determines whether it is safe to call into filesystem code. > + pub fn reclaim_fs_allowed(&self) -> bool { > + // SAFETY: Okay by type invariants. > + let mask =3D unsafe { (*self.ptr.as_ptr()).gfp_mask }; > + > + (mask & bindings::__GFP_FS) !=3D 0 > + } > + > + /// Determines whether it is safe to call into IO code. > + pub fn reclaim_io_allowed(&self) -> bool { > + // SAFETY: Okay by type invariants. > + let mask =3D unsafe { (*self.ptr.as_ptr()).gfp_mask }; > + > + (mask & bindings::__GFP_IO) !=3D 0 > + } > + > + /// Returns the number of objects that `scan_objects` should try = to reclaim. > + pub fn nr_to_scan(&self) -> usize { > + // SAFETY: Okay by type invariants. > + unsafe { (*self.ptr.as_ptr()).nr_to_scan as usize } > + } > + > + /// The callback should set this value to the number of objects = inspected by the shrinker. > + pub fn set_nr_scanned(&mut self, val: usize) { > + // SAFETY: Okay by type invariants. > + unsafe { (*self.ptr.as_ptr()).nr_scanned =3D val as c_ulong = }; > + } > +} > + > +unsafe extern "C" fn rust_count_objects( > + shrink: *mut bindings::shrinker, > + sc: *mut bindings::shrink_control, > +) -> c_ulong { > + // SAFETY: We own the private data, so we can access it. > + let private =3D unsafe { (*shrink).private_data }; > + // SAFETY: This function is only used with shrinkers where `T` is = the type of the private data. > + let private =3D unsafe { ::borrow(private) }; > + // SAFETY: The caller passes a valid `sc` pointer. > + let sc =3D unsafe { ShrinkControl::from_raw(sc) }; > + > + let ret =3D T::count_objects(private, sc); > + ret.inner > +} > + > +unsafe extern "C" fn rust_scan_objects( > + shrink: *mut bindings::shrinker, > + sc: *mut bindings::shrink_control, > +) -> c_ulong { > + // SAFETY: We own the private data, so we can access it. > + let private =3D unsafe { (*shrink).private_data }; > + // SAFETY: This function is only used with shrinkers where `T` is = the type of the private data. > + let private =3D unsafe { ::borrow(private) }; > + // SAFETY: The caller passes a valid `sc` pointer. > + let sc =3D unsafe { ShrinkControl::from_raw(sc) }; > + > + let ret =3D T::scan_objects(private, sc); > + ret.inner > +} >=20 > --- > base-commit: 8cf0b93919e13d1e8d4466eb4080a4c4d9d66d7b > change-id: 20240911-shrinker-f8371af00b68 >=20 > Best regards, > --=20 > Alice Ryhl >=20 >=20 =E2=80=94 Daniel