* Re: [PATCH 1/5] liveupdate: Remove limit on the number of sessions [not found] ` <20260414200237.444170-2-pasha.tatashin@soleen.com> @ 2026-05-12 13:35 ` Pratyush Yadav 2026-05-12 15:55 ` Pasha Tatashin 0 siblings, 1 reply; 6+ messages in thread From: Pratyush Yadav @ 2026-05-12 13:35 UTC (permalink / raw) To: Pasha Tatashin Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, linux-kernel, dmatlack, kexec, pratyush, skhawaja, graf On Tue, Apr 14 2026, Pasha Tatashin wrote: > Currently, the number of LUO sessions is limited by a fixed number of > pre-allocated pages for serialization (16 pages, allowing for ~819 > sessions). > > This limitation is problematic if LUO is used to support things such as > systemd file descriptor store, and would be used not just as VM memory > but to save other states on the machine. > > Remove this limit by transitioning to a linked-block approach for > session metadata serialization. Instead of a single contiguous block, > session metadata is now stored in a chain of 16-page blocks. Each block > starts with a header containing the physical address of the next block > and the number of session entries in the current block. We now have 3 variants of this linked block data structure: LUO sessions, LUO files, and KHO vmalloc. Is it time now to unify them into a reusable data structure? I proposed "KHO Array" some time ago. That was a collection of pointers, but perhaps we can generalize that to a collection of elements of arbitrary size? [0] https://lore.kernel.org/linux-mm/20250909144426.33274-1-pratyush@kernel.org/T/#u > > - Bump session ABI version to v3. > - Update struct luo_session_header_ser to include a 'next' pointer. > - Implement dynamic block allocation in luo_session_insert(). > - Update setup, serialization, and deserialization logic to traverse > the block chain. > - Remove LUO_SESSION_MAX limit. > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> > --- > include/linux/kho/abi/luo.h | 19 +-- > kernel/liveupdate/luo_internal.h | 12 +- > kernel/liveupdate/luo_session.c | 237 +++++++++++++++++++++++-------- > 3 files changed, 197 insertions(+), 71 deletions(-) > > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h > index 46750a0ddf88..f5732958545e 100644 > --- a/include/linux/kho/abi/luo.h > +++ b/include/linux/kho/abi/luo.h > @@ -57,9 +57,10 @@ > * - compatible: "luo-session-v1" > * Identifies the session ABI version. > * - luo-session-header: u64 > - * The physical address of a `struct luo_session_header_ser`. This structure > - * is the header for a contiguous block of memory containing an array of > - * `struct luo_session_ser`, one for each preserved session. > + * The physical address of the first `struct luo_session_header_ser`. > + * This structure is the header for a block of memory containing an array > + * of `struct luo_session_ser` entries. Multiple blocks are linked via > + * the `next` field in the header. > * > * File-Lifecycle-Bound Node (luo-flb): > * This node describes all preserved global objects whose lifecycle is bound > @@ -77,9 +78,9 @@ > * `__packed` structures. These structures contain the actual preserved state. > * > * - struct luo_session_header_ser: > - * Header for the session array. Contains the total page count of the > - * preserved memory block and the number of `struct luo_session_ser` > - * entries that follow. > + * Header for the session data block. Contains the physical address of the > + * next session data block and the number of `struct luo_session_ser` > + * entries that follow this header in the current block. > * > * - struct luo_session_ser: > * Metadata for a single session, including its name and a physical pointer > @@ -153,21 +154,23 @@ struct luo_file_set_ser { > * luo_session_header_ser > */ > #define LUO_FDT_SESSION_NODE_NAME "luo-session" > -#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v2" > +#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v3" > #define LUO_FDT_SESSION_HEADER "luo-session-header" > > /** > * struct luo_session_header_ser - Header for the serialized session data block. > + * @next: Physical address of the next struct luo_session_header_ser. > * @count: The number of `struct luo_session_ser` entries that immediately > * follow this header in the memory block. > * > - * This structure is located at the beginning of a contiguous block of > + * This structure is located at the beginning of a block of > * physical memory preserved across the kexec. It provides the necessary > * metadata to interpret the array of session entries that follow. > * > * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated. > */ > struct luo_session_header_ser { > + u64 next; > u64 count; > } __packed; > > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h > index 875844d7a41d..a73f42069301 100644 > --- a/kernel/liveupdate/luo_internal.h > +++ b/kernel/liveupdate/luo_internal.h > @@ -11,6 +11,16 @@ > #include <linux/liveupdate.h> > #include <linux/uaccess.h> > > +/* > + * Safeguard limit for the number of serialization blocks. This is used to > + * prevent infinite loops and excessive memory allocation in case of memory > + * corruption in the preserved state. > + * > + * This limit allows for ~8.1 million sessions and ~1.2 million files per > + * session, which is more than enough for all realistic use cases. > + */ > +#define LUO_MAX_BLOCKS 10000 > + > struct luo_ucmd { > void __user *ubuffer; > u32 user_size; > @@ -59,7 +69,6 @@ struct luo_file_set { > * struct luo_session - Represents an active or incoming Live Update session. > * @name: A unique name for this session, used for identification and > * retrieval. > - * @ser: Pointer to the serialized data for this session. > * @list: A list_head member used to link this session into a global list > * of either outgoing (to be preserved) or incoming (restored from > * previous kernel) sessions. > @@ -70,7 +79,6 @@ struct luo_file_set { > */ > struct luo_session { > char name[LIVEUPDATE_SESSION_NAME_LENGTH]; > - struct luo_session_ser *ser; I was confused by this removal. Seeing this makes one think this got moved to some other place. But it seems like this was never used. I think it would be good to mention that in the commit message. > struct list_head list; > bool retrieved; > struct luo_file_set file_set; > diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c > index 92b1af791889..007ca34eba79 100644 > --- a/kernel/liveupdate/luo_session.c > +++ b/kernel/liveupdate/luo_session.c > @@ -69,30 +69,39 @@ > #include <uapi/linux/liveupdate.h> > #include "luo_internal.h" > > -/* 16 4K pages, give space for 744 sessions */ > +/* 16 4K pages, give space for 819 sessions per block */ It seems odd to read that we added 8 bytes to the header and the number of sessions per block grew. But then I did the math and I think the number was always 819 sessions per block and adding the extra 8 bytes didn't make a difference. > #define LUO_SESSION_PGCNT 16ul > -#define LUO_SESSION_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \ > +#define LUO_SESSION_BLOCK_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \ > sizeof(struct luo_session_header_ser)) / \ > sizeof(struct luo_session_ser)) > > +/** > + * struct luo_session_block - Internal representation of a session serialization block. > + * @list: List head for linking blocks in memory. > + * @ser: Pointer to the serialized header in preserved memory. > + */ > +struct luo_session_block { > + struct list_head list; > + struct luo_session_header_ser *ser; Nit: luo_session_header_ser reads like it is the header for the entire list not for each block. Perhaps rename it to luo_block_header_ser? > +}; > + > /** > * struct luo_session_header - Header struct for managing LUO sessions. > * @count: The number of sessions currently tracked in the @list. > + * @nblocks: The number of allocated serialization blocks. > * @list: The head of the linked list of `struct luo_session` instances. > * @rwsem: A read-write semaphore providing synchronized access to the > * session list and other fields in this structure. > - * @header_ser: The header data of serialization array. > - * @ser: The serialized session data (an array of > - * `struct luo_session_ser`). > + * @blocks: The list of serialization blocks (struct luo_session_block). > * @active: Set to true when first initialized. If previous kernel did not > * send session data, active stays false for incoming. > */ > struct luo_session_header { > long count; > + long nblocks; > struct list_head list; > struct rw_semaphore rwsem; > - struct luo_session_header_ser *header_ser; > - struct luo_session_ser *ser; > + struct list_head blocks; > bool active; > }; > > @@ -110,10 +119,12 @@ static struct luo_session_global luo_session_global = { > .incoming = { > .list = LIST_HEAD_INIT(luo_session_global.incoming.list), > .rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem), > + .blocks = LIST_HEAD_INIT(luo_session_global.incoming.blocks), > }, > .outgoing = { > .list = LIST_HEAD_INIT(luo_session_global.outgoing.list), > .rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem), > + .blocks = LIST_HEAD_INIT(luo_session_global.outgoing.blocks), > }, > }; > > @@ -140,6 +151,70 @@ static void luo_session_free(struct luo_session *session) > kfree(session); > } > > +static int luo_session_add_block(struct luo_session_header *sh, > + struct luo_session_header_ser *ser) > +{ > + struct luo_session_block *block; > + > + if (sh->nblocks >= LUO_MAX_BLOCKS) > + return -ENOSPC; > + > + block = kzalloc_obj(*block); > + if (!block) > + return -ENOMEM; > + > + block->ser = ser; > + list_add_tail(&block->list, &sh->blocks); > + sh->nblocks++; > + > + return 0; > +} > + > +static int luo_session_create_ser_block(struct luo_session_header *sh) > +{ > + struct luo_session_block *last = NULL; > + struct luo_session_header_ser *ser; > + int err; > + > + ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT); > + if (IS_ERR(ser)) > + return PTR_ERR(ser); > + > + if (!list_empty(&sh->blocks)) > + last = list_last_entry(&sh->blocks, struct luo_session_block, list); Nit: using list_last_entry_or_null() is a tiny bit cleaner. > + > + err = luo_session_add_block(sh, ser); > + if (err) > + goto err_unpreserve; > + > + if (last) > + last->ser->next = virt_to_phys(ser); Nit: can you please move this to luo_session_add_block(). Logically this operation is a part of adding a block. You add a block to the list and then update the serialized state. So would be nice to have it done in one place. > + > + return 0; > + > +err_unpreserve: > + kho_unpreserve_free(ser); > + return err; > +} > + > +static void luo_session_destroy_ser_blocks(struct luo_session_header *sh, > + bool unpreserve) > +{ > + struct luo_session_block *block, *tmp; > + > + list_for_each_entry_safe(block, tmp, &sh->blocks, list) { > + if (block->ser) { Block always has ser. Why this check? > + if (unpreserve) > + kho_unpreserve_free(block->ser); > + else > + kho_restore_free(block->ser); Ugh, this is ugly. But I don't see anything obviously better. Perhaps we can check for sh == luo_session_global.outgoing but that is probably worse. > + } > + list_del(&block->list); > + kfree(block); > + sh->nblocks--; > + } > +} > + > static int luo_session_insert(struct luo_session_header *sh, > struct luo_session *session) > { > @@ -147,15 +222,6 @@ static int luo_session_insert(struct luo_session_header *sh, > > guard(rwsem_write)(&sh->rwsem); > > - /* > - * For outgoing we should make sure there is room in serialization array > - * for new session. > - */ > - if (sh == &luo_session_global.outgoing) { > - if (sh->count == LUO_SESSION_MAX) > - return -ENOMEM; > - } > - > /* > * For small number of sessions this loop won't hurt performance > * but if we ever start using a lot of sessions, this might > @@ -166,6 +232,20 @@ static int luo_session_insert(struct luo_session_header *sh, > if (!strncmp(it->name, session->name, sizeof(it->name))) > return -EEXIST; > } > + > + /* > + * For outgoing we should make sure there is room in serialization array > + * for new session. If not, allocate a new block. > + */ > + if (sh == &luo_session_global.outgoing) { > + if (sh->count == sh->nblocks * LUO_SESSION_BLOCK_MAX) { > + int err = luo_session_create_ser_block(sh); > + > + if (err) > + return err; > + } > + } > + Since we just allocate space here and not actually fill it yet, I think we can do the same check in luo_session_remove() to free blocks once session count falls below (sh->nblocks - 1) * LUO_SESSION_BLOCK_MAX. This prevents memory leak if the number of sessions goes too high at some point and then falls back down. Not that I think it is something likely to happen, but I don't see why not. Perhaps also abstract this out to a helper function for readability? > list_add_tail(&session->list, &sh->list); > sh->count++; > > @@ -444,9 +524,12 @@ int __init luo_session_setup_outgoing(void *fdt_out) > u64 header_ser_pa; > int err; > > - header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT); > - if (IS_ERR(header_ser)) > - return PTR_ERR(header_ser); > + err = luo_session_create_ser_block(&luo_session_global.outgoing); > + if (err) > + return err; > + > + header_ser = list_first_entry(&luo_session_global.outgoing.blocks, > + struct luo_session_block, list)->ser; I suppose it would be a tiny bit better to create a placeholder entry here and then fill it up later in luo_session_serialize(). This would result in the first block not being a special case and it can be allocated and freed on demand list the rest of the blocks. I won't insist on it but would be nice to have IMO if you're willing to do the refactor. > header_ser_pa = virt_to_phys(header_ser); > > err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME); > @@ -459,19 +542,18 @@ int __init luo_session_setup_outgoing(void *fdt_out) > if (err) > goto err_unpreserve; > > - luo_session_global.outgoing.header_ser = header_ser; > - luo_session_global.outgoing.ser = (void *)(header_ser + 1); > luo_session_global.outgoing.active = true; > > return 0; > > err_unpreserve: > - kho_unpreserve_free(header_ser); > + luo_session_destroy_ser_blocks(&luo_session_global.outgoing, true); > return err; > } > > int __init luo_session_setup_incoming(void *fdt_in) > { > + struct luo_session_header *sh = &luo_session_global.incoming; > struct luo_session_header_ser *header_ser; > int err, header_size, offset; > u64 header_ser_pa; [...] -- Regards, Pratyush Yadav ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/5] liveupdate: Remove limit on the number of sessions 2026-05-12 13:35 ` [PATCH 1/5] liveupdate: Remove limit on the number of sessions Pratyush Yadav @ 2026-05-12 15:55 ` Pasha Tatashin 0 siblings, 0 replies; 6+ messages in thread From: Pasha Tatashin @ 2026-05-12 15:55 UTC (permalink / raw) To: Pratyush Yadav Cc: Pasha Tatashin, linux-kselftest, rppt, shuah, akpm, linux-mm, linux-kernel, dmatlack, kexec, skhawaja, graf On 05-12 15:35, Pratyush Yadav wrote: > On Tue, Apr 14 2026, Pasha Tatashin wrote: > > > Currently, the number of LUO sessions is limited by a fixed number of > > pre-allocated pages for serialization (16 pages, allowing for ~819 > > sessions). > > > > This limitation is problematic if LUO is used to support things such as > > systemd file descriptor store, and would be used not just as VM memory > > but to save other states on the machine. > > > > Remove this limit by transitioning to a linked-block approach for > > session metadata serialization. Instead of a single contiguous block, > > session metadata is now stored in a chain of 16-page blocks. Each block > > starts with a header containing the physical address of the next block > > and the number of session entries in the current block. > > We now have 3 variants of this linked block data structure: LUO > sessions, LUO files, and KHO vmalloc. Is it time now to unify them into > a reusable data structure? I proposed "KHO Array" some time ago. That > was a collection of pointers, but perhaps we can generalize that to a > collection of elements of arbitrary size? I agree, especially since there are a couple more users incoming. I know Sami is proposing this for IOMMU as well. He and I have talked about creating a library that others could use. My suggestion is to get this in now and unify it later into something that works for all use cases, rather than changing it for every new one. The KHO ABI is not stable, so we can change it at any time, but I would still like to minimize the number of times we do so. > [0] https://lore.kernel.org/linux-mm/20250909144426.33274-1-pratyush@kernel.org/T/#u > > > > > - Bump session ABI version to v3. > > - Update struct luo_session_header_ser to include a 'next' pointer. > > - Implement dynamic block allocation in luo_session_insert(). > > - Update setup, serialization, and deserialization logic to traverse > > the block chain. > > - Remove LUO_SESSION_MAX limit. > > > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> > > --- > > include/linux/kho/abi/luo.h | 19 +-- > > kernel/liveupdate/luo_internal.h | 12 +- > > kernel/liveupdate/luo_session.c | 237 +++++++++++++++++++++++-------- > > 3 files changed, 197 insertions(+), 71 deletions(-) > > > > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h > > index 46750a0ddf88..f5732958545e 100644 > > --- a/include/linux/kho/abi/luo.h > > +++ b/include/linux/kho/abi/luo.h > > @@ -57,9 +57,10 @@ > > * - compatible: "luo-session-v1" > > * Identifies the session ABI version. > > * - luo-session-header: u64 > > - * The physical address of a `struct luo_session_header_ser`. This structure > > - * is the header for a contiguous block of memory containing an array of > > - * `struct luo_session_ser`, one for each preserved session. > > + * The physical address of the first `struct luo_session_header_ser`. > > + * This structure is the header for a block of memory containing an array > > + * of `struct luo_session_ser` entries. Multiple blocks are linked via > > + * the `next` field in the header. > > * > > * File-Lifecycle-Bound Node (luo-flb): > > * This node describes all preserved global objects whose lifecycle is bound > > @@ -77,9 +78,9 @@ > > * `__packed` structures. These structures contain the actual preserved state. > > * > > * - struct luo_session_header_ser: > > - * Header for the session array. Contains the total page count of the > > - * preserved memory block and the number of `struct luo_session_ser` > > - * entries that follow. > > + * Header for the session data block. Contains the physical address of the > > + * next session data block and the number of `struct luo_session_ser` > > + * entries that follow this header in the current block. > > * > > * - struct luo_session_ser: > > * Metadata for a single session, including its name and a physical pointer > > @@ -153,21 +154,23 @@ struct luo_file_set_ser { > > * luo_session_header_ser > > */ > > #define LUO_FDT_SESSION_NODE_NAME "luo-session" > > -#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v2" > > +#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v3" > > #define LUO_FDT_SESSION_HEADER "luo-session-header" > > > > /** > > * struct luo_session_header_ser - Header for the serialized session data block. > > + * @next: Physical address of the next struct luo_session_header_ser. > > * @count: The number of `struct luo_session_ser` entries that immediately > > * follow this header in the memory block. > > * > > - * This structure is located at the beginning of a contiguous block of > > + * This structure is located at the beginning of a block of > > * physical memory preserved across the kexec. It provides the necessary > > * metadata to interpret the array of session entries that follow. > > * > > * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated. > > */ > > struct luo_session_header_ser { > > + u64 next; > > u64 count; > > } __packed; > > > > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h > > index 875844d7a41d..a73f42069301 100644 > > --- a/kernel/liveupdate/luo_internal.h > > +++ b/kernel/liveupdate/luo_internal.h > > @@ -11,6 +11,16 @@ > > #include <linux/liveupdate.h> > > #include <linux/uaccess.h> > > > > +/* > > + * Safeguard limit for the number of serialization blocks. This is used to > > + * prevent infinite loops and excessive memory allocation in case of memory > > + * corruption in the preserved state. > > + * > > + * This limit allows for ~8.1 million sessions and ~1.2 million files per > > + * session, which is more than enough for all realistic use cases. > > + */ > > +#define LUO_MAX_BLOCKS 10000 > > + > > struct luo_ucmd { > > void __user *ubuffer; > > u32 user_size; > > @@ -59,7 +69,6 @@ struct luo_file_set { > > * struct luo_session - Represents an active or incoming Live Update session. > > * @name: A unique name for this session, used for identification and > > * retrieval. > > - * @ser: Pointer to the serialized data for this session. > > * @list: A list_head member used to link this session into a global list > > * of either outgoing (to be preserved) or incoming (restored from > > * previous kernel) sessions. > > @@ -70,7 +79,6 @@ struct luo_file_set { > > */ > > struct luo_session { > > char name[LIVEUPDATE_SESSION_NAME_LENGTH]; > > - struct luo_session_ser *ser; > > I was confused by this removal. Seeing this makes one think this got > moved to some other place. But it seems like this was never used. I > think it would be good to mention that in the commit message. Let me move this to a separate commit, may be together with the comment fix below. > > > struct list_head list; > > bool retrieved; > > struct luo_file_set file_set; > > diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c > > index 92b1af791889..007ca34eba79 100644 > > --- a/kernel/liveupdate/luo_session.c > > +++ b/kernel/liveupdate/luo_session.c > > @@ -69,30 +69,39 @@ > > #include <uapi/linux/liveupdate.h> > > #include "luo_internal.h" > > > > -/* 16 4K pages, give space for 744 sessions */ > > +/* 16 4K pages, give space for 819 sessions per block */ > > It seems odd to read that we added 8 bytes to the header and the number > of sessions per block grew. But then I did the math and I think the > number was always 819 sessions per block and adding the extra 8 bytes > didn't make a difference. Yeah, I updated the comment while at it, 744 was true in some earlier LUO versions, but was never updated. > > > #define LUO_SESSION_PGCNT 16ul > > -#define LUO_SESSION_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \ > > +#define LUO_SESSION_BLOCK_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \ > > sizeof(struct luo_session_header_ser)) / \ > > sizeof(struct luo_session_ser)) > > > > +/** > > + * struct luo_session_block - Internal representation of a session serialization block. > > + * @list: List head for linking blocks in memory. > > + * @ser: Pointer to the serialized header in preserved memory. > > + */ > > +struct luo_session_block { > > + struct list_head list; > > + struct luo_session_header_ser *ser; > > Nit: luo_session_header_ser reads like it is the header for the entire > list not for each block. Perhaps rename it to luo_block_header_ser? Sounds good I will rename this to: luo_session_block_header_ser and, in the next patch luo_file_block_header_ser accordingly. > > > +}; > > + > > /** > > * struct luo_session_header - Header struct for managing LUO sessions. > > * @count: The number of sessions currently tracked in the @list. > > + * @nblocks: The number of allocated serialization blocks. > > * @list: The head of the linked list of `struct luo_session` instances. > > * @rwsem: A read-write semaphore providing synchronized access to the > > * session list and other fields in this structure. > > - * @header_ser: The header data of serialization array. > > - * @ser: The serialized session data (an array of > > - * `struct luo_session_ser`). > > + * @blocks: The list of serialization blocks (struct luo_session_block). > > * @active: Set to true when first initialized. If previous kernel did not > > * send session data, active stays false for incoming. > > */ > > struct luo_session_header { > > long count; > > + long nblocks; > > struct list_head list; > > struct rw_semaphore rwsem; > > - struct luo_session_header_ser *header_ser; > > - struct luo_session_ser *ser; > > + struct list_head blocks; > > bool active; > > }; > > > > @@ -110,10 +119,12 @@ static struct luo_session_global luo_session_global = { > > .incoming = { > > .list = LIST_HEAD_INIT(luo_session_global.incoming.list), > > .rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem), > > + .blocks = LIST_HEAD_INIT(luo_session_global.incoming.blocks), > > }, > > .outgoing = { > > .list = LIST_HEAD_INIT(luo_session_global.outgoing.list), > > .rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem), > > + .blocks = LIST_HEAD_INIT(luo_session_global.outgoing.blocks), > > }, > > }; > > > > @@ -140,6 +151,70 @@ static void luo_session_free(struct luo_session *session) > > kfree(session); > > } > > > > +static int luo_session_add_block(struct luo_session_header *sh, > > + struct luo_session_header_ser *ser) > > +{ > > + struct luo_session_block *block; > > + > > + if (sh->nblocks >= LUO_MAX_BLOCKS) > > + return -ENOSPC; > > + > > + block = kzalloc_obj(*block); > > + if (!block) > > + return -ENOMEM; > > + > > + block->ser = ser; > > + list_add_tail(&block->list, &sh->blocks); > > + sh->nblocks++; > > + > > + return 0; > > +} > > + > > +static int luo_session_create_ser_block(struct luo_session_header *sh) > > +{ > > + struct luo_session_block *last = NULL; > > + struct luo_session_header_ser *ser; > > + int err; > > + > > + ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT); > > + if (IS_ERR(ser)) > > + return PTR_ERR(ser); > > + > > + if (!list_empty(&sh->blocks)) > > + last = list_last_entry(&sh->blocks, struct luo_session_block, list); > > Nit: using list_last_entry_or_null() is a tiny bit cleaner. Done. > > > + > > + err = luo_session_add_block(sh, ser); > > + if (err) > > + goto err_unpreserve; > > + > > + if (last) > > + last->ser->next = virt_to_phys(ser); > > Nit: can you please move this to luo_session_add_block(). Logically this > operation is a part of adding a block. You add a block to the list and > then update the serialized state. So would be nice to have it done in > one place. Done. > > > + > > + return 0; > > + > > +err_unpreserve: > > + kho_unpreserve_free(ser); > > + return err; > > +} > > + > > +static void luo_session_destroy_ser_blocks(struct luo_session_header *sh, > > + bool unpreserve) > > +{ > > + struct luo_session_block *block, *tmp; > > + > > + list_for_each_entry_safe(block, tmp, &sh->blocks, list) { > > + if (block->ser) { > > Block always has ser. Why this check? Removed, thanks! > > > + if (unpreserve) > > + kho_unpreserve_free(block->ser); > > + else > > + kho_restore_free(block->ser); > > Ugh, this is ugly. But I don't see anything obviously better. Perhaps we > can check for sh == luo_session_global.outgoing but that is probably > worse. > > > + } > > + list_del(&block->list); > > + kfree(block); > > + sh->nblocks--; > > + } > > +} > > + > > static int luo_session_insert(struct luo_session_header *sh, > > struct luo_session *session) > > { > > @@ -147,15 +222,6 @@ static int luo_session_insert(struct luo_session_header *sh, > > > > guard(rwsem_write)(&sh->rwsem); > > > > - /* > > - * For outgoing we should make sure there is room in serialization array > > - * for new session. > > - */ > > - if (sh == &luo_session_global.outgoing) { > > - if (sh->count == LUO_SESSION_MAX) > > - return -ENOMEM; > > - } > > - > > /* > > * For small number of sessions this loop won't hurt performance > > * but if we ever start using a lot of sessions, this might > > @@ -166,6 +232,20 @@ static int luo_session_insert(struct luo_session_header *sh, > > if (!strncmp(it->name, session->name, sizeof(it->name))) > > return -EEXIST; > > } > > + > > + /* > > + * For outgoing we should make sure there is room in serialization array > > + * for new session. If not, allocate a new block. > > + */ > > + if (sh == &luo_session_global.outgoing) { > > + if (sh->count == sh->nblocks * LUO_SESSION_BLOCK_MAX) { > > + int err = luo_session_create_ser_block(sh); > > + > > + if (err) > > + return err; > > + } > > + } > > + > > Since we just allocate space here and not actually fill it yet, I think > we can do the same check in luo_session_remove() to free blocks once > session count falls below (sh->nblocks - 1) * LUO_SESSION_BLOCK_MAX. > This prevents memory leak if the number of sessions goes too high at > some point and then falls back down. > > Not that I think it is something likely to happen, but I don't see why > not. Done. I did not want to add this functionality just to keep the patch simpler, but since you brought it up, I am adding it here. > > Perhaps also abstract this out to a helper function for readability? Done > > > list_add_tail(&session->list, &sh->list); > > sh->count++; > > > > @@ -444,9 +524,12 @@ int __init luo_session_setup_outgoing(void *fdt_out) > > u64 header_ser_pa; > > int err; > > > > - header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT); > > - if (IS_ERR(header_ser)) > > - return PTR_ERR(header_ser); > > + err = luo_session_create_ser_block(&luo_session_global.outgoing); > > + if (err) > > + return err; > > + > > + header_ser = list_first_entry(&luo_session_global.outgoing.blocks, > > + struct luo_session_block, list)->ser; > > I suppose it would be a tiny bit better to create a placeholder entry > here and then fill it up later in luo_session_serialize(). This would > result in the first block not being a special case and it can be > allocated and freed on demand list the rest of the blocks. > > I won't insist on it but would be nice to have IMO if you're willing to > do the refactor. Let's keep it as is for now. Perhaps, we can return to this once we have a unifying library for KHO arrays. > > > header_ser_pa = virt_to_phys(header_ser); > > > > err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME); > > @@ -459,19 +542,18 @@ int __init luo_session_setup_outgoing(void *fdt_out) > > if (err) > > goto err_unpreserve; > > > > - luo_session_global.outgoing.header_ser = header_ser; > > - luo_session_global.outgoing.ser = (void *)(header_ser + 1); > > luo_session_global.outgoing.active = true; > > > > return 0; > > > > err_unpreserve: > > - kho_unpreserve_free(header_ser); > > + luo_session_destroy_ser_blocks(&luo_session_global.outgoing, true); > > return err; > > } > > > > int __init luo_session_setup_incoming(void *fdt_in) > > { > > + struct luo_session_header *sh = &luo_session_global.incoming; > > struct luo_session_header_ser *header_ser; > > int err, header_size, offset; > > u64 header_ser_pa; > [...] > > -- > Regards, > Pratyush Yadav ^ permalink raw reply [flat|nested] 6+ messages in thread
[parent not found: <20260414200237.444170-3-pasha.tatashin@soleen.com>]
* Re: [PATCH 2/5] liveupdate: Remove limit on the number of files per session [not found] ` <20260414200237.444170-3-pasha.tatashin@soleen.com> @ 2026-05-12 13:58 ` Pratyush Yadav 0 siblings, 0 replies; 6+ messages in thread From: Pratyush Yadav @ 2026-05-12 13:58 UTC (permalink / raw) To: Pasha Tatashin Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, linux-kernel, dmatlack, kexec, pratyush, skhawaja, graf On Tue, Apr 14 2026, Pasha Tatashin wrote: > To remove the fixed limit on the number of preserved files per session, > transition the file metadata serialization from a single contiguous memory > block to a chain of linked blocks. Same comment as previous patch about a general linked block data structure here. > > ABI Changes: > - Define 'struct luo_file_header_ser' to manage linked blocks in preserved > memory. > - Update 'struct luo_file_set_ser' to point to the head of the block chain. > - Bump the session ABI version to 'luo-session-v4' to reflect these breaking > changes. > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> > --- > include/linux/kho/abi/luo.h | 35 ++++- > kernel/liveupdate/luo_file.c | 226 ++++++++++++++++++++----------- > kernel/liveupdate/luo_internal.h | 17 ++- > 3 files changed, 189 insertions(+), 89 deletions(-) > > diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h > index f5732958545e..a15f6a0f3d93 100644 > --- a/include/linux/kho/abi/luo.h > +++ b/include/linux/kho/abi/luo.h > @@ -84,8 +84,13 @@ > * > * - struct luo_session_ser: > * Metadata for a single session, including its name and a physical pointer > - * to another preserved memory block containing an array of > - * `struct luo_file_ser` for all files in that session. > + * to the first `struct luo_file_header_ser` for all files in that session. > + * Multiple blocks are linked via the `next` field in the header. > + * > + * - struct luo_file_header_ser: > + * Header for the file data block. Contains the physical address of the > + * next file data block and the number of `struct luo_file_ser` entries > + * that follow this header in the current block. > * > * - struct luo_file_ser: > * Metadata for a single preserved file. Contains the `compatible` string to > @@ -134,11 +139,29 @@ struct luo_file_ser { > u64 token; > } __packed; > > +/** > + * struct luo_file_header_ser - Header for the serialized file data block. > + * @next: Physical address of the next struct luo_file_header_ser. > + * @count: The number of `struct luo_file_ser` entries that immediately > + * follow this header in the memory block. > + * > + * This structure is located at the beginning of a block of > + * physical memory preserved across the kexec. It provides the necessary > + * metadata to interpret the array of file entries that follow. > + * > + * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated. > + */ > +struct luo_file_header_ser { Nit: Same as before, can we rename it to make it clear that this is a block header? > + u64 next; > + u64 count; > +} __packed; > + > /** > * struct luo_file_set_ser - Represents the serialized metadata for file set > - * @files: The physical address of a contiguous memory block that holds > - * the serialized state of files (array of luo_file_ser) in this file > - * set. > + * @files: The physical address of the first `struct luo_file_header_ser`. > + * This structure is the header for a block of memory containing > + * an array of `struct luo_file_ser` entries. Multiple blocks are > + * linked via the `next` field in the header. > * @count: The total number of files that were part of this session during > * serialization. Used for iteration and validation during > * restoration. > @@ -154,7 +177,7 @@ struct luo_file_set_ser { > * luo_session_header_ser > */ > #define LUO_FDT_SESSION_NODE_NAME "luo-session" > -#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v3" > +#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v4" > #define LUO_FDT_SESSION_HEADER "luo-session-header" > > /** > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c > index dedbb8d02e6a..20a2d776cff4 100644 > --- a/kernel/liveupdate/luo_file.c > +++ b/kernel/liveupdate/luo_file.c > @@ -119,10 +119,12 @@ static LIST_HEAD(luo_file_handler_list); > /* Keep track of files being preserved by LUO */ > static DEFINE_XARRAY(luo_preserved_files); > > -/* 2 4K pages, give space for 128 files per file_set */ > +/* 2 4K pages, give space for 127 files per block */ > #define LUO_FILE_PGCNT 2ul > -#define LUO_FILE_MAX \ > - ((LUO_FILE_PGCNT << PAGE_SHIFT) / sizeof(struct luo_file_ser)) > +#define LUO_FILE_BLOCK_MAX \ > + (((LUO_FILE_PGCNT << PAGE_SHIFT) - \ > + sizeof(struct luo_file_header_ser)) / \ > + sizeof(struct luo_file_ser)) > > /** > * struct luo_file - Represents a single preserved file instance. > @@ -158,7 +160,7 @@ static DEFINE_XARRAY(luo_preserved_files); > * and the serialized state handle returned by the handler's .preserve() > * operation. > * > - * These instances are tracked in a per-file_set list. The @serialized_data > + * These structures are tracked in a per-file_set list. The @serialized_data > * field, which holds a handle to the file's serialized state, may be updated > * during the .freeze() callback before being serialized for the next kernel. > * After reboot, these structures are recreated by luo_file_deserialize() and > @@ -175,37 +177,68 @@ struct luo_file { > u64 token; > }; > > -static int luo_alloc_files_mem(struct luo_file_set *file_set) > +static int luo_file_add_block(struct luo_file_set *file_set, > + struct luo_file_header_ser *ser) > { > - size_t size; > - void *mem; > + struct luo_file_block *block; > > - if (file_set->files) > - return 0; > - > - WARN_ON_ONCE(file_set->count); > + if (file_set->nblocks >= LUO_MAX_BLOCKS) > + return -ENOSPC; > > - size = LUO_FILE_PGCNT << PAGE_SHIFT; > - mem = kho_alloc_preserve(size); > - if (IS_ERR(mem)) > - return PTR_ERR(mem); > + block = kzalloc_obj(*block); > + if (!block) > + return -ENOMEM; > > - file_set->files = mem; > + block->ser = ser; > + list_add_tail(&block->list, &file_set->blocks); > + file_set->nblocks++; > > return 0; > } > > -static void luo_free_files_mem(struct luo_file_set *file_set) > +static int luo_file_create_ser_block(struct luo_file_set *file_set) > { > - /* If file_set has files, no need to free preservation memory */ > - if (file_set->count) > - return; > + struct luo_file_block *last = NULL; > + struct luo_file_header_ser *ser; > + int err; > > - if (!file_set->files) > - return; > + ser = kho_alloc_preserve(LUO_FILE_PGCNT << PAGE_SHIFT); > + if (IS_ERR(ser)) > + return PTR_ERR(ser); > + > + if (!list_empty(&file_set->blocks)) > + last = list_last_entry(&file_set->blocks, struct luo_file_block, list); > + > + err = luo_file_add_block(file_set, ser); > + if (err) > + goto err_unpreserve; > + > + if (last) > + last->ser->next = virt_to_phys(ser); Same comments as last patch about list_last_entry_or_null() and moving this to luo_file_add_block(). > + > + return 0; > > - kho_unpreserve_free(file_set->files); > - file_set->files = NULL; > +err_unpreserve: > + kho_unpreserve_free(ser); > + return err; > +} > + > +static void luo_file_destroy_ser_blocks(struct luo_file_set *file_set, > + bool unpreserve) > +{ > + struct luo_file_block *block, *tmp; > + > + list_for_each_entry_safe(block, tmp, &file_set->blocks, list) { > + if (block->ser) { Same here. block is always created with its ser, so why have this check? > + if (unpreserve) > + kho_unpreserve_free(block->ser); > + else > + kho_restore_free(block->ser); And same comment about the nastiness here, but once again I have nothing better to offer. > + } > + list_del(&block->list); > + kfree(block); > + file_set->nblocks--; > + } > } > > static unsigned long luo_get_id(struct liveupdate_file_handler *fh, > @@ -247,7 +280,9 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token) > * 3. Iterates through registered handlers, calling can_preserve() to find one > * compatible with the given @fd. > * 4. Calls the handler's .preserve() operation, which saves the file's state > - * and returns an opaque private data handle. > + * and returns an opaque u64 handle. This is typically performed while the > + * workload is still active to minimize the downtime during the > + * actual reboot transition. > * 5. Adds the new instance to the file_set's internal list. > * > * On success, LUO takes a reference to the 'struct file' and considers it > @@ -277,17 +312,16 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) > if (luo_token_is_used(file_set, token)) > return -EEXIST; > > - if (file_set->count == LUO_FILE_MAX) > - return -ENOSPC; > + if (file_set->count == file_set->nblocks * LUO_FILE_BLOCK_MAX) { > + err = luo_file_create_ser_block(file_set); > + if (err) > + return err; > + } > > file = fget(fd); > if (!file) > return -EBADF; > > - err = luo_alloc_files_mem(file_set); > - if (err) > - goto err_fput; > - > err = -ENOENT; > down_read(&luo_register_rwlock); > list_private_for_each_entry(fh, &luo_file_handler_list, list) { > @@ -301,7 +335,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) > > /* err is still -ENOENT if no handler was found */ > if (err) > - goto err_free_files_mem; > + goto err_fput; > > /* safe to use fh because its module is pinned */ > err = xa_insert(&luo_preserved_files, luo_get_id(fh, file), > @@ -345,8 +379,6 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) > xa_erase(&luo_preserved_files, luo_get_id(fh, file)); > err_module_put: > module_put(fh->ops->owner); > -err_free_files_mem: > - luo_free_files_mem(file_set); > err_fput: > fput(file); > > @@ -399,7 +431,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set) > kfree(luo_file); > } > > - luo_free_files_mem(file_set); > + luo_file_destroy_ser_blocks(file_set, true); > } > > static int luo_file_freeze_one(struct luo_file_set *file_set, > @@ -446,6 +478,7 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set, > struct luo_file *failed_entry) > { > struct list_head *files_list = &file_set->files_list; > + struct luo_file_block *block; > struct luo_file *luo_file; > > list_for_each_entry(luo_file, files_list, list) { > @@ -455,7 +488,11 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set, > luo_file_unfreeze_one(file_set, luo_file); > } > > - memset(file_set->files, 0, LUO_FILE_PGCNT << PAGE_SHIFT); > + list_for_each_entry(block, &file_set->blocks, list) { > + block->ser->count = 0; > + memset(block->ser + 1, 0, > + (LUO_FILE_PGCNT << PAGE_SHIFT) - sizeof(*block->ser)); > + } > } > > /** > @@ -494,7 +531,8 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set, > int luo_file_freeze(struct luo_file_set *file_set, > struct luo_file_set_ser *file_set_ser) > { > - struct luo_file_ser *file_ser = file_set->files; > + struct luo_file_block *block; > + struct luo_file_ser *file_ser; > struct luo_file *luo_file; > int err; > int i; > @@ -502,11 +540,18 @@ int luo_file_freeze(struct luo_file_set *file_set, > if (!file_set->count) > return 0; > > - if (WARN_ON(!file_ser)) > - return -EINVAL; > + block = list_first_entry(&file_set->blocks, struct luo_file_block, list); > + file_ser = (void *)(block->ser + 1); > > i = 0; > list_for_each_entry(luo_file, &file_set->files_list, list) { > + if (i == LUO_FILE_BLOCK_MAX) { > + block->ser->count = i; > + block = list_next_entry(block, list); > + file_ser = (void *)(block->ser + 1); > + i = 0; > + } > + > err = luo_file_freeze_one(file_set, luo_file); > if (err < 0) { > pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n", > @@ -521,10 +566,13 @@ int luo_file_freeze(struct luo_file_set *file_set, > file_ser[i].token = luo_file->token; > i++; > } > + block->ser->count = i; > > file_set_ser->count = file_set->count; > - if (file_set->files) > - file_set_ser->files = virt_to_phys(file_set->files); > + if (!list_empty(&file_set->blocks)) { > + block = list_first_entry(&file_set->blocks, struct luo_file_block, list); > + file_set_ser->files = virt_to_phys(block->ser); > + } > > return 0; > > @@ -746,10 +794,7 @@ int luo_file_finish(struct luo_file_set *file_set) > kfree(luo_file); > } > > - if (file_set->files) { > - kho_restore_free(file_set->files); > - file_set->files = NULL; > - } > + luo_file_destroy_ser_blocks(file_set, false); > > return 0; > } > @@ -782,8 +827,10 @@ int luo_file_finish(struct luo_file_set *file_set) > int luo_file_deserialize(struct luo_file_set *file_set, > struct luo_file_set_ser *file_set_ser) > { > - struct luo_file_ser *file_ser; > - u64 i; > + struct luo_file_header_ser *header_ser; > + struct luo_file_block *block; > + u64 header_ser_pa; > + int err; > > if (!file_set_ser->files) { > WARN_ON(file_set_ser->count); > @@ -791,7 +838,15 @@ int luo_file_deserialize(struct luo_file_set *file_set, > } > > file_set->count = file_set_ser->count; > - file_set->files = phys_to_virt(file_set_ser->files); > + header_ser_pa = file_set_ser->files; > + > + while (header_ser_pa) { > + header_ser = phys_to_virt(header_ser_pa); > + err = luo_file_add_block(file_set, header_ser); > + if (err) > + return err; > + header_ser_pa = header_ser->next; > + } > > /* > * Note on error handling: > @@ -808,42 +863,51 @@ int luo_file_deserialize(struct luo_file_set *file_set, > * userspace to detect the failure and trigger a reboot, which will > * reliably reset devices and reclaim memory. > */ > - file_ser = file_set->files; > - for (i = 0; i < file_set->count; i++) { > - struct liveupdate_file_handler *fh; > - bool handler_found = false; > - struct luo_file *luo_file; > - > - down_read(&luo_register_rwlock); > - list_private_for_each_entry(fh, &luo_file_handler_list, list) { > - if (!strcmp(fh->compatible, file_ser[i].compatible)) { > - if (try_module_get(fh->ops->owner)) > - handler_found = true; > - break; > - } > - } > - up_read(&luo_register_rwlock); > + list_for_each_entry(block, &file_set->blocks, list) { > + struct luo_file_ser *file_ser = (void *)(block->ser + 1); > > - if (!handler_found) { > - pr_warn("No registered handler for compatible '%.*s'\n", > - (int)sizeof(file_ser[i].compatible), > - file_ser[i].compatible); > - return -ENOENT; > + if (block->ser->count > LUO_FILE_BLOCK_MAX) { > + pr_warn("File block contains too many entries: %llu\n", > + block->ser->count); > + return -EINVAL; > } > > - luo_file = kzalloc_obj(*luo_file); > - if (!luo_file) { > - module_put(fh->ops->owner); > - return -ENOMEM; > - } > + for (int i = 0; i < block->ser->count; i++) { > + struct liveupdate_file_handler *fh; > + bool handler_found = false; > + struct luo_file *luo_file; > + > + down_read(&luo_register_rwlock); > + list_private_for_each_entry(fh, &luo_file_handler_list, list) { > + if (!strcmp(fh->compatible, file_ser[i].compatible)) { > + if (try_module_get(fh->ops->owner)) > + handler_found = true; > + break; > + } > + } > + up_read(&luo_register_rwlock); > > - /* safe to use fh because its module is pinned */ > - luo_file->fh = fh; > - luo_file->file = NULL; > - luo_file->serialized_data = file_ser[i].data; > - luo_file->token = file_ser[i].token; > - mutex_init(&luo_file->mutex); > - list_add_tail(&luo_file->list, &file_set->files_list); > + if (!handler_found) { > + pr_warn("No registered handler for compatible '%.*s'\n", > + (int)sizeof(file_ser[i].compatible), > + file_ser[i].compatible); > + return -ENOENT; > + } > + > + luo_file = kzalloc_obj(*luo_file); > + if (!luo_file) { > + module_put(fh->ops->owner); > + return -ENOMEM; > + } > + > + /* safe to use fh because its module is pinned */ > + luo_file->fh = fh; > + luo_file->file = NULL; > + luo_file->serialized_data = file_ser[i].data; > + luo_file->token = file_ser[i].token; > + mutex_init(&luo_file->mutex); > + list_add_tail(&luo_file->list, &file_set->files_list); > + } Nit: this diff is very hard to read. Perhaps splitting this into helper functions would help? > } > > return 0; > @@ -852,12 +916,14 @@ int luo_file_deserialize(struct luo_file_set *file_set, > void luo_file_set_init(struct luo_file_set *file_set) > { > INIT_LIST_HEAD(&file_set->files_list); > + INIT_LIST_HEAD(&file_set->blocks); > } > > void luo_file_set_destroy(struct luo_file_set *file_set) > { > WARN_ON(file_set->count); > WARN_ON(!list_empty(&file_set->files_list)); > + WARN_ON(!list_empty(&file_set->blocks)); > } > > /** > diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h > index a73f42069301..a92aaa9c073d 100644 > --- a/kernel/liveupdate/luo_internal.h > +++ b/kernel/liveupdate/luo_internal.h > @@ -50,19 +50,30 @@ static inline int luo_ucmd_respond(struct luo_ucmd *ucmd, > */ > #define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__) > > +/** > + * struct luo_file_block - Internal representation of a file serialization block. > + * @list: List head for linking blocks in memory. > + * @ser: Pointer to the serialized header in preserved memory. > + */ > +struct luo_file_block { > + struct list_head list; > + struct luo_file_header_ser *ser; > +}; > + > /** > * struct luo_file_set - A set of files that belong to the same sessions. > * @files_list: An ordered list of files associated with this session, it is > * ordered by preservation time. > - * @files: The physically contiguous memory block that holds the serialized > - * state of files. > + * @blocks: The list of serialization blocks (struct luo_file_block). > * @count: A counter tracking the number of files currently stored in the > * @files_list for this session. > + * @nblocks: The number of allocated serialization blocks. > */ > struct luo_file_set { > struct list_head files_list; > - struct luo_file_ser *files; > + struct list_head blocks; > long count; > + long nblocks; > }; > > /** -- Regards, Pratyush Yadav ^ permalink raw reply [flat|nested] 6+ messages in thread
[parent not found: <20260414200237.444170-4-pasha.tatashin@soleen.com>]
* Re: [PATCH 3/5] selftests/liveupdate: Test session and file limit removal [not found] ` <20260414200237.444170-4-pasha.tatashin@soleen.com> @ 2026-05-12 14:04 ` Pratyush Yadav 0 siblings, 0 replies; 6+ messages in thread From: Pratyush Yadav @ 2026-05-12 14:04 UTC (permalink / raw) To: Pasha Tatashin Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, linux-kernel, dmatlack, kexec, pratyush, skhawaja, graf On Tue, Apr 14 2026, Pasha Tatashin wrote: > With the removal of static limits on the number of sessions and files per > session, the orchestrator now uses dynamic allocation. > > Add new test cases to verify that the system can handle a large number of > sessions and files. These tests ensure that the dynamic block allocation > and reuse logic for session metadata and outgoing files work correctly > beyond the previous static limits. > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> > --- > .../testing/selftests/liveupdate/liveupdate.c | 99 +++++++++++++++++++ > 1 file changed, 99 insertions(+) > > diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c > index 37c808fbe1e9..0eaf97b19267 100644 > --- a/tools/testing/selftests/liveupdate/liveupdate.c > +++ b/tools/testing/selftests/liveupdate/liveupdate.c > @@ -22,6 +22,7 @@ > #include <fcntl.h> > #include <string.h> > #include <sys/ioctl.h> > +#include <sys/resource.h> > #include <unistd.h> > > #include <linux/liveupdate.h> > @@ -386,4 +387,102 @@ TEST_F(liveupdate_device, prevent_double_preservation) > ASSERT_EQ(close(session_fd2), 0); > } > > +static void ensure_nofile_limit(struct __test_metadata *_metadata, > + long min_limit) > +{ > + struct rlimit hl; > + > + if (getrlimit(RLIMIT_NOFILE, &hl) < 0) > + ksft_exit_fail_msg("getrlimit failed: %s\n", strerror(errno)); > + > + if (hl.rlim_cur >= min_limit) > + return; > + > + hl.rlim_cur = min_limit; > + if (hl.rlim_cur > hl.rlim_max) > + hl.rlim_max = hl.rlim_cur; > + > + if (setrlimit(RLIMIT_NOFILE, &hl) < 0) { > + if (errno == EPERM) { > + SKIP(return, "Insufficient privileges to set RLIMIT_NOFILE to %ld", > + hl.rlim_cur); > + } > + ksft_exit_fail_msg("setrlimit to %ld failed: %s\n", > + hl.rlim_cur, strerror(errno)); > + } > +} > + > +/* > + * Test Case: Manage Many Sessions > + * > + * Verifies that a large number of sessions can be created and then > + * destroyed during normal system operation. This specifically tests the > + * dynamic block allocation and reuse logic for session metadata management > + * without preserving any files. > + */ > +TEST_F(liveupdate_device, preserve_many_sessions) > +{ > +#define MANY_SESSIONS 2000 > + int session_fds[MANY_SESSIONS]; > + int i; > + > + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); > + if (self->fd1 < 0 && errno == ENOENT) > + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); > + ASSERT_GE(self->fd1, 0); > + > + ensure_nofile_limit(_metadata, MANY_SESSIONS + 10); > + if (_metadata->exit_code == KSFT_SKIP) > + return; Nit: This is strange. Why not just return errno in ensure_nofile_limit() and handle the skipping here? Same below. LGTM otherwise. > + > + for (i = 0; i < MANY_SESSIONS; i++) { > + char name[64]; > + > + snprintf(name, sizeof(name), "many-session-%d", i); > + session_fds[i] = create_session(self->fd1, name); > + ASSERT_GE(session_fds[i], 0); > + } > + > + for (i = 0; i < MANY_SESSIONS; i++) > + ASSERT_EQ(close(session_fds[i]), 0); > +} > + > +/* > + * Test Case: Preserve Many Files > + * > + * Verifies that a large number of files can be preserved in a single session > + * and then destroyed during normal system operation. This tests the dynamic > + * block allocation and management for outgoing files. > + */ > +TEST_F(liveupdate_device, preserve_many_files) > +{ > +#define MANY_FILES 500 > + int mem_fds[MANY_FILES]; > + int session_fd; > + int i; > + > + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); > + if (self->fd1 < 0 && errno == ENOENT) > + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); > + ASSERT_GE(self->fd1, 0); > + > + session_fd = create_session(self->fd1, "many-files-test"); > + ASSERT_GE(session_fd, 0); > + > + ensure_nofile_limit(_metadata, MANY_FILES + 10); > + if (_metadata->exit_code == KSFT_SKIP) > + return; > + > + for (i = 0; i < MANY_FILES; i++) { > + mem_fds[i] = memfd_create("test-memfd", 0); > + ASSERT_GE(mem_fds[i], 0); > + ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0); > + } > + > + for (i = 0; i < MANY_FILES; i++) > + ASSERT_EQ(close(mem_fds[i]), 0); > + > + ASSERT_EQ(close(session_fd), 0); > +} > + > TEST_HARNESS_MAIN -- Regards, Pratyush Yadav ^ permalink raw reply [flat|nested] 6+ messages in thread
[parent not found: <20260414200237.444170-5-pasha.tatashin@soleen.com>]
* Re: [PATCH 4/5] selftests/liveupdate: Add stress-sessions kexec test [not found] ` <20260414200237.444170-5-pasha.tatashin@soleen.com> @ 2026-05-12 14:09 ` Pratyush Yadav 0 siblings, 0 replies; 6+ messages in thread From: Pratyush Yadav @ 2026-05-12 14:09 UTC (permalink / raw) To: Pasha Tatashin Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, linux-kernel, dmatlack, kexec, pratyush, skhawaja, graf On Tue, Apr 14 2026, Pasha Tatashin wrote: > Add a new test that creates 2000 LUO sessions before a kexec > reboot and verifies their presence after the reboot. This ensures > that the linked-block serialization mechanism works correctly for > a large number of sessions. > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> > --- > diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c > index 3c8721c505df..37c330b9bb36 100644 > --- a/tools/testing/selftests/liveupdate/luo_test_utils.c > +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c > @@ -20,6 +20,7 @@ > #include <sys/stat.h> > #include <errno.h> > #include <stdarg.h> > +#include <sys/resource.h> > > #include "luo_test_utils.h" > > @@ -28,6 +29,30 @@ int luo_open_device(void) > return open(LUO_DEVICE, O_RDWR); > } > > +void luo_ensure_nofile_limit(long min_limit) Since you are adding this to luo_test_utils anyway, I think you should do it in patch 3 and use it there too. LGTM otherwise. > +{ > + struct rlimit hl; > + > + if (getrlimit(RLIMIT_NOFILE, &hl) < 0) > + ksft_exit_fail_msg("getrlimit failed: %s\n", strerror(errno)); > + > + if (hl.rlim_cur >= min_limit) > + return; > + > + hl.rlim_cur = min_limit; > + if (hl.rlim_cur > hl.rlim_max) > + hl.rlim_max = hl.rlim_cur; > + > + if (setrlimit(RLIMIT_NOFILE, &hl) < 0) { > + if (errno == EPERM) { > + ksft_exit_skip("Insufficient privileges to set RLIMIT_NOFILE to %ld\n", > + hl.rlim_cur); > + } > + ksft_exit_fail_msg("setrlimit to %ld failed: %s\n", > + hl.rlim_cur, strerror(errno)); > + } > +} > + > int luo_create_session(int luo_fd, const char *name) > { > struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) }; -- Regards, Pratyush Yadav ^ permalink raw reply [flat|nested] 6+ messages in thread
[parent not found: <20260414200237.444170-6-pasha.tatashin@soleen.com>]
* Re: [PATCH 5/5] selftests/liveupdate: Add stress-files kexec test [not found] ` <20260414200237.444170-6-pasha.tatashin@soleen.com> @ 2026-05-12 14:10 ` Pratyush Yadav 0 siblings, 0 replies; 6+ messages in thread From: Pratyush Yadav @ 2026-05-12 14:10 UTC (permalink / raw) To: Pasha Tatashin Cc: linux-kselftest, rppt, shuah, akpm, linux-mm, linux-kernel, dmatlack, kexec, pratyush, skhawaja, graf On Tue, Apr 14 2026, Pasha Tatashin wrote: > Add a new luo_stress_files kexec test that verifies preserving and > retrieving 500 files across a kexec reboot. > > Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org> -- Regards, Pratyush Yadav ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-05-12 15:55 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260414200237.444170-1-pasha.tatashin@soleen.com>
[not found] ` <20260414200237.444170-2-pasha.tatashin@soleen.com>
2026-05-12 13:35 ` [PATCH 1/5] liveupdate: Remove limit on the number of sessions Pratyush Yadav
2026-05-12 15:55 ` Pasha Tatashin
[not found] ` <20260414200237.444170-3-pasha.tatashin@soleen.com>
2026-05-12 13:58 ` [PATCH 2/5] liveupdate: Remove limit on the number of files per session Pratyush Yadav
[not found] ` <20260414200237.444170-4-pasha.tatashin@soleen.com>
2026-05-12 14:04 ` [PATCH 3/5] selftests/liveupdate: Test session and file limit removal Pratyush Yadav
[not found] ` <20260414200237.444170-5-pasha.tatashin@soleen.com>
2026-05-12 14:09 ` [PATCH 4/5] selftests/liveupdate: Add stress-sessions kexec test Pratyush Yadav
[not found] ` <20260414200237.444170-6-pasha.tatashin@soleen.com>
2026-05-12 14:10 ` [PATCH 5/5] selftests/liveupdate: Add stress-files " Pratyush Yadav
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox