Repository navigation
Conversation
|
69e3fe6 to
52e93c8
Compare
|
@xiaoxiang781216 i think this is breaking change. Should I mark it and bring a vote to the list? |
the old behavior doesn't compliant to POSIX, this patch fix the problem. So, I think it's a bug fix.:) |
52e93c8 to
a04d4c0
Compare
|
ok, i agree |
❌ Cross-repo dependency could not be appliedThe Build report says the declared dependency PR(s) could not be applied, so CI did not run against the combined code: Reason: cherry-pick failed (if your PR has merge commits, rebase instead) CI run: https://github.com/apache/nuttx/actions/runs/38088049757 |
| its last handle in the task group is closed. They do not run per | ||
| ``dlopen()``/``dlclose()`` pair. | ||
| * The libraries that a program loaded by ``exec()`` names in ``DT_NEEDED`` | ||
| belong to its task group in the FLAT build, so ``dlopen()`` of one of them |
There was a problem hiding this comment.
does DT_NEEDED work in protected/kernel build now?
There was a problem hiding this comment.
In the PROTECTED build, yes: the kernel loads the libraries of a program and keeps them on a list in its struct binary_s.
In the KERNEL build, no: with CONFIG_ARCH_ADDRENV, libelf_bind() fails a module that has DT_NEEDED (ENOSYS), because a library that libelf_insert() loads cannot reach the address space of the program.
| @@ -175,6 +175,10 @@ struct fdpic_desc_s; | |||
| struct module_s | |||
| { | |||
| FAR struct module_s *flink; /* Supports a singly linked list */ | |||
| FAR struct module_s **registry; /* The registry this module is on */ | |||
| FAR struct module_s *libraries; /* exec(): its own registry, for | |||
There was a problem hiding this comment.
why need add libraries/registry field? we just need a link list and hold the head in tls
There was a problem hiding this comment.
Done: struct module_s has only a list node now, and the head is ta_modules in the task info, with its lock ta_modlock.
One field stays in mod_loadinfo_s: the registry that libelf_bind() puts the DT_NEEDED libraries on. A program that exec() loads has no task group yet, so its libraries go first on a list in struct binary_s, and exec_module() moves them to the new task group.
| * Name: libelf_registry_kernel | ||
| ****************************************************************************/ | ||
|
|
||
| FAR struct module_s **libelf_registry_kernel(void) |
| * Name: libelf_registry | ||
| ****************************************************************************/ | ||
|
|
||
| FAR struct module_s **libelf_registry(void) |
| @@ -1040,7 +984,8 @@ static int libelf_relocatedyn(FAR struct module_s *modp, | |||
| return ret; | |||
| } | |||
|
|
|||
| handle = libelf_insertneeded((FAR const char *)loadinfo->iobuffer); | |||
| handle = libelf_insertneeded(modp->registry, | |||
There was a problem hiding this comment.
why not insert to ta_modules
| { | ||
| FAR struct module_s *modp; | ||
|
|
||
| libelf_registry_lock(); |
There was a problem hiding this comment.
should use per task lock from tls
| * | ||
| ****************************************************************************/ | ||
|
|
||
| void libelf_closeall(FAR struct module_s **registry) |
libelf_remove() set the reference count of a module to zero before libelf_uninit() refused to unload it because other modules depend on it. The failed dlclose() thus gave up the last reference anyway. When the last dependent went, libelf_undepend() unloaded the module, and the next dlclose() of the still open handle failed with ENOENT. examples/sotest shows it: "ERROR: dlclose(handle1) failed: -1". Refuse with EBUSY before the reference is given up, and keep the reference if the uninitializer of the module fails. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
One global registry held every ELF object that dlopen(), insmod() and the DT_NEEDED loader loaded. Thus a library was one instance for the whole system, and all task groups shared its data. A registry is now a list head, and a module is on it through a list node, so a module can leave its list without knowing the head. The libelf functions get the registry from the caller and do not lock it: the caller holds the lock of that registry. - dlopen(), dlclose() and dlsym() use the registry of the calling task group, ta_modules in struct task_info_s. POSIX makes a dlopen() handle valid in the whole process, so the registry is per task group and not per thread. Its lock, ta_modlock, is recursive because the constructors of a library run with it held and can call dlopen(). - insmod(), rmmod(), modsym(), modhandle() and /proc/modules use the registry of the kernel modules, which is now in sched/module. - The global symbol table has a lock of its own. A DT_NEEDED library goes on the registry of the module that needs it, which libelf_bind() gets in struct mod_loadinfo_s. A program that exec() loads has no task group yet, so its libraries go on a list in struct binary_s. In a flat build, exec_module() moves them to the registry of the new task group, so dlopen() in the program finds the same copy. In the protected build the program is loaded by the kernel libc, which has another heap, so its libraries stay on the list of the program. If the bind of a program fails, its libraries are now released. libelf_removeall() closes the libraries that a task group did not close. In a flat build, group_release() does it after the last thread, and then gives what the program still uses back to the program, before the registry goes with the task information. In the protected build the kernel cannot reach the user registry, so exit() does it. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Describe the per task group registry: each task group loads its own copy of a library, constructors and destructors run once per task group, and a task group closes what it left open when it exits. Kernel modules stay on one list for the system. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
a04d4c0 to
1516192
Compare
Summary
Each task group now has its own registry of the ELF objects that
dlopen()loads, as suggested in #20132. The registry is instruct task_info_sand not instruct tls_info_s, because POSIX makes adlopen()handle valid in the whole process, not only in the thread that opened it.Kernel modules from
insmod()stay on one registry for the system, and/proc/modulesshows only them.A library that a module names in
DT_NEEDEDgoes on the registry of that module. In a flat build,exec()gives the libraries of the program to the new task group, sodlopen()in the program finds the same copy.When a task group exits, the libraries that it did not close are closed. A flat build does this in
group_release(), after the last thread. A protected build does it inexit(), because the kernel cannot reach the user registry.The first commit fixes
dlclose()of a library that other modules still depend on: it gave up the last reference although it failed, so a laterdlclose()of the open handle failed withENOENT. It now fails withEBUSYand keeps the reference.Impact
Two processes that use one library now each have their own copy of its data, and its constructors and destructors run once per process. An FDPIC library executed in place from xipfs still shares its text.
A library that is not executed in place is now loaded once per process, so its text uses RAM in each process.
A program loaded by
exec()no longer resolves symbols from libraries that other processes loaded, or frominsmod()modules.dlopen()handles that a process does not close are now freed when it exits. Before, they stayed loaded.The xipfs C++ test in apps checks the old shared behaviour. apache/nuttx-apps#3813 changes it.
Testing
The table below is from the first version of this PR. The reworked version (one list per registry, recursive registry lock,
libelf_removeall()) was built forqemu-armv8a:knshon the current master, and a 4-workerexec()stress test onqemu-armv8a:knshwith 4 CPUs completes 100 rounds with no memory growth (page memory back to 16 MB after the run).Arm GNU Toolchain 13.2.rel1, QEMU
mps2-an500.xipfs_test fdpic(FDPIC,mps2-an500:xipfs)fdpicxip solibfdpicxip cxxsotest(plain ELF)sotestthat exits with its libraries openmps2-an500:knsh(protected) with ELF and dlfcn:ostestqemu-armv7a:knsh(kernel) with dlfcnesp32s3-devkit:kernel_n8r2(kernel,CONFIG_LIBC_ELF), kernel and user programsesp32s3-devkit:kernel_oct(kernel,CONFIG_LIBC_ELF) on an ESP32-S3-WROOM-2-N32R8V:ostestandsandboxostestexits 0,fork()passes;sandboxreports every check passedtools/checkpatch.sh -c -u -m -gpasses.