Even after generating a .ko, loading may still fail; having a valid signature does not guarantee ABI compatibility. To explain whether a module truly fits, you must check along the lines of configuration, build information, loading checks, and resource cleanup.
Inspection Target
Reading Location
Feature selection and artifact composition
Kconfig, Kbuild
Reproducible builds and target matching
External module project, build information
Runtime and trust boundaries
Loading, exported symbols, signatures
Kconfig: Dependencies Are Not Textual Menu Decorations
bool represents y/n, while tristate also allows m, with prerequisites including module support configurations. depends on constrains the availability of options; select forcibly enables another symbol, potentially bypassing the latter's original dependency checks, so select should not be treated as a general dependency declaration. Kconfig
.config is the result of selections. It can be updated using olddefconfig etc. after changing source code or kernel versions, but changes in default values should be reviewed. defconfig is architecture-dependent and does not guarantee suitability for all hardware on the current machine.
Why consumers cannot remain 'y' when dependencies are 'm'
For standard tristate dependencies, you can understand the upper bound using n < m < y. If CONSUMER directly calls the functionality of PROVIDER and the dependency is written as depends on PROVIDER, when PROVIDER=m, CONSUMER can only reach up to m; built-in code cannot depend on modules that are yet to be loaded at runtime.
Save the following Kconfig in an independent experiment directory. It includes an intentionally incorrect select relationship to observe how forced selection breaks dependencies.
Select forces lower bound, no dependency satisfied for DRIVER
For example, save the following as seed.config, and use scripts/kconfig/conf from the built kernel source tree to evaluate it; the output directory and KCONFIG_CONFIG should remain in this experiment directory to avoid modifying the kernel configuration currently in use.
The fourth line is not a recommended configuration, but demonstrates why incorrect relationships are dangerous. When fixing, let the configuration express real dependencies, or model dependency-free internal helpers suitable for selection by multiple callers separately. Replacing select with imply cannot replace the design of built-in/module linking relationships. Kconfig Dependencies and Three-Valued Expressions
Directly change dependencies and the requested tristate values. Below, treat n, m, y as ordered upper bounds, showing the build result after taking the smaller value between requested and dependency upper bounds; it does not treat dependencies as automatically selected menus.
Preparing the visual
Requested y, why is the final result only m or n?
depends on establishes an upper bound for symbols. When DRIVER depends on BUS && NET, it cannot reference dependencies provided only as modules (m) as built-in (y). select has different semantics and cannot replace standard dependencies.
{"id":"kconfig-tristate-bounds","title":"Requested y, why is the final result only m or n?","summary":"depends on establishes an upper bound for symbols. When DRIVER depends on BUS && NET, it cannot reference dependencies provided only as modules (m) as built-in (y). select has different semantics and cannot replace standard dependencies.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"modules\" checked><span data-i18n=\"modules\"></span></label><label for=\"bus\" class=\"caption\" data-i18n=\"bus\"></label><select id=\"bus\"><option value=\"0\">n</option><option value=\"1\" selected>m</option><option value=\"2\">y</option></select><label for=\"net\" class=\"caption\" data-i18n=\"net\"></label><select id=\"net\"><option value=\"0\">n</option><option value=\"1\">m</option><option value=\"2\" selected>y</option></select><label for=\"requested\" class=\"caption\" data-i18n=\"requested\"></label><select id=\"requested\"><option value=\"0\">n</option><option value=\"1\">m</option><option value=\"2\" selected>y</option></select><div class=\"tristates\"><div><span data-i18n=\"bus\"></span><div class=\"level\"><i id=\"bar-bus\"></i></div><strong id=\"bus-value\"></strong></div><div><span data-i18n=\"net\"></span><div class=\"level\"><i id=\"bar-net\"></i></div><strong id=\"net-value\"></strong></div><div><span data-i18n=\"requested\"></span><div class=\"level\"><i id=\"bar-requested\"></i></div><strong id=\"requested-value\"></strong></div><div><span data-i18n=\"bound\"></span><div class=\"level\"><i id=\"bar-bound\"></i></div><strong id=\"bound-value\"></strong></div><div><span data-i18n=\"effective\"></span><div class=\"level\"><i id=\"bar-effective\"></i></div><strong id=\"effective-value\"></strong></div></div><div class=\"formula\" id=\"formula\"></div><div class=\"card\"><header data-i18n=\"artifact\"></header><div id=\"artifact\" class=\"readout\"></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n.caption{display:block;margin:12px 0 5px}.tristates{margin:20px 0}.tristates>div{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr) 20px;gap:8px;align-items:center;min-height:4.5em;font-size:12px}.level{height:18px;background:var(--surface);border-inline:1px solid var(--rule)}.level i{display:block;height:100%;background:var(--accent);transition:width .2s}#bar-bound{background:var(--second)}.readout{min-height:3.2em}.formula{min-height:4.8em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}function draw(){const b=+$('#bus').value,n=+$('#net').value,r=+$('#requested').value,bound=Math.min(b,n),effective=Math.min(r,bound);for(const[id,v]of [['bus',b],['net',n],['requested',r],['bound',bound],['effective',effective]]){set(id+'-value',['n','m','y'][v]);$('#bar-'+id).style.width=v*50+'%';}set('formula','min('+[r,b,n].map(v=>['n','m','y'][v]).join(', ')+') = '+['n','m','y'][effective]);set('artifact',t(['none','module','builtin'][effective]));set('explanation',t('note'));}$('#modules').onchange=()=>{for(const id of ['bus','net','requested']){const e=$('#'+id);e.querySelector('[value=\"1\"]').disabled=!$('#modules').checked;if(!$('#modules').checked&&e.value==='1')e.value='0';}draw();};for(const id of ['bus','net','requested'])$('#'+id).onchange=draw;draw();","audio":false,"strings":{"artifact":"Corresponding build artifact","bound":"Dependency upper bound BUS && NET","builtin":"obj-y → Built-in link","bus":"BUS","effective":"Actual value for DRIVER","heading":"Requested y, why is the final result only m or n?","intro":"Select expected values for BUS, NET, and DRIVER respectively. The dependency expression uses three-valued logic n<m<y; observe the upper bound of DRIVER and the build artifact.","module":"obj-m → driver.ko","modules":"Enable MODULES","net":"NET","none":"Does not enter object set","note":"Set BUS to m, NET to y, expect DRIVER=y. The dependency upper bound is only m, so DRIVER will not become built-in according to this request.","requested":"Expected value for DRIVER","reset":"Reset","scope":"Three visible tristate symbols with no default/select/imply. Only calculate effective=min(requested,BUS,NET), do not execute full Kconfig. When MODULES is disabled, this interface explicitly resets m selection to n and disables m; this UI selection is not a universal rule for all real configuration migrations.","scopeLabel":"Model Scope and Conventions"}}
When configured as y, add the built-in object path; when m, build the module; when disabled, do not add to the corresponding set. mydrv-y specifies the files composing this composite object; do not treat mydrv.o as two different targets unrelated to the same-named mydrv.c.
Configuring as y does not automatically guarantee final linking, and configuring as m does not guarantee the dependency can call it in built-in mode. Kconfig relationships and link dependencies must be designed together.
A Complete External Module Project
The directory contains the following three files. This example only records loading and unloading, not device takeover.
hello.c:
#include<linux/init.h>#include<linux/module.h>#include<linux/printk.h>staticint greeting_id =42;module_param(greeting_id,int,0444);MODULE_PARM_DESC(greeting_id,"Identifier printed on initialization");staticint __init hello_init(void){pr_info("kb_hello: id=%d\n", greeting_id);return0;}staticvoid __exit hello_exit(void){pr_info("kb_hello: removed\n");}module_init(hello_init);module_exit(hello_exit);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Knowledge base build example");
Run make in this directory to get kb_hello.ko, or specify the target build directory via make KDIR=/path/to/prepared/kernel. Use $(MAKE) to preserve recursive build parameters; Kbuild lists objects separately to avoid confusion between standard wrapper targets and kernel rules. External Modules
Headers Are Not Enough: Must Match Target Build Information
External modules require the target kernel's configuration, generated headers, and necessary build artifacts. modules_prepare does not generate the complete Module.symvers required when MODVERSIONS is enabled; the corresponding build must provide it. Do not assume that preparing headers includes all symbol version information.
modpost handles exported symbols and module metadata. Unresolved symbols, namespace, and version issues should be investigated at this stage and the loading stage respectively. Mainline Linux does not promise a stable internal module ABI across kernel versions. Stable API Explanation
Two External Modules: Known Symbols Do Not Mean Linked Into Consumer
The following second project only demonstrates dependencies between modules; place it in a different directory from the hello project. provider exports a function, which consumer calls during initialization. Build both modules uniformly first, so modpost can see their export information simultaneously.
#include<linux/module.h>#include"kb_api.h"unsignedintkb_step(unsignedintvalue){return value +1U;}EXPORT_SYMBOL_GPL(kb_step);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Knowledge base symbol provider");
kb_consumer.c:
#include<linux/init.h>#include<linux/module.h>#include<linux/errno.h>#include"kb_api.h"staticint __init kb_consumer_init(void){returnkb_step(41U)==42U?0:-EINVAL;}staticvoid __exit kb_consumer_exit(void){}module_init(kb_consumer_init);module_exit(kb_consumer_exit);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Knowledge base symbol consumer");
Kbuild:
obj-m:=kb_provider.o kb_consumer.o
You can reuse the wrapper rules from the previous Makefile, specifying the prepared KDIR to build. After completion, you can check without loading the modules:
depends should include kb_provider, and you can still see kb_step in the list of undefined symbols. This is not contradictory: consumer's .ko does not statically copy provider's implementation; symbols are resolved when the kernel loads them; modpost knows which module they come from and generates dependency metadata.
If you split the two modules into independent build directories and provide only the declaration header to consumer, C compilation may still succeed, but modpost will refuse to proceed due to missing export records for kb_step. Build provider first, then pass KBUILD_EXTRA_SYMBOLS=/absolute/path/to/provider/Module.symvers to the consumer build to supplement the symbol information required at the build stage; both projects must be built against compatible target kernels.
This parameter does not install or load provider, nor does it make inconsistent headers, outdated symvers, or ABI mismatches into correct dependencies. At runtime, provider must still exist and be resolved first; only when the module index is installed correctly can modprobe find and load dependencies according to them. If you only use insmod to specify the consumer file, it will not automatically find provider just because symvers were provided at build time. Symbols and Module.symvers for External Modules
Loading Tools and Lifecycle
Tool
Dependency Handling
insmod
Loads the specified file, does not automatically find all dependencies
modprobe
Handles dependencies and aliases based on module index and configuration
modprobe -r
Can attempt to remove unused dependencies, does not guarantee all are unloaded
Distinguish between "loading this file" and "loading according to installed module relationships" before choosing a tool.
Module loading involves relocation, symbol resolution, checks, and init calls; if init fails, resources already acquired must be cleaned up. The exit path must stop new requests, cancel work, wait for references and callbacks to finish, and cannot simply write a pr_info as complete unloading logic for a real driver.
You can first use modinfo ./kb_hello.ko to check names, parameters, and vermagic. Installation and loading affect the system, so it is usually best to do this in a test VM matching the kernel. The boot entries and initramfs behavior of make install depend on architecture and distribution hooks; these are not all steps guaranteed to be completed by Kbuild in one go.
Exported Symbols Are Not Permanently Stable Public APIs
EXPORT_SYMBOL exposes symbols to applicable modules, EXPORT_SYMBOL_GPL imposes restrictions on module license declarations; symbol namespaces may also require corresponding imports. Exporting means it can be resolved, but does not imply that calling it in any context is safe, nor does it promise it will remain unchanged in future versions.
Module Signing: Verification, Automatic Signing, and Mandatory Policies Are Separate
Configuration or Policy
Responsible For
CONFIG_MODULE_SIG
Provides signature verification mechanism
CONFIG_MODULE_SIG_ALL
Involves automatic signing during build and installation
CONFIG_MODULE_SIG_FORCE or mandatory policy
Determines if an acceptable signature is required
A SIG=y does not mean "automatically sign all modules and forcibly reject all unmatched modules". Signing Documentation
Runtime policies, trusted keys, and Secure Boot integration are affected by kernel and distribution configurations. Successful signature verification does not guarantee that the module matches the current kernel ABI or behaves correctly. Signatures are appended after the module content; modifying or stripping after signing may invalidate them; arrange signing at the correct product processing stage.