目录
写在上面
之前做项目的时侯,有高手告诉自己,要去学一下Linux内核,对好多方面都有帮助,如今闲出来,来花时间学一下这一部份的知识点,也算是一个学习笔记
目前跟随B站UP主——简说linux的教程《Linux内核开发100讲》学习,链接如下:
简说linux个人空间
本章学习参考链接:
printk和printf的区别
《Linux内核设计与实现》
在学习的过程中,我也会对遇见的各类问题进行深一步学习,进而总结知识点到博客当中,这都会出现内容可能会到处跳跃,并且这些跳跃符合我的学习过程。
整体环境
为了学习代码,我们须要一个一套Linux环境,由于为了便捷自己记笔记和学习,没有用双系统,直接在windows10下边用VMware建了一个虚拟机进行试验。
开发环境:VMWare虚拟机Ubuntu18.04
Linux源码版本:linux4.9.229
学习笔记
这一章是关于Linux内核的一个总体印象,以及应用层和驱动层之间互相调用的逻辑关系。
操作系统和内核简介
在《Linux内核设计与实现》中觉得,操作系统是指在整个系统中负责完成最基本功能和系统管理的这些部份。这种部份应当包括内核、设备驱动程序、启动引导程序、命令行Shell或则其他种类的用户界面、基本的文件管理工具和系统工具。
而内核时操作系统的核心所在,系统的其他部份都必须借助内核这部份软件提供的服务,例如管理硬件设备、分配系统资源等等。
对于提供保护机制的现代系统来说,内核独立于普通应用程序,通常处于系统态,拥有受保护的显存空间和访问硬件设备的所有权限,这些系统态被保护上去的显存空间,也称为内核空间。
而与这个相对的是应用程序在用户空间执行。只能看见容许它们使用的部份系统资源,不能直接访问硬件,也不能访问其他人的显存范围。执行一个应用程序的时侯,系统将以用户态步入用户空间执行。
系统中运行的应用程序通过系统调用来实现与内核通讯。具体的图如下:
应用程序一般调用库函数(例如C库函数)再由库函数通过系统调用界面,让内核代其完成各类不同任务。一些库调用提供了系统调用不具备的许多功能。
printf()和prinfk()
在Linux内核学习(二)上面,我们就在我们自己的设备驱动上面用了printk()函数来进行信息的输出因而进行调试,但为何不用C语言上面的printf函数呢?
由于大部份的C语言库上面的函数在内核中都得到了实现,而且printf()函数是并没有被实现的,因而在我们上面的内核驱动设备的C语言代码中,我们是难以调用printf()函数的,并且没有了这个函数,内核中使用了另一个函数,printk()
记录等级描述记录等级
KERN_EMEG
一个紧急情况
KERN_ALERT
一个须要立刻被注意到的错误
KERN_CRIT
一个临界情况
KERN_ERR
一个错误
KERN_WARNING
一个警告
KERN_NOTICE
一个普通的,不过也有可能须要被注意的情况
KERN_INFO
一条非即将的消息
KERN_DEBUG
一条调试信息——一般是冗余信息
假如没有非常指定一个记录等级,函数会选用默认的DEFAULT_MESSAGE_LOGLEVEL,默认等级是KERN_WARNING,内核上面最重要的记录等级是KERN_EMEG,根据表格从上往下对应从重要到不重要的记录等级,最无关紧要的是KERN_DEBUG。当记录等级高于默认等级的时侯,不会在终端上面显示,而显示在日志上面
sudodmesg查看日志信息
sudodmesg-C消除日志信息
我们可以查看当前的记录等级
cat /proc/sys/kernel/printk
我们试着输入一下:返回了4417
cat /proc/sys/kernel/printk
4 4 1 7
输出结果中的四个数字分别代表当前记录等级,默认等级,最杂记录等级,和最大记录等级。
由上面获知,高于默认等级的时侯,不会在终端显示,而在日志显示,因而等级0-3会输出到终端,4-7只会显示在日志当中.
我们可以使用sudoecho"6">/proc/sys/kernel/printk来改变系统的默认等级。注:此改变方式,可能须要使用sudosu切换到root用户才可以更改。
应用层对内核的调用
在我后面写的Linux内核学习(二)上面,我们早已实现了一个设备驱动的插入,但是其设备具有三个操作,open,write,read。那当我们有了一个设备以后linux 应用,我们就可以在我们应用层通过系统调用的插口调用设备驱动,因而实现对硬件设备的调用。其实,因为我们是没有具体的硬件的,所以我们省略最后一步设备驱动对硬件设备的调用,只了解应用层假如实现对内核上面的设备驱动的调用
从反例看原理:应用层的write()怎样调用内核中的write()调用过程实践
要实现这个反例,我们其实首先要手写一个应用层的程序test.c,随意放在哪linux 应用,我把学习过程中的笔记根据注释写在代码中,具体如下:
#include
#include
#include
#include
#define DATA_NUM (64)
int main(int argc, char *argv[])
{
int fd,i;
int r_len,w_len;
fd_set fdset;
char buf[DATA_NUM]="hello world"; //创建一个字符缓冲数组,以便后续使用
memset(buf,0,DATA_NUM); //将DATA_NUM中剩余的值填充到buf中
fd = open("/dev/hello", O_RDWR); //用可读写的权限打开设备驱动hello
printf("%drn", fd);
if(-1 == fd) //判断文件是否打开
{
perror("open file errorrn");
return -1;
}
else
{
printf("open successern");
}
w_len = write(fd, buf, DATA_NUM); //打开成功 就调用write和read
r_len = read(fd, buf, DATA_NUM);
printf("%d%drn", w_len, r_len); //将返回值打印出来
printf("%srn",buf);
return 0;
}
之后我们来运行这个运行这个程序
我们先根据最基本的C语言的流程,编译它。
gcc -o test test.c
之后我们运行它
./test
但我们发觉它未能打开文件,返回以下错误
-1
open file error
: No such file or directory
其缘由是,尽管我们在内核上面注册了我们的内核驱动,并且我们在应用层上面没有构建这样的一个文设备件。即使在现今的Linux内核可以手动生成这样的设备文件,但UP主给我们演示了具体的实现过程:
首先,我们须要创建一个设备文件,须要使用mknod命令,其用法如下:
mknod[OPTION]NAMETYPE[MAJORMINOR]
TYPE是设备的类型,MAJOR和MINOR指的是主设备号和次设备号
mknod /dev/hello c 232 0
# 由于我们应用层中写的打开设备时hello,所以这里名字和代码中的文件名一样
# c 代表着这是一个字符设备
# 232 0 是我们上一节写的内核注册的驱动文件中的主次设备号
ls -l /dev/hello
# 此时再用ls命令就可以看到返回的设备文件啦
之后我们再清空一下我们的日志,并执行测试程序linux,
sudo dmesg -C
./test
发觉返回了以下数据
这样,我们就运行完了了我们应用层的软件代码了,上述的过程中,我们是调用了我们上面写的驱动来进行的,而在我们前一节的笔记中,正好在内核的驱动文件中,有三个对应应用层软件代码中open,write,read的函数,并对其进行了指向具体。代码如下:
int hello_open(struct inode *p, struct file *f)
{
printk(KERN_EMERG"hello_openrn");
return 0;
}
ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_writern");
return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{
printk(KERN_EMERG"hello_readrn");
return 0;
}
ps:函数里面的loff_t是一个类型的声明,其本质就是一个long long类型
这个时侯,我们再使用dmesg查看日志的时侯,发觉这个应用层确实调用了驱动层中的驱动设备,并输出了对应的日志内容,这么具体是如何实现的呢?
实现原理
因为用户空间的程序是难以直接执行内核代码。它们不能直接调用内核空间中的函数,由于内核留驻在受保护的地址空间。假如进程可以直接在内核的地址空间上读写的话,系统的安全系和稳定性将不复存在
为此linux学习论坛,应用程序须要通过某种形式通知系统,告诉内核自己须要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序在内核空间执行系统调用。
通知内核的机制是靠软中断来实现的,通过引起一个异常来促进系统切换到内核态去执行异常处理程序。这个异常处理程序实质上就是一个系统调用处理程序。处理的程序就称作system_call()
具体的过程用图片表示如下:
以之前的应用层的write()为例,具体的过程如下:
首先应用层的代码执行以后,形成一个中断,之后被系统调用程序进行处理。具体的就是一个SYSCALL_DEFINE3(write,unsignedint,fd,constchar__user*,buf,size_t,count)这样的一个函数
之后这个调用会将其调用到ssize_t__vfs_write(structfile*file,constchar__user*p,size_tcount,loff_t*pos)的函数,就是在这个函数中,将我们设备驱动中的read,open,write三个函数给调用了。
最终逐层传递回参数给用户空间,因而实现了整个过程
学习笔记
在上述的test.c文件中,有以下几个须要注意的点
文章评论