如何调用linux内核源码下载中的全局变量

新手园地& & & 硬件问题Linux系统管理Linux网络问题Linux环境编程Linux桌面系统国产LinuxBSD& & & BSD文档中心AIX& & & 新手入门& & & AIX文档中心& & & 资源下载& & & Power高级应用& & & IBM存储AS400Solaris& & & Solaris文档中心HP-UX& & & HP文档中心SCO UNIX& & & SCO文档中心互操作专区IRIXTru64 UNIXMac OS X门户网站运维集群和高可用服务器应用监控和防护虚拟化技术架构设计行业应用和管理服务器及硬件技术& & & 服务器资源下载云计算& & & 云计算文档中心& & & 云计算业界& & & 云计算资源下载存储备份& & & 存储文档中心& & & 存储业界& & & 存储资源下载& & & Symantec技术交流区安全技术网络技术& & & 网络技术文档中心C/C++& & & GUI编程& & & Functional编程内核源码& & & 内核问题移动开发& & & 移动开发技术资料ShellPerlJava& & & Java文档中心PHP& & & php文档中心Python& & & Python文档中心RubyCPU与编译器嵌入式开发驱动开发Web开发VoIP开发技术MySQL& & & MySQL文档中心SybaseOraclePostgreSQLDB2Informix数据仓库与数据挖掘NoSQL技术IT业界新闻与评论IT职业生涯& & & 猎头招聘IT图书与评论& & & CU技术图书大系& & & Linux书友会二手交易下载共享Linux文档专区IT培训与认证& & & 培训交流& & & 认证培训清茶斋投资理财运动地带快乐数码摄影& & & 摄影器材& & & 摄影比赛专区IT爱车族旅游天下站务交流版主会议室博客SNS站务交流区CU活动专区& & & Power活动专区& & & 拍卖交流区频道交流区
稍有积蓄, 积分 482, 距离下一级还需 18 积分
论坛徽章:0
关于linux内核抢占的问题:
在不支持内核抢占中:
如果中断发生在内核中,本次中断返回是不会引起调度的,而要到最初使CPU从用户空间进入内核空间的那次系统调用或中断(异常)返回时才会发生调度。
这句话正确吗?
或者 什么时候 引起调度!
附:编译内核的时候可选择 配制成 可抢占 或 不可抢占!
&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp&&nbsp|&&nbsp
丰衣足食, 积分 621, 距离下一级还需 379 积分
论坛徽章:0
如果是从异常返回的话。Cli禁止中断。(中断返回时,本地中断是禁止的)
1.& & & & 确定要返回到内核态还是用户态。
2.& & & & 如果是返回到内核态的话:
resume_kernel:
cmpl $0, 0x14(%ebp)&&//检查是否可以抢占thread_info-&preempt_count
jz need_resched
restore_all:
addl $4, %esp
iret& && && & //中断和异常退出的硬件处理过程
need_resched:
movl 0x8(%ebp), %ecx
testb $(1&&TIF_NEED_RESCHED), %cl
jz restore_all&&//如果falgs没有设置TIF_NEED_RESCHED,不需要抢占
testl $0xx30(%esp)
jz restore_all //如果正在禁用本地CPU,不需要抢占
call preempt_schedule_irq //打开本地中断,调用preempt_schedule,进行抢占
jmp need_resched
3.& & & & 如果是返回到用户态的话:
resume_userspace:
movl 0x8(%ebp), %ecx
andl $0x0000ff6e, %ecx //检查flags是否只设置了某些标志
je restore_all
jmp work_pending
work_pending:
testb $(1&&TIF_NEED_RESCHED), %cl
jz work_notifysig //如果falgs没有设置TIF_NEED_RESCHED,不需要调度
work_resched:
call schedule& && &//进程调度
jmp resume_userspace
work_notifysig:& &//处理挂起的信号,虚拟8086和单步执行
movl %esp, %eax
testl $0xx30(%esp)
work_notifysig_v86:
pushl %ecx
call save_v86_state
movl %eax, %esp
xorl %edx, %edx
call do_notify_resume
jmp restore_all
restore_all:
addl $4, %esp
iret& && && & //中断和异常退出的硬件处理过程
注意:不管返回到内核态还是返回到用户态,都会试图进行进程调度。在返回内核态进行进程调度就叫做内核抢占。在返回用户态进行进程调度属于普通进程调度。它们都会检查current-&thread_info-&flags 的TIF_NEED_RESCHED标志是否需要进行进程调度。但是返回内核态的时候,额外的需要检查current-&thread_info-&preempt_count的内核抢占位确定是否可以进行内核抢占。
稍有积蓄, 积分 482, 距离下一级还需 18 积分
论坛徽章:0
多谢,没有研究这么深,看来还要学习一下关于进程调度方面的知识。。。thks
丰衣足食, 积分 981, 距离下一级还需 19 积分
论坛徽章:0
zhuqing_739
在不支持内核抢占中,进程时间片用完,由于资源原因放弃cpu,进程结束等情况会发生切换。
富足长乐, 积分 7205, 距离下一级还需 795 积分
论坛徽章:1
current-&thread_info-&flags 的TIF_NEED_RESCHED标志是如何被设置的?
以前这一标志是全局变量,如果代码认为某个事件可能导致reschedule,就在全局变量里设置这一标志;
现在这一标志被挪到了task_struct, 那就是说在系统的任何地方,如果某个事件可能导致reschedule, 就要设置current task_struct的这一标志。问题是current task_struct是否随时可以access?
富足长乐, 积分 5503, 距离下一级还需 2497 积分
论坛徽章:2
current宏很有名的啊。
富足长乐, 积分 7205, 距离下一级还需 795 积分
论坛徽章:1
是有名,但是是不是任何情况下都可以操作?
如果cpu b引起cpu a context switch(如果不是通过中断方式), 那么如何设置cpu a的CURRENT这一标志?
丰衣足食, 积分 621, 距离下一级还需 379 积分
论坛徽章:0
本帖最后由 blake326 于
10:32 编辑
linuxfellow
一般是在cfs调度时候设置的。
具体来说,调度具体分:
1. cfs正常调度,hrtick() hrtimer定时器,&&cfs在enqueue_task,dequeue_task一些工作之后,可能需要设置下次中断时间。在hrtick中直接resched_task进行调度。
2. fork新创建一个进程。一般来说会交换父子进程的虚拟时间,那么就会直接调用resched_task进行调度。
3. wakeup一个进程,一般会补偿虚拟时间。通过check_preempt_wakeup,如果需要重新调度则调用resched_task进行调度。
resched_task设置了TIF_NEED_RESCHED,在中断或者异常返回时适当的进行抢占或者调度。
暂时就想到这么多,欢迎补充纠正。
------------------------------------------------------------------------------------------------------------
如果cpu b引起cpu a context switch(如果不是通过中断方式), 那么如何设置cpu a的CURRENT这一标志?
不可能把,cpu b wakeup一个进程都是到cpu b上的吧, 应该没有你说的这种状况把》
小富即安, 积分 2540, 距离下一级还需 2460 积分
论坛徽章:2
linuxfellow
& & 嗯, 看了一下. 你的问题是resched_task标志如何被设置, 这个和何时会进行调度是两回事. flag被设置成TIF_NEED_RESCHED是在发生调度之前, 只要是在调度之前, 任何时候设置(当然如LS所言, 设置flag也是有时机的)都是可以的. current宏只要是在进程上下文中都可以访问, 中断上下文中不可以. 对于smp的情况, 以前貌似是一个全局的运行队列, 现在每个cpu都有自己的运行队列, 所以每个cpu都有自己独立的进程上下文, 假设两个CPU同时访问current宏, 那是不一样的进程上下文.
& & 个人愚见, 砖头轻拍.
富足长乐, 积分 7205, 距离下一级还需 795 积分
论坛徽章:1
captivated
PREEMPT可以有下面4种情况触发:
1 中断(硬中断或软中断)
2 一些函数触发, 如spin_unlock
3 进程主动调用schedule
4. 进程阻塞,被迫调用schedule
2,3,4都是运行在process stack上时发生,用CURRENT可以方便找到当前task_struct,然后设置flag
1运行在irq stack,如果isr调用try_to_wakeup_process去唤醒一个优先级高的进程,这时候如何找到被中断的current task_struct来设置这一标志?博客访问: 462012
博文数量: 257
博客积分: 511
博客等级: 下士
技术积分: 1590
注册时间:
认证徽章:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
原文地址: 作者:
摘要:本文主要讲述linux如何处理ARM cortex A9多核处理器的内核同步部分。主要包括其中的内存屏障、原子变量、每CPU变量。
自旋锁、信号量、complete、读写自旋锁、读写信号量、顺序锁、RCU放在后文介绍。
法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友()发表于,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。
本连载文章并不是为了形成一本适合出版的书籍,而是为了向有一定内核基本的读者提供一些linux3.0源码分析。因此,请读者结合《深入理解LINUX内核》第三版阅读本连载。
Paul曾经讲过:在建造大桥之前,必须得明白力学的原理。要理解内存屏障,首先得明白计算机硬件体系结构,特别是硬件是如何管理缓存的。缓存在多核上的一致性问题是如何产生的。
要深入理解内存屏障,建议大家首先阅读以下资料:
1、《深入理解并行编程》,下载地址是:.
2、内核自带的文档documentation/memory-barriers.txt.
内存屏障是如此难此理解也难以使用,为什么还需要它呢?硬件工程师为什么不给软件开发者提供一种程序逻辑一致性的内存视图呢?归根结底,这个问题受到光速的影响。在1.8G的主频系统中,在一个时钟周期内,光在真空中的传播距离只有几厘米,电子的传播距离更短,根本无法传播到整个系统中。
Linux为开发者实现了以下内存屏障:
在多核和IO内存、缓存之间设置一个完全读写屏障
在多核和IO内存、缓存之间设置一个读屏障
在多核和IO内存、缓存之间设置一个写屏障
读依赖屏障
read_barrier_depends
在多核和IO内存、缓存之间设置一个读依赖屏障
多核读写屏障
在多核之间设置一个完全读写屏障
多核读屏障
在多核之间设置一个读屏障
多核写屏障
在多核之间设置一个写屏障
多核读依赖屏障
Smp_read_barrier_depends
在多核之间设置一个读依赖屏障
按照linux设计,mb、rmb、wmb、read_barrier_depends主要用于CPU与外设IO之间。在arm及其他一些RISC系统中,通常将外设IO地址映射为一段内存地址。虽然这样的内存是非缓存的,但是仍然受到内存读写乱序的影响。例如,我们要读写一个外部IO端口的数据时,可能会先向某个寄存器写入一个要读写的端口号,再读取另一个端口得到其值。如果要读取值之前,设置的端口号还没有到达外设,那么通常读取的数据是不可靠的,有时甚至会损坏硬件。这种情况下,需要在读寄存器前,设置一个内存屏障,保证二次操作外部端口之间没有乱序。
Smp_mb、smp_rmb、smp_wmb仅仅用于SMP系统,它解决的是多核之间内存乱序的问题。其具体用法及原理,请参阅《深入理解并行编程》。
read_barrier_depends和smp_ read_barrier_depends是读依赖屏障。除了在DEC alpha架构外,linux支持的其他均不需要这个屏障。Alpha需要它,是因为alpha架构中,使用的缓存是split cache.所谓split cache,简单的说就是一个核的缓存不止一个.在arm架构下,我们可以简单的忽略这个屏障。
虽然linux分读写屏障、读屏障、写屏障,但是在ARM中,它们的实现都是一样的,没有严格区别不同的屏障。
内存屏障也隐含了编译屏障的作用。所谓编译屏障,是为了解决编译乱序的问题。这个问题的根源在于:在发明编译器的时候,多核还未出现。编译器开发者认为编译出来的二进制代码只要在单核上运行正确就可以了。甚至,只要保证单线程内的程序逻辑正确性即可。例如,我们有两句赋值语句:
编译器并不保证生成的汇编是按照C语句的顺序。为了效率或者其他原因,它生成的汇编语句可能与下面的C代码是一致的:
要防止编译乱序,可以使用编译屏障指令barrier();
不是题外话的题外话
在描述原子变量和每CPU变量、其他内核同步方法之前,我们先看一段代码。假设有两个线程A和线程B,它们的执行代码分别是foo_a、foo_b,它们都操作一个全局变量g_a,如下:
Unsigned long g_a;
Int stoped = 0;
Void foo_a(void *unused)
&&&&&&&& While (stopped == 0)
&&&&&&&& {
Void foo_b(void *unused)
&&&&&&&& While (stopped == 0)
&&&&&&&& {
假设当stopped被设置为1后,线程A和线程B执行了count_a、count_b次,您会认为g_a的值等于count_a + count_b吗?
恩,当您在一台真实的计算上测试这个程序的时候,也许您的直觉是对的,g_a的值确实等于count_a + count_b。
但是,请您:
1、将测试程序运行的时间运行得久一点
2、或者将程序放到arm、powerpc或者mips上运行
3、或者找一台运行linux的多核x86机器运行。
g_a的值还会等于count_a + count_b吗?
答案是不会。
原因是什么呢?
产生这个问题的根本原因是:
1、在多核上,一个CPU在向内存写入数据时,它并不知道其他核在向同样的内存地址写入。某一个核写入的数据可能会覆盖其他核写入的数据。假说g_a当前值是0,那么线程A和线程B同时读取它的值,当内存中的值放入总线上后,两个线程都认为其值是0.并同时将其值加1后提交给总线并向内存中写入1.其中一个线程对g_a的递增被丢失了。
2、Arm、powerpc、mips这些体系结构都是存储/加载体系结构,它们不能直接对内存中的值进行操作。而必须将内存中的值加载到寄存器中后,将寄存器中的值加1后,再存储到内存中。如果两个线程都读取0值到寄存器中,并将寄存器的值递增为1后存储到内存,那么也会丢失一次递增。
3、即使在x86体系结构中,允许直接对内存进行递增操作。也会由于编译器的原因,将内存中的值加载到内存,同第二点,也可能造成丢失一次递增。
怎么解决这个问题呢?
聪明的读者会说了:是不是需要这样声明g_a?
Unsigned long volatile g_a;
更聪明的读者会说,在写g_a时还需要锁住总线,使用汇编语句并在汇编前加lock前缀。
锁总线是正确的,但是也必须将g_a声明为valatile类型的变量。可是,在我们分析的ARM多核上,应该怎么办?
原子变量就是为了解决我们遇到的问题:如果在共享内存的多核系统上正确的修改共享变量的计数值。
首先,我们看一下老版本是如何定义原子变量的:
&* 将counter声明成volatile是为了防止编译器优化,强制从内存中读取counter的值
typedef struct {
} atomic_t;
在linux3.0中,已经有所变化:
typedef struct {
} atomic_t;
已经没有volatile来定义counter了。难道不需要禁止编译优化了吗?答案不是的。这是因为linux3.0已经修改了原子变量相关的函数。
除此以外,还有一组操作64位原子变量的变体,以及一些位操作宏及函数。这里不再罗列。
&* 返回原子变量的值。
&* 这里强制将counter转换为volatile int并取其值。目的就是为了避免编译优化。
#define atomic_read(v)&& (*(volatile int *)&(v)-&counter)
&* 设置原子变量的值。
#define atomic_set(v,i)&&& (((v)-&counter) = (i))
原子递增的实现比较精妙,理解它的关键是需要明白ldrex、strex这一对指令的含义。
&* 原子的递增计数的值。
static inline void atomic_add(int i, atomic_t *v)
&&&&&&&& /**
&&&&&&&& &* __volatile__是为了防止编译器乱序。与"#define atomic_read(v)&&&&&&&&& (*(volatile int *)&(v)-&counter)"中的volatile类似。
&&&&&&&& &*/
&&&&&&&& __asm__ __volatile__("@ atomic_add\n"
&&&&&&&& /**
&&&&&&&& &* ldrex是arm为了支持多核引入的新指令,表示"排它性"加载。与mips的ll指令一样的效果。
&&&&&&&& &* 它与"排它性"存储配对使用。
&&&&&&&& &*/
"1:&&& ldrex&&&&&&&& %0, [%3]\n"
&&&&&&&& /**
&&&&&&&& &* 原子变量的值已经加载到寄存器中,这里对寄存器中的值减去指定的值。
&&&&&&&& &*/
"&&&&&& add& %0, %0, %4\n"
&&&&&&&& /**
&&&&&&&& &* strex是"排它性"的存储寄存器的值到内存中。类似于mips的sc指令。
&&&&&&&& &*/
"&&&&&& strex&&&&&&&& %1, %0, [%3]\n"
&&&&&&&& /**
&&&&&&&& &* 关键代码是这里的判断。如果在ldrex和strex之间,其他核没有对原子变量变量进行加载存储操作,
&&&&&&&& &* 那么寄存器中值就是0,否则非0.
&&&&&&&& &*/
"&&&&&& teq&& %1, #0\n"
&&&&&&&& /**
&&&&&&&& &* 如果其他核与本核冲突,那么寄存器值为非0,这里跳转到标号1处,重新加载内存的值并递增其值。
&&&&&&&& &*/
"&&&&&& bne& 1b"
&&&&&&&& : "=&r" (result), "=&r" (tmp), "+Qo" (v-&counter)
&&&&&&&& : "r" (&v-&counter), "Ir" (i)
&&&&&&&& : "cc");
atomic_add_return递增原子变量的值,并返回它的新值。它与atomic_add的最大不同,在于在原子递增前后各增加了一句:smp_mb();
这是由linux原子操作函数的语义规定的:所有对原子变量的操作,如果需要向调用者返回结果,那么就需要增加多核内存屏障的语义。通俗的说,就是其他核看到本核对原子变量的操作结果时,本核在原子变量前的操作对其他核也是可见的。
理解了atomic_add,其他原子变量的实现也就容易理解了。这里不再详述。
每CPU变量
原子变量是不是很棒?无论有多少个核,每个核都可以修改共享内存变量,并且这样的修改可以被其他核立即看到。多核编程原来so easy!
不过还是不能太高兴了,原子变量虽然不是毒瘤,但是也差不多了。我曾经遇到一个兄弟,工作十多年了吧,得意的吹嘘:“我写的代码精细得很,统计计数都是用的汇编实现的,汇编加法指令还用了lock前缀。”呜呼,这个兄弟完全没有意识到在x86体系结构中,这个lock前缀对性能的影响。
不管哪种架构,原子计数(包含原子比较并交换)都是极耗CPU的。与单纯的加减计数指令相比,它消耗的CPU周期要高一到两个数量级。原因是什么呢?还是光信号(电信号)的传播速度问题。要让某个核上的修改被其他核发现,需要信号在整个系统中进行传播。这在几个核的系统中,可能还不是大问题,但是在1024个核以上的系统中呢?比如我们熟知的天河系统。
为了解决这个问题,内核引用入了每CPU变量。
可以将它理解为数据结构的数组。系统的每个CPU对应数组中的一个元素。每个CPU都只访问本CPU对应的数组元素。
每CPU数组中,确保每一个数组元素都位于不同的缓存行中。假如您有一个int型的每CPU数组,那么每个int型都会占用一个缓存行(很多系统中一个缓存行是32个字节),这看起来有点浪费。这样做的原因是:
ü对每CPU数组的并发访问不会导致高速缓存行的失效。避免在各个核之间引起缓存行的抖动。
ü这也是为了避免出现多核之间数据覆盖的情况。对这一点,可能您暂时不能理解。也许您在内核领域实际工作几年,也会觉得这有点难于理解。不过,现在您只需要知道有这么一个事实存在就行了。
关于第二个原因,您可以参考一个内核补丁:
99dcc3e5a94ed491fbefc0bbb267f995。据提交补丁的兄弟讲,这个补丁表面是一个性能优化的措施。但是,它实际上是一个BUG。该故障会引起内核内存分配子系统的一个BUG,最终会引起内存分配子系统陷入死循环。我实际的遇到了这个故障,可怜了我的两位兄弟,为了解决这个故障,花了近两个月时间,今天终于被我搞定了。
每CPU变量的主要目的是对多CPU并发访问的保护。但是它不能防止同一核上的中断的影响。我们曾经讲过,在arm、mips等系统中,++、--这样的简单计数操作,都需要几条汇编语句来完成。如果在从内存中加载数据到寄存器后,还没有将数据保存到内存中前,有中断将操作过程打断,并在中断处理函数中对同样的计数值进行操作,那么中断中的操作将被覆盖。
不管在多CPU还是单CPU中,内核抢占都可能象中断那样破坏我们对计数的操作。因此,应当在禁用抢占的情况下访问每CPU变量。内核抢占是一个大的话题,我们在讲调度的时候再提这个事情。
相关宏和函数:
宏或者函数
DEFINE_PER_CPU
静态定义一个每CPU变量数组
获得每CPU数组中某个CPU对应的元素
__this_cpu_ptr
获得当前CPU在数组中的元素的指针。
__get_cpu_var
获得当前CPU在数组中的元素的值。
get_cpu_ptr
关抢占,并获得CPU对应的元素指针。
put_cpu_var
开抢占,与get_cpu_ptr配对使用。
看到这里,也许大家会觉得,用每CPU变量来代替原子变量不是很好么?不过,存在的东西就必然在存在的理由,因为每CPU变量用于计数有一个致使的弊端:它是不精确的。我们设想:有32个核的系统,每个核更新自己的CPU计数,如果有一个核想知道计数总和怎么办?简单的用一个循环将计数加起来吗?这显然是不行的。因为某个核修改了自己的计数变量时,其他核不能立即看到它对这个核的计数进行的修改。这会导致计数总和不准。特别是某个核对计数进行了大的修改的时候,总计数看起来会严重不准。
为了使总和大致可信,内核又引入了另一种每CPU变量:percpu_counter。
percpu_counter的详细实现在percpu_counter.c中。有兴趣的同学可以研究一下。下面我们讲一个主要的函数,希望起个抛砖引玉的作用:
* 增加每CPU变量计数
*&&&&&&&&&&& fbc:&&&&&&&&&&& 要增加的每CPU变量
*&&&&&&&&&&& amount:&& 本次要增加的计数值
*&&&&&&&&&&& batch:&&&&&& 当本CPU计数超过此值时,要确保其他核能及时看到。                                     
void __percpu_counter_add(struct percpu_counter *fbc, s64 amount, s32 batch)
&&&&&&&& s64
&&&&&&&& /**
* 为了避免当前任务飘移到其他核上,或者被其他核抢占,导致计数丢失
* 这里需要关抢占。
&&&&&&&& preempt_disable();
&&&&&&&& /**
&&&& * 获得本CPU计数值并加上计数值。
&&&&&&&& count = __this_cpu_read(*fbc-&counters) +
&&&&&&&& if (count &= batch || count &= -batch) {/* 本次修改的值较大,需要同步到全局计数中 */
&&&&&&&&&&&&&&&&&& spin_lock(&fbc-&lock);/* 获得自旋锁,这样可以避免多核同时更新全局计数。 */
&&&&&&&&&&&&&&&&&& fbc-&count +=/* 修改全局计数,并将本CPU计数清0 */
&&&&&&&&&&&&&&&&&& __this_cpu_write(*fbc-&counters, 0);
&&&&&&&&&&&&&&&&&& spin_unlock(&fbc-&lock);
&&&&&&&& } else {
&&&&&&&&&&&&&&&&&& __this_cpu_write(*fbc-&counters, count);/* 本次修改的计数较小,仅仅更新本CPU计数。 */
&&&&&&&& }
&&&&&&&& preempt_enable();/* 打开抢占 */
大家现在觉得多核编程有那么一点难了吧?一个简单的计数都可以搞得这么复杂。
复杂的东西还在后面。接下来我们新开一帖,讨论内核同步的其他技术:自旋锁、信号量、RCU、无锁编程。
阅读(3328) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。当前位置:&&技术问答>
linux内核全局变量定义问题!
&&&&来源:&互联网& 发布时间:
&&&&本文导语:&
请问各位高手、行家,linux内核代码中如何定义全局变量啊?就是定义此变量之后可以在各内核代码文件中都可以访问和使用!谢谢!
如果编译到内核,内核中的代码可以用
如果是模块间使用的话,...
请问各位高手、行家,linux内核代码中如何定义全局变量啊?就是定义此变量之后可以在各内核代码文件中都可以访问和使用!谢谢!
如果编译到内核,内核中的代码可以用
如果是模块间使用的话,用EXPORT_SYMBOL() 导出后,其他模块就可以使用
linux 内核头文件提供了方便来管理你的符号的可见性, 因此减少了命名空间的污染( 将与在内核别处已定义的符号冲突的名子填入命名空间), 并促使了正确的信息隐藏. 如果你的模块需要输出符号给其他模块使用, 应当使用下面的宏定义:
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
上面宏定义的任一个使得给定的符号在模块外可用. _GPL 版本的宏定义只能使符号对 GPL 许可的模块可用. 符号必须在模块文件的全局部分输出, 在任何函数之外, 因为宏定义扩展成一个特殊用途的并被期望是全局存取的变量的声明. 这个变量存储于模块的一个特殊的可执行部分( 一个 "ELF 段" ), 内核用这个部分在加载时找到模块输出的变量. ( 感兴趣的读者可以看
获知详情, 尽管并不需要这些细节使东西动起来. )
如果你想导出你的变量, 使用 EXPORT_SYMBOL, 你的变量名字就可以了.
您可能感兴趣的文章:
本站()旨在分享和传播互联网科技相关的资讯和技术,将尽最大努力为读者提供更好的信息聚合和浏览方式。本站()站内文章除注明原创外,均为转载、整理或搜集自网络。欢迎任何形式的转载,转载请注明出处。
相关文章推荐
特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!
(C)2012-,E-mail:www_169it_(请将#改为@)2015年9月 Linux/Unix社区大版内专家分月排行榜第二
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。1287人阅读
kernel(2)
Linux(6)
最近一段时间在研究 linux 的内核,linux 内核中的代码十分的精炼,无论出于什么目的来学习它,都会有很多的收获。
但是,看代码与写代码虽说都需要大量的脑力参与其中,但是,二者的等级绝对不同。怎么各不同?就相当于我们在
学习的时候,知道一个函数应该如何的使用 与 这个函数的内部结构是什么样的,在不参照其他人代码的情况下自己
编写相同功能的函数应该如何实现一样。
所以,对于学习内核的方法是这样的:
首先,将内核中实现的方法/函数进行封装,写到自己的程序中,然后通过不同参数的
传递来慢慢熟悉这个函数的功能是什么样的。(知其然)
然后,在仔细的阅读所熟悉功能的函数内部实现中是怎样处理
如此之多的功能的。(知其所以然)
但是,这两个过程确实等同重要的,也就是说不能够从第一个阶段直接跨越到第二个阶段,也不能够将认知仅仅停留在第一个阶段。
========================================================
本篇文章主要是为今后进行第一个阶段进行一些铺垫,主要介绍的就是,
如何在自己的程序中使用内核中实现的库函数,在这里仅仅举一个简单的小例子来说明。
实验目的:
在自己编写的程序中引用内核中的库函数
实验环境:
gcc 4.4.7,
linux -centos
实验思想:
通过 Makefile 和 导出符号表的方式来将内核模块动态的加载到用户自行编译的程序中,
实质上是使用类似于驱动模块的形式加载内核的方法。
也就是根据用户编写的程序需要来动态的将内核中的实现代码抽取出来进行编译成目标文件。
实验代码:
实验代码主要分为3个部分: hello.c &myFunc.h &myFunc.c&
和一个 Makefile
其中 hello.c 中会调用 myFunc.c 中所实现的函数 &void sayHello ( void )
并且为了表明,在执行上述操作之后真的可以在当前用户自行编写的程序中使用kernel 中的变量,
我们在程序中定义了一个位于 include/linux/list.h 中所定义的 struct list_head 变量,它也将会是
后续文章中主要介绍的对象,所以在这里先做一下铺垫与测试。
在 myFunc.h myFunc.c 中分别是 sayHello 方法的定义与实现,在这里之所以写上子函数调用
是为了学习在后续编写复杂调用的时候,主方法是如何对位于其他文件中的子函数的编译等处理问题。
#include &linux/module.h&
#include &linux/init.h&
#include &linux/list.h&
#include &myFunc.h&
static int __init hello_init ( void )
printk(KERN_EMERG&hello linux internal world \n&) ;
sayHello( ) ;
struct list_head myL//no error ,when compiling
return 0 ;
static void __exit hello_exit ( void )
printk(KERN_EMERG&bye ,see you \n&) ;
module_init (hello_init) ;
module_exit (hello_exit) ;
MODULE_LICENSE(&GPL&) ;
MODULE_AUTHOR(&inuyasha1027&) ;
MODULE_VERSION(&1.0&) ;
//myFunc.h
#ifndef __MYFUNC_H__
#define __MYFUNC_H__
void sayHello(void) ;
//myFunc.c
#include &linux/module.h&
void sayHello ( void )
printk(KERN_EMERG&emm hi , i am inuyasha
\n nice to meet you !&
EXPORT_SYMBOL(sayHello) ;
MODULE_LICENSE(&GPL&) ;~
//Makefile
obj-m := hello.o myFunc.o
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
下面介绍的是在代码编写之后,如何对其进行编译与运行
首先,makefile 已经编写好了,那么我们来输入 make 来进行编译
编译好了之后,我们将会在当前目录下面看到有许多的目标文件,其中我们所需要的就是 1. hello.ko 2.myFunc.ko
我们所熟知的有 &.so ----》shared &object ,是linux 中的共享库,同样也被称作动态库。
所以对于 .ko 文件来说,从其英文名称上来入手,ko ===& kernel object ,也就是内核模块的意思,
它可以动态地加载到系统中运行的内核中,也可以动态地从其中(运行的内核)进行卸载。
那么我们就来对其进行加载以下吧,对于将 .ko 文件加载到内核的命令是 &insmod ,
而将 .ko 文件从内核中卸载的命令是 rmmod ,不过加载和卸载的顺序是有一定要求的。 由前面的代码实现可知,
hello.c 中调用了 myFunc.c 中实现的函数,那么在编译之后所生成的 hello.ko 文件中一定会依赖 myFunc.ko 。
所以,应该首先加载 myFunc.ko , 然后在加载 hello.ko 才可以得到正确的输出。
同样,如何知道某一个内核模块 *.ko 已经被内核所加载了呢? 查询的命令有很多种,其中比较常用的有
1. ps -ef | grep myFunc
2. cat &/proc/kallsyms | grep myFunc
3. lsmod | grep myFunc
任何一种方法都可以, 因为我们并没有在 myFunc 中写入在运行的时候输出的提示信息,所以需要通过
系统命令来查看它是否在运行,以及运行状态相关的信息。
接下来,我们使用同样的方法,在 myFunc 被加载到内核中的基础之上,将 hello.ko 加载到内核中
通过再次输入命令 lsmod | grep myFunc 可以看到 hello 这个模块已经类似于挂载的关系依附于其之上,这也是为什么
在将 myFunc hello 内核模块从内核中卸载下来的时候,需要首先将 hello 模块卸载下来,然后在卸载 myFunc 的原因。
实验不足与改进:
这个实验中有一个地方和我预先想到的输出有所不同,在 hello 中打印信息的时候多加了一个换行符号
\n 没有想到信息竟然没有一次性全部输出,这个可能与 printk 函数的属性有关,也就是说 作为 printf
的兄弟的printk 在每次向屏幕输出的时候,是需要向内核请求一个缓冲区来存放将要输出的内容的,
这样长长的一句话可能由于分配的缓冲区的不同而被多次输出,而内核或许将 \n 这一换行符号,默许是
分配缓冲区结束的一个标志,这样后半句话就会在前一句输出之后,清空缓冲区,然后装载后一句话,
然后将后一句话输出,这才导致了,换行符号的后半句话在将 hello 模块从内核中卸载下来之后才显示&
到屏幕上面的原因吧。(经过后续的实验证实,这个想法是错误的)
不过具体的原因还不是很清楚,但是之所以知道它是错的是因为:在执行 insmod *.ko&
模块加载的时候,所加载的仅仅是零零散散的 目标文件,而非最终我们想要实现全局功能的hello.o&
真正最终起到全局功能的就是 hello.o , 也就是通过 ismod hello.o 可以将全部的信息(无论是否有 \n 等等)
不过它的运行之前,是必须要通过 ismod myFunc.ko &ismod hello.ko 这些内核加载命令的基础之上才能够正确运行的。
printk 中还需要知道一点就是,在输出的字符串信息前面中是需要加上各种内核中定义的宏变量所代表输出信息的不同等级的,
如果等级过低则不会输出,那么在控制台上看不到写入方法中本应显示的信息的。 这里我们使用的是最高等级,
紧急状态 : KERN_EMERG&就可以在屏幕上看到写入程序中的字符了。
后续实验主要是围绕 list hlist&
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:35673次
排名:千里之外
原创:60篇
(3)(14)(4)(5)(22)(9)(1)(3)}

我要回帖

更多关于 linux内核全局变量 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信