如果不存在请建立一个sym link指向你的kernel header directory:
/lib/modules/`uname -r`/build -> /usr/src/linux-headers-2631-14/
Ref here:
>看一下include/linux/inith中的注释:
1)对 __init的:
These macros are used to mark some functions or
initialized data (doesn't apply to uninitialized data)
as `initialization' functions The kernel can take this
as hint that the function is used only during the initialization
phase and free up used memory resources after
2)对module_init的:
module_init() - driver initialization entry point
@x: function to be run at kernel boot time or module insertion
module_init() will either be called during do_initcalls (if
builtin) or at module insertion time (if a module) There can only
be one per module上面讲的自旋锁,信号量和互斥锁的实现,都是使用了原子 *** 作指令。由于原子 *** 作会 lock,当线程在多个 CPU 上争抢进入临界区的时候,都会 *** 作那个在多个 CPU 之间共享的数据 lock。CPU 0 *** 作了 lock,为了数据的一致性,CPU 0 的 *** 作会导致其他 CPU 的 L1 中的 lock 变成 invalid,在随后的来自其他 CPU 对 lock 的访问会导致 L1 cache miss(更准确的说是communication cache miss),必须从下一个 level 的 cache 中获取。
这就会使缓存一致性变得很糟,导致性能下降。所以内核提供一种新的同步方式:RCU(读-复制-更新)。
RCU 解决了什么
RCU 是读写锁的高性能版本,它的核心理念是读者访问的同时,写者可以更新访问对象的副本,但写者需要等待所有读者完成访问之后,才能删除老对象。读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。
RCU 适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是 RCU 发挥作用的最佳场景。
RCU 例子
RCU 常用的接口如下图所示:
为了更好的理解,在剖析 RCU 之前先看一个例子:
#include<linux/kernelh>#include<linux/moduleh>#include<linux/inith>#include<linux/slabh>#include<linux/spinlockh>#include<linux/rcupdateh>#include<linux/kthreadh>#include<linux/delayh>structfoo{inta;structrcu_headrcu;};staticstructfoog_ptr;staticintmyrcu_reader_thread1(voiddata)//读者线程1{structfoop1=NULL;while(1){if(kthread_should_stop())break;msleep(20);rcu_read_lock();mdelay(200);p1=rcu_dereference(g_ptr);if(p1)printk("%s: read a=%d\n",__func__,p1->a);rcu_read_unlock();}return0;}staticintmyrcu_reader_thread2(voiddata)//读者线程2{structfoop2=NULL;while(1){if(kthread_should_stop())break;msleep(30);rcu_read_lock();mdelay(100);p2=rcu_dereference(g_ptr);if(p2)printk("%s: read a=%d\n",__func__,p2->a);rcu_read_unlock();}return0;}staticvoidmyrcu_del(structrcu_headrh)//回收处理 *** 作{structfoop=container_of(rh,structfoo,rcu);printk("%s: a=%d\n",__func__,p->a);kfree(p);}staticintmyrcu_writer_thread(voidp)//写者线程{structfooold;structfoonew_ptr;intvalue=(unsignedlong)p;while(1){if(kthread_should_stop())break;msleep(250);new_ptr=kmalloc(sizeof(structfoo),GFP_KERNEL);old=g_ptr;new_ptr=old;new_ptr->a=value;rcu_assign_pointer(g_ptr,new_ptr);call_rcu(&old->rcu,myrcu_del);printk("%s: write to new %d\n",__func__,value);value++;}return0;}staticstructtask_structreader_thread1;staticstructtask_structreader_thread2;staticstructtask_structwriter_thread;staticint__initmy_test_init(void){intvalue=5;printk("figo: my module init\n");g_ptr=kzalloc(sizeof(structfoo),GFP_KERNEL);reader_thread1=kthread_run(myrcu_reader_thread1,NULL,"rcu_reader1");reader_thread2=kthread_run(myrcu_reader_thread2,NULL,"rcu_reader2");writer_thread=kthread_run(myrcu_writer_thread,(void)(unsignedlong)value,"rcu_writer");return0;}staticvoid__exitmy_test_exit(void){printk("goodbye\n");kthread_stop(reader_thread1);kthread_stop(reader_thread2);kthread_stop(writer_thread);if(g_ptr)kfree(g_ptr);}MODULE_LICENSE("GPL");module_init(my_test_init);module_exit(my_test_exit);
执行结果是:
myrcu_reader_thread2:reada=0myrcu_reader_thread1:reada=0myrcu_reader_thread2:reada=0myrcu_writer_thread:writetonew5myrcu_reader_thread2:reada=5myrcu_reader_thread1:reada=5myrcu_del:a=0
RCU 原理
可以用下面一张图来总结,当写线程 myrcu_writer_thread 写完后,会更新到另外两个读线程 myrcu_reader_thread1 和 myrcu_reader_thread2。读线程像是订阅者,一旦写线程对临界区有更新,写线程就像发布者一样通知到订阅者那里,如下图所示。
写者在拷贝副本修改后进行 update 时,首先把旧的临界资源数据移除(Removal);然后把旧的数据进行回收(Reclamation)。结合 API 实现就是,首先使用 rcu_assign_pointer 来移除旧的指针指向,指向更新后的临界资源;然后使用 synchronize_rcu 或 call_rcu 来启动 Reclaimer,对旧的临界资源进行回收(其中 synchronize_rcu 表示同步等待回收,call_rcu 表示异步回收)。
为了确保没有读者正在访问要回收的临界资源,Reclaimer 需要等待所有的读者退出临界区,这个等待的时间叫做宽限期(Grace Period)。
Grace Period
中间的部分代表的就是 Grace Period,中文叫做宽限期,从 Removal 到 Reclamation,中间就隔了一个宽限期,只有当宽限期结束后,才会触发回收的工作。宽限期的结束代表着 Reader 都已经退出了临界区,因此回收工作也就是安全的 *** 作了。
宽限期是否结束,与 CPU 的执行状态检测有关,也就是检测静止状态 Quiescent Status。
Quiescent Status
Quiescent Status,用于描述 CPU 的执行状态。当某个 CPU 正在访问 RCU 保护的临界区时,认为是活动的状态,而当它离开了临界区后,则认为它是静止的状态。当所有的 CPU 都至少经历过一次 Quiescent Status 后,宽限期将结束并触发回收工作。
因为 rcu_read_lock 和 rcu_read_unlock 分别是关闭抢占和打开抢占,如下所示:
staticinlinevoid__rcu_read_lock(void){preempt_disable();}
staticinlinevoid__rcu_read_unlock(void){preempt_enable();}
所以发生抢占,就说明不在 rcu_read_lock 和 rcu_read_unlock 之间,即已经完成访问或者还未开始访问。
Linux 同步方式的总结
资料免费领
学习直通车#define NODE_MODULE(modname, regfunc) \
extern "C" { \
NODE_MODULE_EXPORT node::node_module_struct modname ## _module = \
{ \
NODE_STANDARD_MODULE_STUFF, \
regfunc, \
NODE_STRINGIFY(modname) \
}; \
}
NODE_MODULE(name, init);展开后就是:
extern "C" {
node::node_module_struct name_module =
{
1,//NODE_MODULE_VERSION
NULL,
__FILE__,
init,
"name"
};
}
其实就是定义了一个结构体,编译后为动态链接库 node 文件中的一个符号,最后使用的时候由 nodecc 调用uv_dlopen和uv_dlsym动态链接模块,得到初始化函数并执行。
mod->register_func(target);
uv库封装了对动态链接文件 *** 作的具体实现,win下实际调用 LoadLibraryExW 和 GetProcAddress,nix下实际调用dlopen和dlsym实现上诉功能。假设手上有一块从淘宝上买来的开发板,我要在开发板的I2C总线上增加一个从设备(如at24c08),那么我要怎样写这个“I2C设备驱动”,让
应用程序可以访问at24c08呢
先来看一个最简单的i2c设备驱动:
static struct i2c_board_info at24cxx_info = { //所支持的i2c设备的列表
I2C_BOARD_INFO("at24c08", 0x50), //一项代表一个支持的设备,它的名字叫做“at24c08”,器件地址是0x50
};
static struct i2c_client at24cxx_client;
static int at24cxx_dev_init(void)
{
struct i2c_adapter i2c_adap; //分配一个适配器的指针
i2c_adap = i2c_get_adapter(0); //调用core层的函数,获得一个i2c总线。这里我们已经知道新增的器件挂接在编号为0的i2c总线上
at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info); // 把i2c适配器和新增的I2C器件关联起来,这个用了i2c总线0,地址是0x50。这就组成了一个客户端
at24cxx_client i2c_put_adapter(i2c_adap);
return 0;
}
static void at24cxx_dev_exit(void)
{
i2c_unregister_device(at24cxx_client);
}
module_init(at24cxx_dev_init);
module_exit(at24cxx_dev_exit);
从上面的程序可以看到,写一个i2c设备驱动程序,与写普通的字符驱动基本一样。特别之处是它调用了i2c的core层的函数,以获得对i2c总线的控制。因为用的是开发板,板上的与soc芯片(一般来说就是arm的芯片)i2c总线驱动一般都做好了,直接调用core层的函数就可以控制soc的i2c模块了。也就是说,写i2c设备驱动不需要关注arm内部的i2c模块的寄存器,我们需要关注的是设备(at24c08)的寄存器以及它的datasheet对时序的要求。
其实,添加i2c设备的方法很灵活。根据Linux的官方文档《linux-342\Documentation\i2c\instantiating-devices》,添加i2c设备的方法总结有4种:
1 i2c_register_board_info:根据总线编号、设备名字(“at24c08”)、设备地址(0x50)注册一个字符驱动。这种方法最简单、最粗暴,最贴近平时在开片机上开发i2c器件的。
2 i2c_new_device:根据i2c总线的编号,声明一个i2c设备:这种方法就是上面例子用的方法。这种方法也简单,但是需要事先知道器件挂接在哪条总线上。对于设备,还实现知道了设备地址0x50,总线适配器也支持名字为“at24c08”的设备
3 i2c_new_probed_device:
4从用户空间实例化一个器件:这个方法相当智能快速,如下输入指令,即可增加一个i2c设备,同时增加了对应的设备文件。
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
根据英文文档的标题,添加i2c设备有称之为“i2c设备的实例化”。
从上述可以知道,在实例化一个i2c设备之前,除了有对应的驱动支持总线外(这里是总线0),还需要有一个驱动使用了总线0发送时序,支持名字为"at24c08"的器件。这个驱动用总线驱动的函数,配置了at24c08的寄存器。您好,您的问题很有意思,我来给您解答一下。当您加载模块但没有调用module_init时,有可能是您的模块没有正确加载,或者您的模块没有正确安装。因此,您需要检查您的模块是否正确加载,并确保您的模块已经正确安装。如果您的模块仍然没有正确加载,您可以尝试重新安装模块,或者检查您的模块是否有任何错误。通俗点说就是
make 是执行编译工作(对应于当前文件夹下的Makefile)
make moveold 这一句根据名字猜测的话应该是:如果以前安装过该工具,则对其进行迁移
make all install 这一句就是真正的编译安装就是编译源代码,并安装该工具
最后一句,应该是执行/generate-modprobeconf 这个配置脚本文件, /etc/modprobeconf应该是传给该脚本文件的参数如果是输出文件,那么应该是生成配置信息,在该软件启动的时候,可能需要从这个文件中读入具体参数(这样就可以由用户在以后根据自己的需要配置)这个需要编写Makefile文件
首先说一下你的程序有错误:下面这个是我改的,
firstc:
#include<linux/moduleh>
#include<linux/inith>
static int __init hello_init(void) {
printk(KERN_ALERT "Hello,kernal\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_ALERT "Goodbye,kernal\n");
}
MODULE_LICENSE( "GPL" );
module_init(hello_init);
module_exit(hello_exit);
对于你这个程序,我写了一份:
obj-m := firsto #这个是要中间文件
Kernel_path=/usr/src/linux-headers-$(shell uname -r) #内核存在的路径
all:
make -C $(Kernel_path) M=$(PWD) modules
clean:
make -C $(Kernel_path) M=$(PWD) clean
像你的就该为
Kernel_path=/usr/src/linux-headers-$(shell uname -r)/build
保存后make,ok!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)