其中的原因有很多种。其一,C标准库的很多函数实现都是基于内核实现的,这内核编译的时候都还没有内核,所以就不存在这些函数,这个就是先有鸡还是先有蛋这个悖论。其二,其主主要的的原因是熟读和大小。对于内核来说,完整的C库–哪怕是它的一个子集,都太大且太低效了。
不过大部分的C库函数在内核中都已经得到实现。其实现源码在lib/***.c,对应的头文件在include/linux/文件夹中,只需要包含
Linux内核都是用C语言编写的。但是内核不完全符合ANSI C标准。它是ANSI C的扩展,包含一些GNU C独有的特性。具体的区别可以参考 https://blog.csdn.net/lbit20131014/article/details/81108096
开发者采用gcc编译内核同时也可以用gcc编译系统应用程序(gcc是多种GNU编译器的集合,即可以编译内核,也可以编译Linux系统上用C写的其他代码)
如果用户程序试图进行一次非法的内存访问,内核就会发现这个错误,发送SIGSEGV信号,并结束整个进程;这个内存保护机制是内核来实现,而在内核编写的时候如果出现非法内存访问那就很难控制了,所以设计的时候一定哟注意内存错误的问题
内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每一个进程都有两个栈,一个用户栈,存在于用户空间;一个内核栈,存在于内核空间。当进程在用户空间运行时,CPU堆栈指针寄存器里面的内容都是用户栈地址,使用用户栈;当进程在内核空间时,CPU堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。
当进程因为中断或者系统调用陷入到内核态时,进程所使用的堆栈也要从用户栈转到内核栈。进程陷入到内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之后时,在内核态之后的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了用户栈和内核栈的互转。
那么,知道从内核转到用户态时,用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,如何知道内核栈的地址?关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为当进程在用户态运行时,使用的用户栈,当进程陷入到内核态时,内核保存进程在内核态运行的相关信息,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。
用户态程序栈的大小可以是比较大的,同时还可以动态地增长,而内核的堆栈大小都是固定的,其准确大小随着体系结构而变化,以下是网络引用。
Linux内核栈溢出(stack overflow)问题
最近一段时间在设计和开发一个Linux内核模块,进入到最后的正确性测试与稳定性测试阶段。在这个阶段发现了一个非常有意思的问题,堆栈溢出(stack overflow)。Linux内核堆栈溢出之后直接导致了系统kernel Panic。由于导致stack overflow的原因是递归调用导致的,所以,最后通过调试串口导出的kernel panic信息很快就定位问题所在了,否则这样的问题还真是很难调试和发现。通过这次bug,我们应该记住的是:Linux内核stack资源是有限的,而递归调用将大量消耗stack资源,因此在内核编程中尽量少用递归算法,否则将会导致出乎意料的一些问题。依次类推,为了减少stack资源的消耗,程序的局部变量定义的不要太大,否则也将会消耗大量stack资源,从而导致内核程序的不稳定。
为了解决递归调用导致的问题,我将递归算法改写成了非递归算法,解决了stack overflow的问题。在此介绍一下递归算法改写成非递归算法的一些思想。在项目实现过程中,需要对IO请求进行按顺序排队,因此采用了效率较高并且实现简单的快速排序算法,该算法是一种分治算法,即将排序队列进行切分,分解成一系列的小问题进行求解,针对这种问题,很容易采用递归的办法进行实现,伪代码描述如下:
/* qs_sort实现从小到大的排序 */
Struct bio qs_sort(struct bio_list *list_head, struct bio *bio_tail) {
Struct bio_list *less_list, *large_list;
Struct bio *middle_bio;
/* 递归调用结束点,小问题求解完毕,直接返回最后一个元素 */
If (!list_head) {
Return bio_tail;
}
/* 对队列进行切分,选择一个middle_bio,并且按照middle_bio将其切分成less_list队列和large_list队列 */
Split_list(list_head, less_list, large_list, &middle_bio);
/* 采用递归的方法实现大队列的排序操作 */
Middle_bio->bi_next = qs_sort(large_list, bio_tail);
/* 采用递归的方法实现小队列的排序操作 */
Return qs_sort(less_list, middle_bio);
}
有上述可见,采用函数递归的方法实现简单,但是将会牺牲(栈)存储空间,为此,需要将其改写成非递归的实现方法,非递归的实现算法可以点击此处下载(,欢迎提出意见)。改写的思想是将递归所采用的存储栈空间动态分配。递归算法本质上利用堆栈存储了切分的小问题,因此,可以采用系统内存动态分配存储空间,自己维护小问题堆栈。那么可以做到不利用函数堆栈空间,避免了栈空间的大量消耗。
总之,在内核程序实现过程中,一定要注意栈空间的使用,特别像递归这样的方法尽量少用,否则将可能会对产品产生致命的打击。
from:http://hi.baidu.com/mffppwbneqdmnqe/item/8761040489cddfd6dde5b098
内核很容易产生竞争条件,内核的许多特性要求能够并发地访问共享数据,这就要求内核有同步机制保证不出现竞争的条件。以下是应用网络博客。
造成并发执行的原因
中断,软中断和tasklet: 中断和进程, 中断和中断之间有可能会引起并发问题.
内核抢占: 一个线程会被另一个线程抢占, 所以线程和线程之间也有同步问题.
睡眠: 线程主动性睡眠也会引起同步问题.
对称多处理(SMP): 多个处理器同时执行一套代码就有问题.
从上面我们可以看出来, 如果不需要支持SMP的话, 我们只需要关闭中断或者抢占就可以解决并发问题(关闭中断之后, 被动的抢占不会发生, 只有线程主动调用某些API才会触发进程调度). 当SMP出现之后(linux2.0时代就已经出现了), 我们就必须使用自旋锁来解决同步问题了.
造成死锁的原因
递归引起的死锁: linux下的spin_lock是不支持递归的, 所以同一个线程不能多次获取同一个锁.
ABBA引起的死锁: 多个线程获取锁的顺序不一致引起的死锁. 解决方法是大家都按相同的顺序去获取锁.
Linux内核中的同步方法
自旋锁: 可以在中断上下文中使用. (不会引起死锁, 如果线程和中断处理函数共用了一个锁的话, 那么当线程获取自旋锁之后是会明确的关中断的. Spin_lock_irqsave)
信号量: 会引起睡眠, 所以不能在中断上下文中使用.
互斥体: 很容易理解, 这里需要注意的是互斥体只能被同一个线程获取和释放.
读-写自旋锁: 适用于读者和读操作很多, 但是写者和写操作很少的情况. 需要注意的是, 如果读者过多的话, 会造成写者饥饿.
读-写信号量: 基本同读写自旋锁, 然后像信号量一样会引起睡眠.
完成变量: 一种简单的异步通知机制, 通过名字就很容易理解.
顺序锁: 顺序锁与读写自旋锁很类似, 只是该锁的写会优先于读, 也就是写者不会让读者饥饿.
Linux提供了这么多种不同的方式来做同步, 就是为了在不同的应用场景中找到性能和效率的平衡点. 我们要权衡CPU占用和进程调度带来的开销, 在不同的场合下选择不同的加锁机制.
---------------------
作者:shinezhang86
来源:CSDN
原文:https://blog.csdn.net/shinezhang86/article/details/48292565
因为Linux是一个可移植的系统,大部分代码与体系结构无关,同时,作为一个专业的编程者来说,应该要注重系统的可移植性