I'm currently learning how to create USB drivers for Linux, and I absolutely love it so far! However, I've come across an issue when trying to insert my .ko file. I keep receiving the following error: "Unknown symbol in module".
Below, you will find my code. What is weird is if I comment out the code in my __init function, the module loads just fine, however after going through my code, and comparing it to other USB driver code, I am unable to locate any issues. Could you please help me out? Thanks so much for your help
struct xb1_controller {
struct usb_device *udev;
struct usb_interface *interface;
unsigned char minor;
char *int_in_buffer;
struct usb_endpoint_descriptor *endpoint;
struct urb *int_in_urb;
};
static struct usb_device_id xb1_table [] = {
{USB_DEVICE(xb1_vendor_id, xb1_product_id) },
{}
};
MODULE_DEVICE_TABLE(usb, xb1_table);
static int xb1_open(struct inode *inode, struct file *file) {
//Sys call
}
static int xb1_release(struct inode *inode, struct file *file) {
//Sys call
}
static ssize_t xb1_read(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos) {
//Sys call
}
static struct file_operations xb1_fops = {
.owner = THIS_MODULE,
.open = xb1_open,
.release = xb1_release,
.read = xb1_read,
};
static struct usb_class_driver xb1_class = {
.name = "xb1driver",
.fops = &xb1_fops,
.minor_base = MINOR_BASE,
};
static int xb1_probe(struct usb_interface *interface, const struct
usb_device_id *id) {
printk(KERN_INFO "xb1driver probe function called");
}
static void xb1_disconnect(struct usb_interface *interface) {
printk(KERN_INFO "xb1driver disconnect function called");
}
static struct usb_driver xb1_driver = {
.name = "xb1driver",
.id_table = xb1_table,
.probe = xb1_probe,
.disconnect = xb1_disconnect,
};
static int __init xb1_init(void) {
int result;
result = usb_register(&xb1_driver);
if(result) {
printk(KERN_INFO "usb_register has failed..");
return 5;
}
return result;
}
static void __exit xb1_exit(void) {
usb_deregister(&xb1_driver);
}
module_init(xb1_init);
module_exit(xb1_exit);
Related
I have a working program that creates an EGL window in Wayland. I can control the window size, but not its position on the screen. Right now, if I set the size to match the size of the screen, it will run fullscreen but if I set anything less than that, it will just appear in a different random position every time.
I've read that the top surface can't be moved and that if I want to control the surface's position, I have to use a subsurface. However, I wasn't able to find any useful examples. Especially, I wasn't able to find any examples for EGL, which has some caviats.
Anyway, here's the important bit from my program:
static struct waylandContext {
struct wl_display* display;
struct wl_registry* registry;
struct wl_compositor* compositor;
struct wl_shell* shell;
struct wl_surface* surface;
struct wl_shell_surface* shell_surface;
struct wl_egl_window* egl_window;
} wlContext;
static void registry_add_object(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version)
{
if (!strcmp(interface, "wl_compositor")) {
wlContext.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
} else if (!strcmp(interface, "wl_shell")) {
wlContext.shell = wl_registry_bind(registry, name, &wl_shell_interface, 1);
}
}
static void registry_remove_object(void* data, struct wl_registry* registry, uint32_t name)
{
}
static struct wl_registry_listener registry_listener = {
®istry_add_object,
®istry_remove_object
};
static void shell_surface_ping(void* data, struct wl_shell_surface* shell_surface, uint32_t serial)
{
wl_shell_surface_pong(shell_surface, serial);
}
static void shell_surface_configure(void* data, struct wl_shell_surface* shell_surface, uint32_t edges, int32_t width, int32_t height)
{
struct window* window = data;
wl_egl_window_resize(wlContext.egl_window, width, height, 0, 0);
}
static void shell_surface_popup_done(void* data, struct wl_shell_surface* shell_surface)
{
}
static struct wl_shell_surface_listener shell_surface_listener = {
&shell_surface_ping,
&shell_surface_configure,
&shell_surface_popup_done
};
static bool initWayland(int width, int height)
{
wlContext.display = wl_display_connect(NULL);
if (!wlContext.display) {
fprintf(stderr, "failed to connect to display\n");
return false;
}
wlContext.registry = wl_display_get_registry(wlContext.display);
wl_registry_add_listener(wlContext.registry, ®istry_listener, NULL);
wl_display_dispatch(wlContext.display);
wl_display_roundtrip(wlContext.display);
wlContext.surface = wl_compositor_create_surface(wlContext.compositor);
wlContext.shell_surface = wl_shell_get_shell_surface(wlContext.shell, wlContext.surface);
wl_shell_surface_add_listener(wlContext.shell_surface, &shell_surface_listener, NULL);
wl_shell_surface_set_toplevel(wlContext.shell_surface);
wlContext.egl_window = wl_egl_window_create(wlContext.surface, width, height);
return true;
}
I tried changing the code to this:
static struct waylandContext {
struct wl_display* display;
struct wl_registry* registry;
struct wl_compositor* compositor;
struct wl_subcompositor* subcompositor;
struct wl_shell* shell;
struct wl_surface* parent_surface;
struct wl_surface* child_surface;
struct wl_subsurface* subsurface;
struct wl_shell_surface* shell_surface;
struct wl_egl_window* egl_window;
struct wl_egl_window* egl_window2;
} wlContext;
static void registry_add_object(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version)
{
if (!strcmp(interface, "wl_compositor")) {
wlContext.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
} else if (!strcmp(interface, "wl_subcompositor")) {
wlContext.subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
} else if (!strcmp(interface, "wl_shell")) {
wlContext.shell = wl_registry_bind(registry, name, &wl_shell_interface, 1);
}
}
static void registry_remove_object(void* data, struct wl_registry* registry, uint32_t name)
{
}
static struct wl_registry_listener registry_listener = {
®istry_add_object,
®istry_remove_object
};
static void shell_surface_ping(void* data, struct wl_shell_surface* shell_surface, uint32_t serial)
{
wl_shell_surface_pong(shell_surface, serial);
}
static void shell_surface_configure(void* data, struct wl_shell_surface* shell_surface, uint32_t edges, int32_t width, int32_t height)
{
struct window* window = data;
wl_egl_window_resize(wlContext.egl_window, width, height, 0, 0);
}
static void shell_surface_popup_done(void* data, struct wl_shell_surface* shell_surface)
{
}
static struct wl_shell_surface_listener shell_surface_listener = {
&shell_surface_ping,
&shell_surface_configure,
&shell_surface_popup_done
};
static bool initWayland(int width, int height)
{
wlContext.display = wl_display_connect(NULL);
if (!wlContext.display) {
fprintf(stderr, "failed to connect to display\n");
return false;
}
wlContext.registry = wl_display_get_registry(wlContext.display);
wl_registry_add_listener(wlContext.registry, ®istry_listener, NULL);
wl_display_dispatch(wlContext.display);
wl_display_roundtrip(wlContext.display);
wlContext.parent_surface = wl_compositor_create_surface(wlContext.compositor);
wlContext.shell_surface = wl_shell_get_shell_surface(wlContext.shell, wlContext.parent_surface);
wl_shell_surface_add_listener(wlContext.shell_surface, &shell_surface_listener, NULL);
wl_shell_surface_set_toplevel(wlContext.shell_surface);
wlContext.child_surface = wl_compositor_create_surface(wlContext.compositor);
wlContext.subsurface = wl_subcompositor_get_subsurface(wlContext.subcompositor, wlContext.child_surface, wlContext.parent_surface);
wlContext.egl_window = wl_egl_window_create(wlContext.child_surface, width, height);
return true;
}
Where I get subcompositor, create a child surface and create the EGL window from that child surface. However, this doesn't work and I'm not sure why.
Maybe I should add, how I work with EGL, but it's just standard stuff. After I create the EGL window, I have the regular sequence of calls like eglInitialize, eglCreateContext, etc. Then I create an OpenGL context, draw something and call eglSwapBuffers. When I do it with the subsurface, eglSwapBuffers runs once but nothing shows and then when I call eglSwapBuffers the second time, it freezes forever.
At rewriting kernel driver I got this warning:
msm-cirrus-playback.c:545:2: warning: braces around scalar initializer
Read that this warning appears when I am declaring one structure's field in {}:
struct random_struct test = {
{ .name = "StackOverflow" },
{ .name = "StackExchange" },
};
But my structure have 2-3 fields in {}:
static struct device_attribute *opalum_dev_attr = {
{
.attr->name = "temp-acc",
.show = opsl_temp_acc_show,
.store = opsl_temp_acc_store,
},
{
.attr->name = "count",
.show = opsl_count_show,
.store = opsl_count_store,
},
{
.attr->name = "ambient",
.show = opsl_ambient_show,
.store = opsl_ambient_store,
},
{
.attr->name = "f0",
.show = opsl_f0_show,
.store = opsl_f0_store,
},
{
.attr->name = "pass",
.show = opsl_pass_show,
},
{
.attr->name = "start",
.show = opsl_cali_start,
},
};
This structure:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
How can I fix this warning? Qualcomm kernels are building with -Werror flag, so this warning is critical.
static struct device_attribute *opalum_dev_attr means declare opalum_dev_attr as static pointer to struct device_attribute
Your code is trying to initialize a static array of struct device_attribute
What you want is: static struct device_attribute opalum_dev_attr[] which means declare opalum_dev_attr as static array of struct device_attribute
It is because you initialize the pointer to the struct not the struct itself.
you need to assign the reference to the struct for example by using compound literals
struct x
{
int a,b,c;
}a = {1,2,3};
void foo()
{
struct x a = {1,2,3};
struct x *b = {1,2,3}; // wrong warning here
struct x *x = &(struct x){1,2,3}; // correct reference to the struct assigned (using compound literal
struct x *y = (struct x[]){{1,2,3}, {4,5,6}, {4,5,6}, };
struct x z[] = {{1,2,3}, {4,5,6}, {4,5,6}, };
}
I have some problem with my code. In my code i solve the reader writers problem using RCU, when i load the module (insmod) everything works ok, and it works as i expected, but when i unload module (rmmod) my program freezes and I have to restart Ubuntu. In this program i have to use call_rcu() to delete previous version of the shared resource and i think that problem is here, because if i have not if I do not use this function and simply delete via kmalloc () then the program works as it should
#include<linux/module.h>
#include<linux/kthread.h>
#include<linux/wait.h>
#include<linux/slab.h>
#include<linux/rcupdate.h>
enum thread_index {WAKING_THREAD, WRITER_THREAD, FIRST_READER_THREAD, SECOND_READER_THREAD};
static struct thread_structure
{
struct task_struct *thread[4];
} threads;
struct st
{
int number;
struct rcu_head rcu;
};
static wait_queue_head_t wait_queue;
static bool condition;
static struct st *number_pointer = NULL;
static const int first_reader_number = 1, second_thread_number = 2;
static int reader_thread(void *data)
{
struct st *local_number_pointer = NULL;
for(;;) {
rcu_read_lock();
local_number_pointer = rcu_dereference(number_pointer);
if(local_number_pointer)
pr_info("[reader_number: %d] Value of \"number\" variable: %d\n",
*(int *)data,local_number_pointer->number);
rcu_read_unlock();
if(kthread_should_stop())
return 0;
set_current_state(TASK_INTERRUPTIBLE);
if(schedule_timeout(HZ>>2))
pr_info("Signal received!\n");
}
}
static void remove(struct rcu_head *x)
{
struct st *pntr = container_of(x, struct st, rcu);
kfree(pntr);
}
static int writer_thread(void *data)
{
struct st *local_number_pointer = NULL;
int number = 0;
DEFINE_WAIT(wait);
for(;;) {
struct st *old_pointer = NULL;
local_number_pointer = kmalloc(sizeof(*local_number_pointer),GFP_KERNEL);
if(IS_ERR(local_number_pointer)) {
pr_alert("Error allocating memory: %ld\n",PTR_ERR(local_number_pointer));
return 0;
}
local_number_pointer->number = number++;
old_pointer = number_pointer;
rcu_assign_pointer(number_pointer,local_number_pointer);
synchronize_rcu();
if(old_pointer)
call_rcu(&old_pointer->rcu, remove);
add_wait_queue(&wait_queue,&wait);
while(!condition) {
prepare_to_wait(&wait_queue,&wait,TASK_INTERRUPTIBLE);
if(kthread_should_stop())
return 0;
pr_info("[writer_thread]: awake\n");
schedule();
}
condition=false;
finish_wait(&wait_queue,&wait);
}
}
static int waking_thread(void *data)
{
for(;;) {
if(kthread_should_stop())
return 0;
set_current_state(TASK_INTERRUPTIBLE);
if(schedule_timeout(HZ))
pr_info("Signal received!\n");
condition=true;
wake_up(&wait_queue);
}
}
static int __init threads_init(void)
{
init_waitqueue_head(&wait_queue);
threads.thread[WRITER_THREAD] = kthread_run(writer_thread,NULL,"writer_thread");
threads.thread[WAKING_THREAD] = kthread_run(waking_thread,NULL,"waking_thread");
threads.thread[FIRST_READER_THREAD] =
kthread_run(reader_thread,(void *)&first_reader_number,"first_reader_thread");
threads.thread[SECOND_READER_THREAD] =
kthread_run(reader_thread,(void *)&second_thread_number,"second_reader_thread");
return 0;
}
static void __exit threads_exit(void)
{
kthread_stop(threads.thread[WAKING_THREAD]);
kthread_stop(threads.thread[WRITER_THREAD]);
kthread_stop(threads.thread[FIRST_READER_THREAD]);
kthread_stop(threads.thread[SECOND_READER_THREAD]);
}
module_init(threads_init);
module_exit(threads_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("An example of using the kernel linux threads and an RCU mechanism.");
MODULE_AUTHOR("Arkadiusz Chrobot <a.chrobot#tu.kielce.pl>");
I want to compile source code of a very simple file system--ramfs. But when I inserted the module into Linux kernel 3.10 I got a problem:
could not insert module myramfs.ko: Device or resource busy
I don't know which step I did wrong。The following is my code.
source code:ramfs
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/highmem.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/backing-dev.h>
#include <linux/ramfs.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define RAMFS_DEFAULT_MODE 0755
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/ramfs.h>
const struct address_space_operations ramfs_aops = {
.readpage = simple_readpage,
.write_begin = simple_write_begin,
.write_end = simple_write_end,
.set_page_dirty = __set_page_dirty_no_writeback,
};
const struct file_operations ramfs_file_operations = {
.read = do_sync_read,
.aio_read = generic_file_aio_read,
.write = do_sync_write,
.aio_write = generic_file_aio_write,
.mmap = generic_file_mmap,
.fsync = noop_fsync,
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
.llseek = generic_file_llseek,
};
const struct inode_operations ramfs_file_inode_operations = {
.setattr = simple_setattr,
.getattr = simple_getattr,
};
static const struct super_operations ramfs_ops;
static const struct inode_operations ramfs_dir_inode_operations;
static struct backing_dev_info ramfs_backing_dev_info = {
.name = "ramfs",
.ra_pages = 0, /* No readahead */
.capabilities = BDI_CAP_NO_ACCT_AND_WRITEBACK |
BDI_CAP_MAP_DIRECT | BDI_CAP_MAP_COPY |
BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP,
};
struct inode *ramfs_get_inode(struct super_block *sb,
const struct inode *dir, umode_t mode, dev_t dev)
{
struct inode * inode = new_inode(sb);
if (inode) {
inode->i_ino = get_next_ino();
inode_init_owner(inode, dir, mode);
inode->i_mapping->a_ops = &ramfs_aops;
inode->i_mapping->backing_dev_info = &ramfs_backing_dev_info;
mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER);
mapping_set_unevictable(inode->i_mapping);
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
switch (mode & S_IFMT) {
default:
init_special_inode(inode, mode, dev);
break;
case S_IFREG:
inode->i_op = &ramfs_file_inode_operations;
inode->i_fop = &ramfs_file_operations;
break;
case S_IFDIR:
inode->i_op = &ramfs_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
/* directory inodes start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
break;
case S_IFLNK:
inode->i_op = &page_symlink_inode_operations;
break;
}
}
return inode;
}
/*
* File creation. Allocate an inode, and we're done..
*/
/* SMP-safe */
static int
ramfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
{
struct inode * inode = ramfs_get_inode(dir->i_sb, dir, mode, dev);
int error = -ENOSPC;
if (inode) {
d_instantiate(dentry, inode);
dget(dentry); /* Extra count - pin the dentry in core */
error = 0;
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
}
return error;
}
static int ramfs_mkdir(struct inode * dir, struct dentry * dentry, umode_t mode)
{
int retval = ramfs_mknod(dir, dentry, mode | S_IFDIR, 0);
if (!retval)
inc_nlink(dir);
return retval;
}
static int ramfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl)
{
return ramfs_mknod(dir, dentry, mode | S_IFREG, 0);
}
static int ramfs_symlink(struct inode * dir, struct dentry *dentry, const char * symname)
{
struct inode *inode;
int error = -ENOSPC;
inode = ramfs_get_inode(dir->i_sb, dir, S_IFLNK|S_IRWXUGO, 0);
if (inode) {
int l = strlen(symname)+1;
error = page_symlink(inode, symname, l);
if (!error) {
d_instantiate(dentry, inode);
dget(dentry);
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
} else
iput(inode);
}
return error;
}
static const struct inode_operations ramfs_dir_inode_operations = {
.create = ramfs_create,
.lookup = simple_lookup,
.link = simple_link,
.unlink = simple_unlink,
.symlink = ramfs_symlink,
.mkdir = ramfs_mkdir,
.rmdir = simple_rmdir,
.mknod = ramfs_mknod,
.rename = simple_rename,
};
static const struct super_operations ramfs_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
.show_options = generic_show_options,
};
struct ramfs_mount_opts {
umode_t mode;
};
enum {
Opt_mode,
Opt_err
};
static const match_table_t tokens = {
{Opt_mode, "mode=%o"},
{Opt_err, NULL}
};
struct ramfs_fs_info {
struct ramfs_mount_opts mount_opts;
};
static int ramfs_parse_options(char *data, struct ramfs_mount_opts *opts)
{
substring_t args[MAX_OPT_ARGS];
int option;
int token;
char *p;
opts->mode = RAMFS_DEFAULT_MODE;
while ((p = strsep(&data, ",")) != NULL) {
if (!*p)
continue;
token = match_token(p, tokens, args);
switch (token) {
case Opt_mode:
if (match_octal(&args[0], &option))
return -EINVAL;
opts->mode = option & S_IALLUGO;
break;
/*
* We might like to report bad mount options here;
* but traditionally ramfs has ignored all mount options,
* and as it is used as a !CONFIG_SHMEM simple substitute
* for tmpfs, better continue to ignore other mount options.
*/
}
}
return 0;
}
int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct ramfs_fs_info *fsi;
struct inode *inode;
int err;
save_mount_options(sb, data);
fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL);
sb->s_fs_info = fsi;
if (!fsi)
return -ENOMEM;
err = ramfs_parse_options(data, &fsi->mount_opts);
if (err)
return err;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_CACHE_SIZE;
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
sb->s_magic = RAMFS_MAGIC;
sb->s_op = &ramfs_ops;
sb->s_time_gran = 1;
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
sb->s_root = d_make_root(inode);
if (!sb->s_root)
return -ENOMEM;
return 0;
}
struct dentry *ramfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_nodev(fs_type, flags, data, ramfs_fill_super);
}
static struct dentry *rootfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
return mount_nodev(fs_type, flags|MS_NOUSER, data, ramfs_fill_super);
}
static void ramfs_kill_sb(struct super_block *sb)
{
kfree(sb->s_fs_info);
kill_litter_super(sb);
}
static struct file_system_type ramfs_fs_type = {
.name = "ramfs",
.mount = ramfs_mount,
.kill_sb = ramfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
int __init init_ramfs_fs(void)
{
return register_filesystem(&ramfs_fs_type);
}
module_init(init_ramfs_fs)
int __init init_rootfs(void)
{
int err;
err = bdi_init(&ramfs_backing_dev_info);
if (err)
return err;
err = register_filesystem(&rootfs_fs_type);
if (err)
bdi_destroy(&ramfs_backing_dev_info);
return err;
}
Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := myramfs.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.order *.symvers *.unsigned
At version 4.10.0-38-generic there are no ctl_name field at ctl_table struct
I have found the tutorial https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&uact=8&ved=2ahUKEwie4Zz_5ZrdAhVKiaYKHRqsDiwQFjABegQICRAC&url=https%3A%2F%2Fsar.informatik.hu-berlin.de%2Fteaching%2F2012-s%2F2012-s%2520Operating%2520Systems%2520Principles%2Flab%2Flab-1%2Fsysctl_.pdf&usg=AOvVaw0mJdbT9E3lP2k3AQOGgzQz
But there are the usage of this field
Could you please give me an example of the usage of ctl_table at version 4.10.0-38-generic
I try to implement:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sysctl.h>
#define SUCCESS (0)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kasparyants George");
MODULE_DESCRIPTION("A simple Linux driver");
MODULE_VERSION("0.1");
static int global_var1 = 1;
static int global_var2 = 1;
static int min_val = 0;
static int max_val = 5;
static struct ctl_table_header* header;
static struct ctl_table child_ctl_table[] = {
{
.procname = "sample_value1",
.data = &global_var1,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec_minmax,
.extra1 = &min_val,
.extra2 = &max_val
},
{
.procname = "sample_value2",
.data = &global_var2,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec_minmax,
.extra1 = &min_val,
.extra2 = &max_val
},
{}
};
static struct ctl_table parent_ctl_table[] = {
{
.procname = "mykernel",
.mode = 0555,
.child = child_ctl_table
},
{}
};
static int __init sysctl_module_init(void) {
if (!(header = register_sysctl_table(parent_ctl_table))) {
printk(KERN_ALERT "Error: Failed to register parent_ctl_table\n");
return -EFAULT;
}
printk(KERN_INFO "Start global_var1 = %d, global_var2 = %d\n", global_var1, global_var2);
return SUCCESS;
}
static void __exit sysctl_module_exit(void) {
printk(KERN_INFO "End global_var1 = %d, global_var2 = %d\n", global_var1, global_var2);
}
module_init(sysctl_module_init);
module_exit(sysctl_module_exit);
But there are faults from time to time.
Also, i have another question:
At kernel sources, there is the comment, that this parameter is deprecated... Why?
How can I work with the hierarchy of parameters without this field?
Please help!
Fixed my bug
We should unregister_ctl_table in exit function
But also there is a question with the field "child"...