kernel代码分析
bootload、kernel、rootfs
嵌入式Linux系统的构建一、嵌入式Linux系统中的典型分区结构Root filesystemKernel二、各个结构的分析1、从咱们所学的硬件知识能够明白,在系统上电后需要一段程序来进行初始化(关闭WATCHDOG、改变系统时钟、初始化存储器操纵器、将更多的代码复制到内存中)。
简单的说bootload确实是这么一段小程序(相当于PC机中的BIOS),初始化硬件设备、预备好软件环境,最后挪用操作系统内核。
从某个观点上来看Bootload能够分为两种操作模式:启动模式和下载模式。
启动模式:上电后bootload从板子上的某个固态存储器上将操作系统加载到RAM中运行,整个进程并无效户的介入下载模式:在这种模式下,开发人员能够利用各类命令,通过串口连接或网络连接等通信手腕从主机下载文件,将它们直接放在内存运行或是烧入Flash类固态存储设备中。
Bootload能够分为两个时期:第一时期实现的功能:硬件设备初始化、为加载Bootload的第二时期代码预备RAM空间、复制Bootload的第二时期代码到RAM空间中、设置好栈、跳转到第二时期代码的C入口点第二时期:初始化本时期要利用的硬件设备、检测系统内存映射、将内核镜像和根文件映像从Flash上读到RAM空间中、为内核设置启动参数、挪用内核2、内核的结构:Linux内核文件数量快要2万,除去其他构架CPU的相关文件,支持S3C2410、S3C2440这两款芯片的完整内核文件有1万多个。
这些文件组织结构并非复杂,他们别离位于顶层目录下的17个子目录,各个目录功能独立Linu内核Makefile文件分类3、根文件系统嵌入式Linux 中都需要构建根文件系统,构建根文件系统的规那么在FHS(FilesystemHierarchy Standard)文档中,下面是根文件系统顶层目录。
三、根文件系统的制作一、进入到/opt/studyarm 目录,新建成立根文件系统目录的脚本文create_rootfs_bash,利用命令chmod +x create_rootfs_bash 改变文件的可执行限,./create_rootfs_bash 运行脚本,就完成了根文件系统目录的创建。
Linux 内核2.4版源代码分析大全
4.4.4 如何使传统管理方式依然有效
4.4.5 内核实现综述
4.4.6 核心结构与变量
4.4.7 devfs节点注册函数
4.4.8 编写采用devfs的设备驱动程序
4,5 块设备的请求队列
4.5.1 相关结构及请求队列的初始化
4.6.1 构造ioctl命令字
4.6.2 ioctl的实现过程
4.6.3 ioctl的上层处理函数
4.6.4 ioctl的底层处理函数
4.7 I/O端口的资源分配与操作
4.7.1 I/O端口概述
4.7.2 Linux系统中的I/O空间分配
4.7.3 端口操作函数
4.9.4 设备的使用
4.9.5 驱动程序编写实例
4.10 块设备驱动程序的实现
4.10.1 设备功能
4.10.2 编写块设备的函数接口fops
4.10.3 设备接口注册与初始化
第5章 Linux系统初始化
5.1 系统引导
1,13 系统调用
1.13.1 与系统调用有关的数据结构和
函数
1.13.2 进程的系统调用命令是如何转换为
INT0x80中断请求的
1.13.3 系统调用功能模块的初始化
1.13.4 Linux内部是如何分别为各种系统
调用服务的
4.1.2 与外设的数据交流方
4.1.3 字符设备与块设备
4.1.4 主设备号和次设备号
4.1.5 本章内容分配
4.2 设备文件
4.2.1 基本设备文件的设备访问流程
4.2.2 设备驱动程序接口
4.2.3 块设备文件接口
linux下的stdin,stdout,stderr
用户操作Linux 下stdin stdout stderr 的由来收藏现在就从linux kernel的源代码的角度来分析该。
二:fork()与execve()中stderr,stdio.stdout的继承关系其实用继承这个词好像不太准确,要准确一点,可能复制更适合.首先有二点:1:父进程fork出子进程后,是共享所有文件描述符的(实际上也包括socket)2:进程在execve后,除了用O_CLOEXEC标志打开的文件外,其它的文件描述符都是会复制到下个执行序列(注意这里不会产生一个新进程,只是将旧的进程替换了)下面我们从代码中找依据来论证以上的两个观点.对于第一点:我们在分析进程创建的时候,已经说过,如果父过程在创建子进程的时候带了CLONE_FILE S标志的时候,会和父进程共享task->files.如果没有定义,就会复制父进程的task->files.无论是哪种情况,父子进程的环境都是相同的.代码如下:static int copy_files(unsigned long clone_flags, struct task_struct * tsk) {struct files_struct *oldf, *newf;int error = 0;oldf = current->files;if (!oldf)goto out;if (clone_flags & CLONE_FILES) {atomic_inc(&oldf->count);goto out;}tsk->files = NULL;newf = dup_fd(oldf, &error);if (!newf)goto out;tsk->files = newf;error = 0;out:return error;}从上面的代码可以看出.如果带CLONE_FILES标志,只是会增加它的引用计数.否则,打开的文件描符述会全部复制.对于二:我们之前同样也分析过sys_execve().如果有不太熟悉的,到本站找到相关文章进行阅读.在这里不再详细说明整个流程.相关代码如下:static void flush_old_files(struct files_struct * files){long j = -1;struct fdtable *fdt;spin_lock(&files->file_lock);for (;;) {unsigned long set, i;j++;i = j * __NFDBITS;fdt = files_fdtable(files);if (i >= fdt->max_fds)break;set = fdt->close_on_exec->fds_bits[j];if (!set)continue;fdt->close_on_exec->fds_bits[j] = 0;spin_unlock(&files->file_lock);for ( ; set ; i++,set >>= 1) {if (set & 1) {sys_close(i);}}spin_lock(&files->file_lock);}spin_unlock(&files->file_lock);}该函数会将刷新旧环境的文件描述符信息.如果该文件描述符在fdt->close_on_exec被置位,就将其关闭.然后,我们来跟踪一下,在什么样的情况下,才会将fdt->close_on_exec的相关位置1. 在sys_open() àget_unused_fd_flags():int get_unused_fd_flags(int flags){………….if (flags & O_CLOEXEC)FD_SET(fd, fdt->close_on_exec);elseFD_CLR(fd, fdt->close_on_exec);……}只有在带O_CLOEXEC打开的文件描述符,才会在execve()中被关闭.三:用户空间的stderr,stdio.stdout初始化论证完上面的二个观点之后,后面的就很容易分析了.我们先来分析一下,在用户空间中,prin tf是可以使用的.哪它的stderr,stdio.stdout到底是从哪点来的呢?我们知道,用户空间的所有进程都是从init进程fork出来的.因此,它都是继承了init进程的相关文件描述符.因此,问题都落在,init进程的stderr,stdio.stdout是在何时被设置的?首先,我们来看一下内核中的第一个进程.它所代码的task_struct结构如下所示:#define INIT_TASK(tsk){.state = 0,.stack = &init_thread_info,.usage = ATOMIC_INIT(2),.flags = 0,.lock_depth = -1,.prio = MAX_PRIO-20,.static_prio = MAX_PRIO-20,.normal_prio = MAX_PRIO-20,.policy = SCHED_NORMAL,.cpus_allowed = CPU_MASK_ALL,……..files = &init_files,……}它所有的文件描述符信息都是在init_files中的,定义如下:static struct files_struct init_files = INIT_FILES;#define INIT_FILES{.count = ATOMIC_INIT(1),.fdt = &init_files.fdtab,.fdtab = INIT_FDTABLE,.file_lock = __SPIN_LOCK_UNLOCKED(init_task.file_lock),.next_fd = 0,.close_on_exec_init = { { 0, } },.open_fds_init = { { 0, } },.fd_array = { NULL, }}我们从这里可以看到,内核的第一进程是没有带打开文件信息的.我们来看一下用户空间的init进程的创建过程:Start_kernel() -àrest_init()中代码片段如下:static void noinline __init_refok rest_init(void)__releases(kernel_lock){int pid;kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);kthreadd_task = find_task_by_pid(pid);unlock_kernel();/** The boot idle thread must execute schedule()* at least once to get things moving:*/init_idle_bootup_task(current);preempt_enable_no_resched();schedule();preempt_disable();/* Call into cpu_idle with preempt disabled */cpu_idle();}该函数创建了两个进程,然后本进程将做为idle进程在轮转.在创建kernel_init进程的时候,带的参数是CLONE_FS | CLONE_SIGHAND.它没有携带CLONE_FILES标志.也就是说,kernel_init中的文件描述符信息是从内核第一进程中复制过去的.并不和它共享.以后,kernel_init进程中,任何关于files的打开,都不会影响到父进程.然后在kernel_init() àinit_post()中有:static int noinline init_post(void){ …………if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)printk(KERN_WARNING "Warning: unable to open an initial co nsole.\n");(void) sys_dup(0);(void) sys_dup(0);…………run_init_process(XXXX);}从上面的代码中可以看到,它先open了/dev/console.在open的时候,会去找进程没使用的最小文件序号.而,当前进程没有打开任何文件,所以sys_open()的时候肯定会找到0.然后,两次调用sys_dup(0)来复制文件描述符0.复制后的文件找述符肯定是1.2.这样,0.1.2就建立起来了.然后这个进程调用run_init_process() àkernel_execve()将当前进程替换成了用户空间的一个进程,这也就是用户空间init进程的由来.此后,用户空间的进程全是它的子孙进程.也就共享了这个0.1.2的文件描述符了.这也就是我们所说的stderr.stdio,stdout.从用户空间写个程序测试一下:#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>main(){int ret;char *ttyname0,*ttyname1,*ttyname2;ttyname0 = ttyname(0);ttyname1 = ttyname(1);ttyname2 = ttyname(2);printf(“file0 : %s\n”,ttyname0);printf(“file1 : %s\n”,ttyname1);printf(“file2 : %s\n”,ttyname2);return;}运行这个程序,我们会看到,0,1,2描述符的信息全为/dev/consle.四:内核创建用户空间进程的过程在内核中创建用户空间进程的相应接口为call_usermodehelper().实现上,它将要创建的进程信息链入一个工作队列中,然后由工作队列处理函数调用kernel _thread()创建一个子进程,然后在这个进程里调用kernel_execve()来创建用户空间进程.在这里要注意工作队列和下半部机制的差别.工作队列是利用一个内核进程来完成工作的,它和下半部无关.也就是说,它并不在一个中断环境中.那就是说,这样创建出来的进程,其实就是内核环境,它没有打开0,1.2的文件描述符.可能也有人会这么说:那我就不在内核环境下创建用户进程不就行了?例如,我在init_module的时候,创建一个内核线程,然后在这个内核线程里,kernel_execv e()一个用户空间进程不就可以了吗?的确,在这样的情况下,创建的进程不是一个内核环境,因为在调用init_module()的时候,已经通过系统调用进入kernel,这时的环境是对应用户进程环境.但是别忘了.在系统调用环境下,再进行系统调用是不会成功的(kernel_execve也对应一个系统调用.)举例印证如下:Mdoule代码:#include <linux/ioport.h>#include <linux/interrupt.h>#include <asm/io.h>#include <linux/serial_core.h>#include <linux/kmod.h>#include <linux/file.h>#include <linux/unistd.h>MODULE_LICENSE("GPL");MODULE_AUTHOR( "ericxiao:xgr178@" );static int exeuser_init(){int ret;char *argv[] ={"/mnt/hgfs/vm_share/user_test/main",NULL,};char *env[] ={"HOME=/","PATH=/sbin:/bin:/usr/sbin:/usr/bin",NULL,};printk("exeuser_init ...\n");ret = call_usermodehelper(argv[0], argv, env,UMH_WAIT_EXEC);return 0;}static int exeuser_exit(){printk("exeuser_exit ...\n");return 0;}module_init(exeuser_init);module_exit(exeuser_exit);用户空间程序代码:#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(int argc,char *argv[],char *env[]){int i;int fd;int size;char *tty;FILE *confd;char printfmt[4012];system("echo i am coming > /var/console");for(i=0; env[i]!=NULL;i++){sprintf(printfmt,"echo env[%d]:%s. >>/var/console",i,env[i]);system(printfmt);}for(i=0; i<argc ;i++){sprintf(printfmt,"echo arg[%d]:%s. >>/var/console",i,argv[i]);system(printfmt);}tty = ttyname(0);if(tty == NULL)system("echo tty0 is NULL >> /var/console");else{sprintf(printfmt,"echo ttyname0 %s. >>/var/console",tty);system(printfmt);}tty = ttyname(1);if(tty == NULL)system("echo tty1 is NULL >> /var/console");else{sprintf(printfmt,"echo ttyname1 %s. >>/var/console",tty);system(printfmt);}tty = ttyname(2);if(tty == NULL)system("echo tty2 is NULL >> /var/console");else{sprintf(printfmt,"echo ttyname2 %s. >>/var/console",tty);system(printfmt);}tty = ttyname(fd);if(tty == NULL)system("echo fd is NULL >> /var/console");else{sprintf(printfmt,"echo fd %s. >>/var/console",tty);system(printfmt);}return 0;}插入模块过后,调用用户空间的程序,然后这个程序将进程环境输出到/var/console中,完了可以看到.这个进程输出的0,1,2描述符信息全部NULL.千万要注意,在测试的用户空间程序,不能打开文件.这样会破坏该进程的原始文件描述符环境(因为这个问题.狠调了一个晚上,汗颜…).这样.用户空间的printf当然就不能打印出东西了.ps:这位老兄的帖子解了我的一些疑惑。
Linuxkernel有关spi设备树参数解析
Linuxkernel有关spi设备树参数解析⼀、最近做了⼀个 spi 设备驱动从板级设备驱动升级到设备树设备驱动,这其中要了解 spi 设备树代码的解析。
⼆、设备树配置如下:503 &spi0 {504 status = "okay";505 pinctrl-name = "default";506 pinctrl-0 = <&spi0_pins>;507 ti,pindir-d0-out-d1-in;508509 wk2124A {510 compatible = "wk2124A"; // 匹配字符串511 reg = <0>; // cs512 # spi-cpha = <1>; // 配置 spi 的模式513 # spi-tx-bus-width = <1>; // 这是是 spi-tx 的总线宽度514 # spi-rx-bus-width = <1>;515 spi-max-frequency = <10000000>; // spi 最⼤速率配置516 };517 };三、代码跟踪// drivers/spi/spi.c2772 postcore_initcall(spi_init); // spi_init2733 static int __init spi_init(void)2734 {2735 int status;27362737 buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);2738 if (!buf) {2739 status = -ENOMEM;2740 goto err0;2741 }27422743 status = bus_register(&spi_bus_type);2744 if (status < 0)2745 goto err1;27462747 status = class_register(&spi_master_class);2748 if (status < 0)2749 goto err2;27502751 if (IS_ENABLED(CONFIG_OF_DYNAMIC))2752 WARN_ON(of_reconfig_notifier_register(&spi_of_notifier)); // 这⾥要注册主机和从机27532754 return 0;27552756 err2:2757 bus_unregister(&spi_bus_type);2758 err1:2759 kfree(buf);2760 buf = NULL;2761 err0:2762 return status;2763 }2726 static struct notifier_block spi_of_notifier = {2727 .notifier_call = of_spi_notify,2728 };2686 static int of_spi_notify(struct notifier_block *nb, unsigned long action,2687 void *arg)2688 {2689 struct of_reconfig_data *rd = arg;2690 struct spi_master *master;2691 struct spi_device *spi;26922693 switch (of_reconfig_get_state_change(action, arg)) {2694 case OF_RECONFIG_CHANGE_ADD:2695 master = of_find_spi_master_by_node(rd->dn->parent); // 找到主机节点2696 if (master == NULL)2697 return NOTIFY_OK; /* not for us */26982699 spi = of_register_spi_device(master, rd->dn); // ---> 注册设备2700 put_device(&master->dev);27222723 return NOTIFY_OK;2724 }1428 #if defined(CONFIG_OF)1429 static struct spi_device *1430 of_register_spi_device(struct spi_master *master, struct device_node *nc)1431 {1432 struct spi_device *spi;1433 int rc;1434 u32 value;14351436 /* Alloc an spi_device */1437 spi = spi_alloc_device(master);1438 if (!spi) {1439 dev_err(&master->dev, "spi_device alloc error for %s\n",1440 nc->full_name);1441 rc = -ENOMEM;1442 goto err_out;1443 }14441445 /* Select device driver */1446 rc = of_modalias_node(nc, spi->modalias, // 匹配到从机1447 sizeof(spi->modalias));1448 if (rc < 0) {1449 dev_err(&master->dev, "cannot find modalias for %s\n",1450 nc->full_name);1451 goto err_out;1452 }14531454 /* Device address */1455 rc = of_property_read_u32(nc, "reg", &value); // 设备节点 reg 表⽰ cs1456 if (rc) {1457 dev_err(&master->dev, "%s has no valid 'reg' property (%d)\n",1458 nc->full_name, rc);1459 goto err_out;1460 }1461 spi->chip_select = value;14621463 /* Mode (clock phase/polarity/etc.) */ // 选择 spi 的模式1464 if (of_find_property(nc, "spi-cpha", NULL))1465 spi->mode |= SPI_CPHA;1466 if (of_find_property(nc, "spi-cpol", NULL))1467 spi->mode |= SPI_CPOL;1468 if (of_find_property(nc, "spi-cs-high", NULL)) // 选择 spi cs 是⾼有效还是低有效 1469 spi->mode |= SPI_CS_HIGH;1470 if (of_find_property(nc, "spi-3wire", NULL))1471 spi->mode |= SPI_3WIRE;1472 if (of_find_property(nc, "spi-lsb-first", NULL))1473 spi->mode |= SPI_LSB_FIRST;14741475 /* Device DUAL/QUAD mode */ // 选择单线还是双线通道1476 if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) {1477 switch (value) {1478 case 1:1479 break;1480 case 2:1481 spi->mode |= SPI_TX_DUAL;1482 break;1483 case 4:1484 spi->mode |= SPI_TX_QUAD;1485 break;1486 default:1487 dev_warn(&master->dev,1488 "spi-tx-bus-width %d not supported\n",1489 value);1490 break;1491 }1492 }14931494 if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) {1495 switch (value) {1496 case 1:1497 break;1498 case 2:1499 spi->mode |= SPI_RX_DUAL;1500 break;1501 case 4:1502 spi->mode |= SPI_RX_QUAD;1503 break;1504 default:1505 dev_warn(&master->dev,1506 "spi-rx-bus-width %d not supported\n",1508 break;1509 }1510 }15111512 /* Device speed */ // 设备速度配置1513 rc = of_property_read_u32(nc, "spi-max-frequency", &value);1514 if (rc) {1515 dev_err(&master->dev, "%s has no valid 'spi-max-frequency' property (%d)\n", 1516 nc->full_name, rc);1517 goto err_out;1518 }1519 spi->max_speed_hz = value;15201521 /* Store a pointer to the node in the device structure */1522 of_node_get(nc);1523 spi->dev.of_node = nc; // 保存设备结构体15241525 /* Register the new device */1526 rc = spi_add_device(spi);1527 if (rc) {1528 dev_err(&master->dev, "spi_device register error %s\n",1529 nc->full_name);1530 goto err_out;1531 }15321533 return spi;15341535 err_out:1536 spi_dev_put(spi);1537 return ERR_PTR(rc);1538 }。
Linux内核分析及编程
其中基于sparc64平台的Linux用户空间可以运行32位代码,用户空间指针是32位宽的,但内核空间是64位的。
内核中的地址是unsigned long类型,指针大小和long类型相同。
内核提供下列数据类型。
所有类型在头文件<asm/types.h>中声明,这个文件又被头文件<Linux/types.h>所包含。
下面是include/asm/types.h文件。
#ifndef _I386_TYPES_H#define _I386_TYPES_H#ifndef __ASSEMBLY__typedef unsigned short umode_t;// 下面__xx类型不会损害POSIX 名字空间,在头文件使用它们,可以输出给用户空间typedef __signed__ char __s8;typedef unsigned char __u8;typedef __signed__ short __s16;typedef unsigned short __u16;typedef __signed__ int __s32;typedef unsigned int __u32;#if defined(__GNUC__) && !defined(__STRICT_ANSI__)typedef __signed__ long long __s64;typedef unsigned long long __u64;#endif#endif /* __ASSEMBLY__ *///下面的类型只用在内核中,否则会产生名字空间崩溃#ifdef __KERNEL__#define BITS_PER_LONG 32#ifndef __ASSEMBLY__#include <Linux/config.h>typedef signed char s8;typedef unsigned char u8;typedef signed short s16;typedef unsigned short u16;typedef signed int s32;typedef unsigned int u32;typedef signed long long s64;typedef unsigned long long u64;/* DMA addresses come in generic and 64-bit flavours. */ #ifdef CONFIG_HIGHMEM64Gtypedef u64 dma_addr_t;#elsetypedef u32 dma_addr_t;#endiftypedef u64 dma64_addr_t;#ifdef CONFIG_LBDtypedef u64 sector_t;#define HAVE_SECTOR_T#endiftypedef unsigned short kmem_bufctl_t;#endif /* __ASSEMBLY__ */#endif /* __KERNEL__ */#endif下面是Linux/types.h的部分定义。
kernel启动代码分析
arm linux kernel 从入口到start_kernel 的代码分析(二)3. 创建页表通过前面的两步,我们已经确定了processor type 和machine type.此时,一些特定寄存器的值如下所示:r8 = machine info (struct machine_desc的基地址)r9 = cpu id (通过cp15协处理器获得的cpu id)r10 = procinfo (struct proc_info_list的基地址)创建页表是通过函数__create_page_tables 来实现的.这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(section entry). 每个页表项是32 bits(4 bytes)因而L1主页表占用4096 *4 = 16k的内存空间.对于ARM926,其L1 section entry的格式为可参考arm926EJS TRM):下面我们来分析__create_page_tables 函数:在arch/arm/kernel/head.S 中:00206: .type __create_page_tables, %function00207: __create_page_tables:00208: pgtbl r4 @ page table address 00209:00210: /*00211: * Clear the 16K level 1 swapper page table00212: */00213: mov r0, r400214: mov r3, #000215: add r6, r0, #0x400000216: 1: str r3, [r0], #400217: str r3, [r0], #400218: str r3, [r0], #400219: str r3, [r0], #400220: teq r0, r600221: bne 1b00222:00223: ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags 00224:00225: /*00226: * Create identity mapping for first MB of kernel to00227: * cater for the MMU enable. This identity mapping00228: * will be removed by paging_init(). We use our current program00229: * counter to determine corresponding section base address.00230: */00231: mov r6, pc, lsr #20 @ start of kernel section 00232: orr r3, r7, r6, lsl #20 @ flags + kernel base00233: str r3, [r4, r6, lsl #2] @ identity mapping00234:00235: /*00236: * Now setup the pagetables for our kernel direct00237: * mapped region.00238: */00239: add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel 00240: str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!00241:00242: ldr r6, =(_end - PAGE_OFFSET - 1) @ r6 = number of sections 00243: mov r6, r6, lsr #20 @ needed for kernel minus 1 00244:00245: 1: add r3, r3, #1 << 2000246: str r3, [r0, #4]!00247: subs r6, r6, #100248: bgt 1b00249:00250: /*00251: * Then map first 1MB of ram in case it contains our boot params.00252: */00253: add r0, r4, #PAGE_OFFSET >> 1800254: orr r6, r7, #PHYS_OFFSET00255: str r6, [r0]...00314: mov pc, lr00315: .ltorg206, 207行: 函数声明208行: 通过宏pgtbl 将r4设置成页表的基地址(物理地址)宏pgtbl 在arch/arm/kernel/head.S 中:00042: .macro pgtbl, rd00043: ldr \rd, =(__virt_to_phys(KERNEL_RAM_ADDR - 0x4000))00044: .endm可以看到,页表是位于KERNEL_RAM_ADDR 下面16k 的位置宏__virt_to_phys 是在incude/asm-arm/memory.h 中:00125: #ifndef __virt_to_phys00126: #define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET) 00127: #define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET) 00128: #endif下面从213行- 221行, 是将这16k 的页表清0.213行: r0 = r4, 将页表基地址存在r0中214行: 将r3 置成0215行: r6 = 页表基地址+ 16k, 可以看到这是页表的尾地址216 - 221 行: 循环,从r0 到r6 将这16k页表用0填充.223行: 获得proc_info_list的__cpu_mm_mmu_flags的值,并存储到r7中. (宏PROCINFO_MM_MMUFLAGS是在arch/arm/kernel/asm-offset.c中定义)231行: 通过pc值的高12位(右移20位),得到kernel的section,并存储到r6中.因为当前是通过运行时地址得到的kernel的section,因而是物理地址.232行: r3 = r7 | (r6 << 20); flags + kernel base,得到页表中需要设置的值.233行: 设置页表: mem[r4 + r6 * 4] = r3这里,因为页表的每一项是32 bits(4 bytes),所以要乘以4(<<2).上面这三行,设置了kernel的第一个section(物理地址所在的page entry)的页表项239, 240行: TEXTADDR是内核的起始虚拟地址(0xc0008000), 这两行是设置kernel起始虚拟地址的页表项(注意,这里设置的页表项和上面的231 - 233行设置的页表项是不同的)执行完后,r0指向kernel的第2个section的虚拟地址所在的页表项./* TODO: 这两行的code很奇怪,为什么要先取TEXTADDR的高8位(Bit[31:24])0xff000000,然后再取后面的8位(Bit[23:20])0x00f00000*/242行: 这一行计算kernel镜像的大小(bytes)._end 是在vmlinux.lds.S中162行定义的,标记kernel的结束位置(虚拟地址):00158 .bss : {00159 __bss_start = .; /* BSS */00160 *(.bss)00161 *(COMMON)00162 _end = .;00163 }kernel的size = _end - PAGE_OFFSET -1, 这里减1的原因是因为_end 是location counter,它的地址是kernel镜像后面的一个byte的地址.243行: 地址右移20位,计算出kernel有多少sections,并将结果存到r6中245 - 248行: 这几行用来填充kernel所有section虚拟地址对应的页表项.253行: 将r0设置为RAM第一兆虚拟地址的页表项地址(page entry)254行: r7中存储的是mmu flags, 逻辑或上RAM的起始物理地址,得到RAM第一个MB页表项的值. 255行:设置RAM的第一个MB虚拟地址的页表.上面这三行是用来设置RAM中第一兆虚拟地址的页表. 之所以要设置这个页表项的原因是RAM的第一兆内存中可能存储着boot params.这样,kernel所需要的基本的页表我们都设置完了, 如下图所示:4. 调用平台特定的__cpu_flush 函数当__create_page_tables 返回之后此时,一些特定寄存器的值如下所示:r4 = pgtbl (page table 的物理基地址)r8 = machine info (struct machine_desc的基地址)r9 = cpu id (通过cp15协处理器获得的cpu id)r10 = procinfo (struct proc_info_list的基地址)在我们需要在开启mmu之前,做一些必须的工作:清除ICache, 清除DCache, 清除Writebuffer, 清除TLB等.这些一般是通过cp15协处理器来实现的,并且是平台相关的. 这就是__cpu_flush 需要做的工作.在arch/arm/kernel/head.S中00091: ldr r13, __switch_data @ address to jump to after 00092: @ mmu has been enabled00093: adr lr, __enable_mmu @ return (PIC) address00094: add pc, r10, #PROCINFO_INITFUNC第91行: 将r13设置为__switch_data 的地址第92行: 将lr设置为__enable_mmu 的地址第93行: r10存储的是procinfo的基地址, PROCINFO_INITFUNC是在arch/arm/kernel/asm-offset.c 中107行定义.则该行将pc设为proc_info_list的__cpu_flush 函数的地址, 即下面跳转到该函数.在分析__lookup_processor_type 的时候,我们已经知道,对于ARM926EJS 来说,其__cpu_flush指向的是函数__arm926_setup下面我们来分析函数__arm926_setup在arch/arm/mm/proc-arm926.S 中:00391: .type __arm926_setup, #function00392: __arm926_setup:00393: mov r0, #000394: mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4 00395: mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4 00396: #ifdef CONFIG_MMU00397: mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4 00398: #endif00399:00400:00401: #ifdef CONFIG_CPU_DCACHE_WRITETHROUGH00402: mov r0, #4 @ disable write-back on caches explicitly00403: mcr p15, 7, r0, c15, c0, 000404: #endif00405:00406: adr r5, arm926_crval00407: ldmia r5, {r5, r6}00408: mrc p15, 0, r0, c1, c0 @ get control register v4 00409: bic r0, r0, r500410: orr r0, r0, r600411: #ifdef CONFIG_CPU_CACHE_ROUND_ROBIN00412: orr r0, r0, #0x4000 @ .1.. .... .... ....00413: #endif00414: mov pc, lr00415: .size __arm926_setup, . - __arm926_setup00416:00417: /*00418: * R00419: * .RVI ZFRS BLDP WCAM00420: * .011 0001 ..11 010100421: *00422: */00423: .type arm926_crval, #object00424: arm926_crval:00425: crval clear=0x00007f3f, mmuset=0x00003135, ucset=0x00001134第391, 392行: 是函数声明第393行: 将r0设置为0第394行: 清除(invalidate)Instruction Cache 和Data Cache.第395行: 清除(drain) Write Buffer.第396 - 398行: 如果有配置了MMU,则需要清除(invalidate)Instruction TLB 和Data TLB接下来,是对控制寄存器c1进行配置,请参考ARM926 TRM.第401 - 404行: 如果配置了Data Cache使用writethrough方式, 需要关掉write-back.第406行:取arm926_crval的地址到r5中, arm926_crval 在第424行第407行:这里我们需要看一下424和425行,其中用到了宏crval,crval是在arch/arm/mm/proc-macro.S 中:00053: .macro crval, clear, mmuset, ucset00054: #ifdef CONFIG_MMU00055: .word \clear00056: .word \mmuset00057: #else00058: .word \clear00059: .word \ucset00060: #endif00061: .endm配合425行,我们可以看出,首先在arm926_crval的地址处存放了clear的值,然后接下来的地址存放了mmuset的值(对于配置了MMU的情况)所以,在407行中,我们将clear和mmuset的值分别存到了r5, r6中第408行:获得控制寄存器c1的值第409行: 将r0中的clear (r5) 对应的位都清除掉第410行:设置r0中mmuset (r6) 对应的位第411 - 413行:如果配置了使用round robin方式,需要设置控制寄存器c1的Bit[16]第412行: 取lr的值到pc中.而lr中的值存放的是__enable_mmu 的地址(arch/arm/kernel/head.S 93行),所以,接下来就是跳转到函数__enable_mmu5. 开启mmu开启mmu是又函数__enable_mmu 实现的.在进入__enable_mmu 的时候, r0中已经存放了控制寄存器c1的一些配置(在上一步中进行的设置), 但是并没有真正的打开mmu,在__enable_mmu 中,我们将打开mmu.此时,一些特定寄存器的值如下所示:r0 = c1 parameters (用来配置控制寄存器的参数)r4 = pgtbl (page table 的物理基地址)r8 = machine info (struct machine_desc的基地址)r9 = cpu id (通过cp15协处理器获得的cpu id)r10 = procinfo (struct proc_info_list的基地址)在arch/arm/kernel/head.S 中:00146: .type __enable_mmu, %function00147: __enable_mmu:00148: #ifdef CONFIG_ALIGNMENT_TRAP00149: orr r0, r0, #CR_A00150: #else00151: bic r0, r0, #CR_A00152: #endif00153: #ifdef CONFIG_CPU_DCACHE_DISABLE00154: bic r0, r0, #CR_C00155: #endif00156: #ifdef CONFIG_CPU_BPREDICT_DISABLE00157: bic r0, r0, #CR_Z00158: #endif00159: #ifdef CONFIG_CPU_ICACHE_DISABLE00160: bic r0, r0, #CR_I00161: #endif00162: mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \ 00163: domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ 00164: domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \ 00165: domain_val(DOMAIN_IO, DOMAIN_CLIENT))00166: mcr p15, 0, r5, c3, c0, 0 @ load domain access register 00167: mcr p15, 0, r4, c2, c0, 0 @ load page table pointer 00168: b __turn_mmu_on00169:00170: /*00171: * Enable the MMU. This completely changes the structure of the visible 00172: * memory space. You will not be able to trace execution through this.00173: * If you have an enquiry about this, *please* check the linux-arm-kernel 00174: * mailing list archives BEFORE sending another post to the list.00175: *00176: * r0 = cp#15 control register00177: * r13 = *virtual* address to jump to upon completion00178: *00179: * other registers depend on the function called upon completion00180: */00181: .align 500182: .type __turn_mmu_on, %function00183: __turn_mmu_on:00184: mov r0, r000185: mcr p15, 0, r0, c1, c0, 0 @ write control reg00186: mrc p15, 0, r3, c0, c0, 0 @ read id reg00187: mov r3, r300188: mov r3, r300189: mov pc, r13第146, 147行: 函数声明第148 - 161行: 根据相应的配置,设置r0中的相应的Bit. (r0 将用来配置控制寄存器c1)第162 - 165行: 设置domain 参数r5.(r5 将用来配置domain)第166行: 配置domain (详细信息清参考arm相关手册)第167行: 配置页表在存储器中的位置(set ttb).这里页表的基地址是r4, 通过写cp15的c2寄存器来设置页表基地址.第168行: 跳转到__turn_mmu_on. 从名称我们可以猜到,下面是要真正打开mmu了.(继续向下看,我们会发现,__turn_mmu_on就下当前代码的下方,为什么要跳转一下呢? 这是有原因的. go on)第169 - 180行: 空行和注释. 这里的注释我们可以看到, r0是cp15控制寄存器的内容, r13存储了完成后需要跳转的虚拟地址(因为完成后mmu已经打开了,都是虚拟地址了).第181行: .algin 5 这句是cache line对齐. 我们可以看到下面一行就是__turn_mmu_on, 之所以第182 - 183行: __turn_mmu_on 的函数声明. 这里我们可以看到, __turn_mmu_on 是紧接着上面第168行的跳转指令的,只是中间在第181行多了一个cache line对齐.这么做的原因是: 下面我们要进行真正的打开mmu操作了, 我们要把打开mmu的操作放到一个单独的cache line上. 而在之前的"启动条件"一节我们说了,I Cache是可以打开也可以关闭的,这里这么做的原因是要保证在I Cache打开的时候,打开mmu的操作也能正常执行.第184行: 这是一个空操作,相当于nop. 在arm中,nop操作经常用指令mov rd, rd 来实现.注意: 为什么这里要有一个nop,我思考了很长时间,这里是我的猜测,可能不是正确的:因为之前设置了页表基地址(set ttb),到下一行(185行)打开mmu操作,中间的指令序列是这样的: set ttb(第167行)branch(第168行)nop(第184行)enable mmu(第185行)对于arm的五级流水线: fetch - decode - execute - memory - write他们执行的情况如下图所示:这里需要说明的是,branch操作会在3个cycle中完成,并且会导致重新取指.从这个图我们可以看出来,在enable mmu操作取指的时候, set ttb操作刚好完成.第185行: 写cp15的控制寄存器c1, 这里是打开mmu的操作,同时会打开cache等(根据r0相应的配置)第186行: 读取id寄存器.第187 - 188行: 两个nop.第189行: 取r13到pc中,我们前面已经看到了, r13中存储的是__switch_data (在arch/arm/kernel/head.S 91行),下面会跳到__switch_data.第187,188行的两个nop是非常重要的,因为在185行打开mmu操作之后,要等到3个cycle之后才会生效,这和arm的流水线有关系.因而,在打开mmu操作之后的加了两个nop操作.6. 切换数据在arch/arm/kernel/head-common.S 中:00014: .type __switch_data, %object00015: __switch_data:00016: .long __mmap_switched00017: .long __data_loc @ r400018: .long __data_start @ r500019: .long __bss_start @ r600020: .long _end @ r700021: .long processor_id @ r400022: .long __machine_arch_type @ r500023: .long cr_alignment @ r600024: .long init_thread_union + THREAD_START_SP @ sp00025:00026: /*00027: * The following fragment of code is executed with the MMU on in MMU mode, 00028: * and uses absolute addresses; this is not position independent.00029: *00030: * r0 = cp#15 control register00031: * r1 = machine ID00032: * r9 = processor ID00033: */00034: .type __mmap_switched, %function00035: __mmap_switched:00036: adr r3, __switch_data + 400037:00038: ldmia r3!, {r4, r5, r6, r7}00039: cmp r4, r5 @ Copy data segment if needed 00040: 1: cmpne r5, r600041: ldrne fp, [r4], #400042: strne fp, [r5], #400043: bne 1b00044:00045: mov fp, #0 @ Clear BSS (and zero fp) 00046: 1: cmp r6, r700047: strcc fp, [r6],#400048: bcc 1b00049:00050: ldmia r3, {r4, r5, r6, sp}00051: str r9, [r4] @ Save processor ID00052: str r1, [r5] @ Save machine type00053: bic r4, r0, #CR_A @ Clear 'A' bit00054: stmia r6, {r0, r4} @ Save control register values 00055: b start_kernel第14, 15行: 函数声明第16 - 24行: 定义了一些地址,例如第16行存储的是__mmap_switched 的地址, 第17行存储的是__data_loc 的地址 ......第34, 35行: 函数__mmap_switched第36行: 取__switch_data + 4的地址到r3. 从上文可以看到这个地址就是第17行的地址.第37行:依次取出从第17行到第20行的地址,存储到r4, r5, r6, r7 中. 并且累加r3的值.当执行完后, r3指向了第21行的位置.对照上文,我们可以得知:r4 - __data_locr5 - __data_startr6 - __bss_startr7 - _end这几个符号都是在arch/arm/kernel/vmlinux.lds.S 中定义的变量:00102: #ifdef CONFIG_XIP_KERNEL00103: __data_loc = ALIGN(4); /* location in binary */00104: . = PAGE_OFFSET + TEXT_OFFSET;00105: #else00106: . = ALIGN(THREAD_SIZE);00107: __data_loc = .;00108: #endif00109:00110: .data : AT(__data_loc) {00111: __data_start = .; /* address in memory */00112:00113: /*00114: * first, the init task union, aligned00115: * to an 8192 byte boundary.00116: */00117: *(.init.task)......00158: .bss : {00159: __bss_start = .; /* BSS */ 00160: *(.bss)00161: *(COMMON)00162: _end = .;00163: }对于这四个变量,我们简单的介绍一下:__data_loc 是数据存放的位置__data_start 是数据开始的位置__bss_start 是bss开始的位置_end 是bss结束的位置, 也是内核结束的位置其中对第110行的指令讲解一下: 这里定义了.data 段,后面的AT(__data_loc) 的意思是这部分的内容是在__data_loc中存储的(要注意,储存的位置和链接的位置是可以不相同的).关于AT 详细的信息请参考第38行: 比较__data_loc 和__data_start第39 - 43行: 这几行是判断数据存储的位置和数据的开始的位置是否相等,如果不相等,则需要搬运数据,从__data_loc 将数据搬到__data_start.其中__bss_start 是bss的开始的位置,也标志了data 结束的位置,因而用其作为判断数据是否搬运完成.第45 - 48行:是清除bss 段的内容,将其都置成0. 这里使用_end 来判断bss 的结束位置.第50行: 因为在第38行的时候,r3被更新到指向第21行的位置.因而这里取得r4, r5, r6, sp的值分别是:r4 - processor_idr5 - __machine_arch_typer6 - cr_alignmentsp - init_thread_union + THREAD_START_SPprocessor_id 和__machine_arch_type 这两个变量是在arch/arm/kernel/setup.c 中第62, 63行中定义的.cr_alignment 是在arch/arm/kernel/entry-armv.S 中定义的:00182: .globl cr_alignment00183: .globl cr_no_alignment00184: cr_alignment:00185: .space 400186: cr_no_alignment:00187: .space 4init_thread_union 是init进程的基地址. 在arch/arm/kernel/init_task.c 中:00033: union thread_union init_thread_union00034: __attribute__((__section__(".init.task"))) =00035: { INIT_THREAD_INFO(init_task) };对照vmlnux.lds.S 中的的117行,我们可以知道init task是存放在 .data 段的开始8k, 并且是THREAD_SIZE(8k)对齐的第51行: 将r9中存放的processor id (在arch/arm/kernel/head.S 75行) 赋值给变量processor_id第52行: 将r1中存放的machine id (见"启动条件"一节)赋值给变量__machine_arch_type第53行: 清除r0中的CR_A 位并将值存到r4中. CR_A 是在include/asm-arm/system.h 21行定义, 是cp15控制寄存器c1的Bit[1](alignment fault enable/disable)第54行: 这一行是存储控制寄存器的值.从上面arch/arm/kernel/entry-armv.S 的代码我们可以得知.这一句是将r0存储到了cr_alignment 中,将r4存储到了cr_no_alignment 中.第55行: 最终跳转到start_kernel。
怎样读Linux内核源代码
Linux内核分析方法2010-9-12Linux的最大的好处之一就是它的源码公开。
同时,公开的核心源码也吸引着无数的电脑爱好者和程序员;他们把解读和分析Linux的核心源码作为自己的最大兴趣,把修改Linux 源码和改造Linux系统作为自己对计算机技术追求的最大目标。
Linux内核源码是很具吸引力的,特别是当你弄懂了一个分析了好久都没搞懂的问题;或者是被你修改过了的内核,顺利通过编译,一切运行正常的时候。
那种成就感真是油然而生!而且,对内核的分析,除了出自对技术的狂热追求之外,这种令人生畏的劳动所带来的回报也是非常令人着迷的,这也正是它拥有众多追随者的主要原因:•首先,你可以从中学到很多的计算机的底层知识,如后面将讲到的系统的引导和硬件提供的中断机制等;其它,象虚拟存储的实现机制,多任务机制,系统保护机制等等,这些都是非都源码不能体会的。
等等,这些都是非读源码不能体会的。
•同时,你还将从操作系统的整体结构中,体会整体设计在软件设计中的份量和作用,以及一些宏观设计的方法和技巧:Linux的内核为上层应用提供一个与具体硬件不相关的平台;同时在内核内部,它又把代码分为与体系结构和硬件相关的部分,和可移植的部分;再例如,Linux虽然不是微内核的,但他把大部分的设备驱动处理成相对独立的内核模块,这样减小了内核运行的开销,增强了内核代码的模块独立性。
•而且你还能从对内核源码的分析中,体会到它在解决某个具体细节问题时,方法的巧妙:如后面将分析到了的Linux通过Botoom_half机制来加快系统对中断的处理。
•最重要的是:在源码的分析过程中,你将会被一点一点地、潜移默化地专业化。
一个专业的程序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位置。
他们总是通过定义大量的宏,来增强代码的清晰度和可读性,而又不增加编译后的代码长度和代码的运行效率;他们总是在编码的同时,就考虑到了以后的代码维护和升级。
甚至,只要分析百分之一的代码后,你就会深刻地体会到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业余爱好者写的。
kernel里面的中断代码分析
* @depth:
disable-depth, for nested irq_disable() calls
* @wake_depth:
enable depth, for multiple set_irq_wake() callers
* @irq_count:
stats field to detect stalled irqs
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) { irqdbf("registering irq %d (extended s3c irq)\n", irqno); set_irq_chip(irqno, &s3c_irqext_chip); set_irq_handler(irqno, do_edge_IRQ); set_irq_flags(irqno, IRQF_VALID);
struct irq_chip *chip;
void
*handler_data;
void
*chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int
status;
/* IRQ status */
unsigned int
depth;
/* nested irq disables */
unsigned int
wake_depth; /* nested wake enables */
unsigned int
irq_count; /* For detecting broken IRQs */
arm-linux 启动代码分析——stage1 (1)
arm-linux 启动代码分析——stage1 (1)本文针对arm linux, 从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照.例:在文件init/main.c中:00478: asmlinkage void __init start_kernel(void)前面的"00478:" 表示478行,冒号后面的内容就是源码了.在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.另外,本文是以uncompressed kernel开始讲解的.对于内核解压缩部分的code,在arch/arm/boot/compressed中,本文不做讨论.一. 启动条件通常从系统上电到执行到linux kenel这部分的任务是由bootloader来完成.关于boot loader的内容,本文就不做过多介绍.这里只讨论进入到linux kernel的时候的一些限制条件,这一般是bootloader在最后跳转到kernel之前要完成的:1.CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;3. 数据cache(Data cache)必须是关闭的4. 指令cache(Instructioncache)可以是打开的,也可以是关闭的,这个没有强制要求;5. CPU 通用寄存器0 (r0)必须是0;6. CPU 通用寄存器1 (r1)必须是ARM Linux machinetype (关于machine type, 我们后面会有讲解)7. CPU 通用寄存器2 (r2) 必须是kernel parameterlist 的物理地址(parameter list 是由bootloader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM Linux"文档).二. starting kernel首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况): 宏 位置 默认值 说明KERNEL_RAM_ADDR arch/arm/kernel/head.S+26 0xc0008000 kernel在RAM中的的虚拟地址PAGE_OFFSET include/asm-arm/memeory.h+50 0xc0000000 内核空间的起始虚拟地址TEXT_OFFSET arch/arm/Makefile+137 0x00008000 内核相对于存储空间的偏移TEXTADDR arch/arm/kernel/head.S+49 0xc0008000 kernel的起始虚拟地址PHYS_OFFSET include/asm-arm/arch-xxx/memory.h 平台相关 RAM的起始物理地址内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:00011: ENTRY(stext)对于vmlinux.lds.S,这是ldscript文件,此文件的格式和汇编及C程序都不同,本文不对ldscript作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考这里的ENTRY(stext) 表示程序的入口是在符号stext.而符号stext是在arch/arm/kernel/head.S中定义的:下面我们将arm linuxboot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.在arch/arm/kernel/head.S中72 - 94 行,是armlinux boot的主代码:00072:ENTRY(stext) 00073: msr cpsr_c,#PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode00074: @and irqsdisabled 00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error‘a’ 00082:bl __create_page_tables 00083: 00084: 00091: ldr r13,__switch_data @address to jump to after00092: @mmu has beenenabled 00093: adr lr,__enable_mmu @return (PIC)address 00094: add pc,r10,#PROCINFO_INITFUNC 其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.arm linux boot的主线可以概括为以下几个步骤:1. 确定processortype (75 - 78行)2. 确定machinetype (79 - 81行)3.创建页表 (82行) 4.调用平台特定的__cpu_flush函数 (在struct proc_info_list中) (94行) 5.开启mmu (93行)6. 切换数据 (91行)最终跳转到start_kernel (在__switch_data的结束的时候,调用了b start_kernel)下面,我们按照这个主线,逐步的分析Code.1. 确定processor type arch/arm/kernel/head.S中:00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 75行: 通过cp15协处理器的c0寄存器来获得processor id的指令.关于cp15的详细内容可参考相关的arm手册76行:跳转到__lookup_processor_type.在__lookup_processor_type中,会把processortype 存储在r5中77,78行: 判断r5中的processor type是否是0,如果是0,说明是无效的processortype,跳转到__error_p(出错)__lookup_processor_type 函数主要是根据从cpu中获得的processorid和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中,0表示没有找到对应的processor type.下面我们分析__lookup_processor_type函数arch/arm/kernel/head-common.S中:00145:.type __lookup_processor_type,%function00146: __lookup_processor_type:00147: adr r3,3f00148: ldmda r3,{r5 - r7}00149: sub r3, r3,r7 @get offset between virt&phys00150: add r5, r5,r3 @convert virt addresses to00151: add r6, r6,r3 @physical address space00152: 1: ldmia r5,{r3,r4} @value, mask00153: and r4, r4,r9 @mask wanted bits00154: teq r3,r400155:beq 2f00156: add r5, r5,#PROC_INFO_SZ @sizeof(proc_info_list)00157: cmp r5,r600158:blo 1b00159: mov r5,#0 @unknown processor00160: 2: mov pc,lr00161:00162:00165: ENTRY(lookup_processor_type)00166: stmfd sp!,{r4 - r7, r9, lr}00167: mov r9,r000168:bl __lookup_processor_type00169: mov r0,r500170: ldmfd sp!,{r4 - r7, r9, pc}00171:00172:00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end 145, 146行是函数定义147行: 取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3. 这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f 处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册) 148行: 因为r3中的地址是178行的位置的地址,因而执行完后: r5存的是176行符号__proc_info_begin的地址; r6存的是177行符号__proc_info_end的地址; r7存的是3f处的地址. 这里需要注意链接地址和运行时地址的区别. r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址). __proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00031: __proc_info_begin= .; 00032: *(.init) 00033: __proc_info_end= .; 这里是声明了两个变量:__proc_info_begin 和__proc_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __proc_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__proc_info_end 的位置. kernel 使用struct proc_info_list来描述processor type. 在include/asm-arm/procinfo.h 中: 00029: struct proc_info_list { 00030: unsignedint cpu_val; 00031: unsignedint cpu_mask; 00032: unsignedlong __cpu_mm_mmu_flags; 00033: unsignedlong __cpu_io_mmu_flags; 00034: unsignedlong __cpu_flush; 00035: constchar *arch_name; 00036: constchar *elf_name; 00037: unsignedint elf_hwcap; 00038: constchar *cpu_name; 00039: structprocessor *proc; 00040: structcpu_tlb_fns *tlb; 00041: structcpu_user_fns *user; 00042: structcpu_cache_fns *cache; 00043: }; 我们当前以at91为例,其processor是926的. 在arch/arm/mm/proc-arm926.S 中: 00464: .section ".init", #alloc,#execinstr 00465: 00466:.type __arm926_proc_info,#object 00467: __arm926_proc_info: 00468:.long 0x41069260 @ARM926EJ-S (v5TEJ) 00469:.long 0xff0ffff0 00470:.long PMD_TYPE_SECT | \ 00471: PMD_SECT_BUFFERABLE| \ 00472: PMD_SECT_CACHEABLE| \ 00473: PMD_BIT4 |\ 00474: PMD_SECT_AP_WRITE| \ 00475: PMD_SECT_AP_READ 00476:.long PMD_TYPE_SECT | \ 00477: PMD_BIT4 |\ 00478: PMD_SECT_AP_WRITE| \ 00479: PMD_SECT_AP_READ 00480:b __arm926_setup 00481:.long cpu_arch_name 00482:.long cpu_elf_name 00483:.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HW CAP_EDSP|HWCAP_JA V A 00484:.long cpu_arm926_name 00485:.long arm926_processor_functions 00486:.long v4wbi_tlb_fns 00487:.long v4wb_user_fns 00488:.long arm926_cache_fns 00489:.size __arm926_proc_info,. - __arm926_proc_info 从464行,我们可以看到__arm926_proc_info 被放到了".init"段中. 对照struct proc_info_list,我们可以看到__cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4.调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.) 从以上的内容我们可以看出: r5中的__proc_info_begin是proc_info_list的起始地址,r6中的__proc_info_end是proc_info_list的结束地址.149行:从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.150行: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址151行: 将r6存储的虚拟地址(__proc_info_end)转换成物理地址152行: 对照structproc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3,r4中153行: r9中存储了processorid(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值154行: 将153行中得到的值与r3中的cpu_val进行比较155行: 如果相等,说明我们找到了对应的processor type,跳到160行,返回156行: (如果不相等) , 将r5指向下一个proc_info,157行: 和r6比较,检查是否到了__proc_info_end.158行: 如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找159行: 执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknownprocessor)160行: 返回2. 确定machine type arch/arm/kernel/head.S中:00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error ‘a’79行: 跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把structmachine_desc的基地址(machine type)存储在r5中80,81行: 将r5中的machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machinetype,跳转到__error_a(出错)__lookup_machine_type 函数下面我们分析__lookup_machine_type 函数: arch/arm/kernel/head-common.S中: 00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end00181:00182: 00193:.type __lookup_machine_type,%function00194: __lookup_machine_type:00195: adr r3,3b00196: ldmia r3,{r4, r5, r6}00197: sub r3, r3,r4 @get offset between virt&phys00198: add r5, r5,r3 @convert virt addresses to00199: add r6, r6,r3 @physical address space00200: 1: ldr r3,[r5, #MACHINFO_TYPE] @ get machinetype00201: teq r3,r1 @matches loader number?00202:beq 2f @found00203: add r5, r5,#SIZEOF_MACHINE_DESC @ nextmachine_desc00204: cmp r5,r600205:blo 1b00206: mov r5,#0 @unknown machine00207: 2: mov pc,lr193, 194行: 函数声明195行: 取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3. 和上面我们对__lookup_processor_type 函数的分析相同,r3中存放的是3b处物理地址.196行: r3是3b处的地址,因而执行完后: r4存的是3b处的地址 r5存的是__arch_info_begin 的地址 r6存的是__arch_info_end 的地址 __arch_info_begin 和__arch_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00034: __arch_info_begin= .; 00035: *(.init) 00036: __arch_info_end= .; 这里是声明了两个变量:__arch_info_begin 和__arch_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __arch_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__arch_info_end 的位置. kernel 使用struct machine_desc 来描述machine type. 在include/asm-arm/mach/arch.h 中: 00017: struct machine_desc { 00018: 00022: unsignedint nr; 00023: unsignedint phys_io; 00024: unsignedint io_pg_offst; 00026: 00027: constchar *name; 00028: unsignedlong boot_params; 00029: 00030: unsignedint video_start; 00031: unsignedint video_end; 00032: 00033: unsignedint reserve_lp0:1; 00034: unsignedint reserve_lp1:1; 00035: unsignedint reserve_lp2:1; 00036: unsignedint soft_reboot:1; 00037:void (*fixup)(structmachine_desc *, 00038: struct tag *, char **, 00039: struct meminfo *); 00040:void (*map_io)(void); 00041:void (*init_irq)(void); 00042: structsys_timer *timer; 00043:void (*init_machine)(void); 00044: }; 00045: 00046: 00050: #defineMACHINE_START(_type,_name) \ 00051: static const struct machine_desc__mach_desc_##_type \ 00052:__attribute_used__ \ 00053:__attribute__((__section__(".init"))) ={ \ 00054:.nr =MACH_TYPE_##_type, \ 00055:.name =_name, 00056: 00057: #defineMACHINE_END \ 00058:}; 内核中,一般使用宏MACHINE_START来定义machine type. 对于at91, 在arch/arm/mach-at91rm9200/board-ek.c 中: 00137: MACHINE_START(AT91RM9200EK, "Atmel AT91RM9200-EK") 00138: 00139: .phys_io =AT91_BASE_SYS, 00140:.io_pg_offst =(AT91_VA_BASE_SYS >> 18)& 0xfffc, 00141:.boot_params =AT91_SDRAM_BASE + 0x100, 00142:.timer =&at91rm9200_timer, 00143:.map_io =ek_map_io, 00144: .init_irq =ek_init_irq, 00145:.init_machine =ek_board_init, 00146: MACHINE_END197行:r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中198行: 将r5存储的虚拟地址(__arch_info_begin)转换成物理地址 199行:将r6存储的虚拟地址(__arch_info_end)转换成物理地址 200行: MACHINFO_TYPE 在arch/arm/kernel/asm-offset.c 101行定义, 这里是取struct machine_desc中的nr(architecture number) 到r3中201行: 将r3中取到的machine type 和r1中的machine type(见前面的"启动条件")进行比较202行: 如果相同,说明找到了对应的machine type,跳转到207行的2f处,此时r5中存储了对应的structmachine_desc的基地址203行: (不相同), 取下一个machine_desc的地址204行: 和r6进行比较,检查是否到了__arch_info_end.205行: 如果不相同,说明还有machine_desc,返回200行继续查找.206行: 执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的, 将r5设置成0(unknownmachine).207行: 返回3. 创建页表通过前面的两步,我们已经确定了processor type 和machine type.此时,一些特定寄存器的值如下所示:r8 = machineinfo (struct machine_desc的基地址)r9 = cpuid (通过cp15协处理器获得的cpu id)r10 =procinfo (struct proc_info_list的基地址)创建页表是通过函数__create_page_tables 来实现的.这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(sectionentry). 每个页表项是32 bits(4 bytes)因而L1主页表占用4096 *4 = 16k的内存空间. 对于ARM926,其L1 section entry的格式为:(可参考arm926EJS TRM): 。
kernel下的uinput机制_示例及概述说明
kernel下的uinput机制示例及概述说明1. 引言1.1 概述在计算机领域中,uinput(user input)机制是一种通过模拟输入设备来实现对系统的控制和操作的方法。
它在kernel中被广泛应用,可以创建虚拟的输入设备,并向系统注入用户自定义的输入事件。
通过uinput机制,我们可以模拟鼠标、键盘、触摸屏等各种输入设备的行为,使得用户或开发者能够以编程的方式进行各种操作和测试。
1.2 文章结构本文将就kernel下的uinput机制进行详细介绍与示例演示。
首先,在"2. uinput机制的原理与用途"部分中,我们将解释什么是uinput机制以及它在系统中起到的作用,进而探讨其背后的实现原理和所涉及到的应用领域。
接着,在"3. 示例:通过uinput模拟输入设备的创建和操作"部分中,我们将会给出具体示例,演示如何使用uinput机制来创建虚拟输入设备,并对其进行模拟操作。
随后,在"4. kernel中uinput机制的相关配置和API接口介绍"部分中,我们将详细介绍如何配置kernel以支持uinput机制,并提供相关API接口的介绍和使用示例。
最后,在"5. 结论与展望"部分中,我们将对uinput机制进行总结回顾,并探讨目前存在的问题以及未来的发展趋势。
1.3 目的本文旨在全面介绍kernel下的uinput机制,帮助读者了解其原理和用途,并提供具体示例和配置指南,使得读者能够深入理解并应用该机制,从而满足各种自动化测试、模拟操作等需求。
同时,对于开发者来说,该文章也提供了一个参考和指导,以便他们在实际项目中更好地利用uinput机制来实现相关功能。
2. uinput机制的原理与用途2.1 什么是uinput机制uinput(User Input)机制是Linux内核中的一个子系统,用于模拟和生成输入事件。
Start_kernel函数分析
//系统中断关闭标志,当early_init完毕后,会恢复中断设置标志为false。
/* Interrupts are still disabled. Do necessary setups, then enable them */
tick_init();
3、kmem_cache_init();初始化slab分配器
4、percpu_init_late();PerCPU变量系统后期初始化
5、pgtable_cache_init();也表缓存初始化,arm中是个空函数
6、vmalloc_init();初始化虚拟内存分配器
】
/*
* Set up the scheduler prior starting any interrupts (such as the
debug_objects_early_init();
//初始化哈希桶(hash buckets)并将static object和pool object放入poll列表,这样堆栈就可以完全操作了
【这个函数的主要作用就是对调试对象进行早期的初始化,就是HASH锁和静态对象池进行初始化,执行完后,object tracker已经开始完全运作了】
setup_log_buf(0);
//使用bootmeme分配一个记录启动信息的缓冲区
pidhash_init();
//进程ID的HASH表初始化,用bootmem分配并初始化PID散列表,由PID分配器管理空闲和已指派的PID,这样可以提供通PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。
setup_arch(&command_line);
kernel分析
一、kernel是什么?包含几个大的部分进程管理、内存管理、文件系统、网络和驱动。
二、有些什么文件?分析它的目录结构三、内核的配置编译配置系统包含了Kconfig和Kbuild两个系统1.KconfigKconfig 对应的是内核配置阶段,如你使用命令:make menuconfig,就是在使用Kconfig 系统。
Kconfig由以下三部分组成:1.1Kconfig文件解析程序: scripts/kconfig/*1.2各个内核源代码目录中的Kconfig文件:Kconfig1.3各个平台的缺省配置文件:arch/$(ARCH)/configs/*_defconfigKconfig系统目的是产生.config,Kbuild就会依据此文件编译指定的目标。
2.KbuildKbuild对应内核编译阶段,由5部分组成:2.1顶层Makefile,根据不同的平台,对各类目标分类并调用相应的规则Makefile生成目标。
2.2.config,内核配置文件,由Kconfig系统产生2.3Arch/$(ARCH)/Makefile,具体平台相关的Makefile2.4Scripts/Makefile.*,通用规则文件,面向所有的Kbuild Makefiles,具体作用见扩展名编译规则文件有,Kbuild.include:共用的定义文件,被许多独立的Makefile.*规则文件和顶层Makefile包含Makefile.build:提供编译built-in.o,lib.a等目标的规则Makefile.lib:负责归类分析obj-y,obj-m和其中的目录subdir-y或subdir-m所使用的规则Makefile.host:本机编译工具(程序)(hostprog-y)的编译规则Makefile.clean:内核源码目录清理规则Makefile.headerinst:内核头文件安装时使用的规则Makefile.modinst:内核模块安装规则Makefile.modpost:模块编译的第二阶段,由<module>.o和<module>.mod生成<module>.ko时使用的规则2.5各个子目录下的Makefile文件,由其上层目录的Makefile调用,执行其上层传递下来的命令2.6顶层Makefile主要负责完成vmlinux(内核文件)与*.ko(内核模块文件)的编译,它读出.config文件,并根据.config文件确定访问哪些子目录,通过递归向下访问子目录的形式完成。
linux_kernel_fuse_源码剖析解析
FUSE源码剖析1. 前言本文是对FUSE-2.9.2源码的学习总结。
FUSE代码在用户空间和内核空间都有运行,为了突出重点,先简要描述了在基于FUSE的用户空间文件系统中执行write操作的一般流程,接下来介绍了重要的数据结构,最后以FUSE的运行过程为线索,剖析FUSE程序运行过程的3个关键步骤:1.FUSE模块加载2.mount和open过程3.对文件write。
对于虚拟文件系统和设备驱动的相关概念本文仅作简要说明。
需要说明的是,由于内核的复杂性及个人能力的有限,本文省略了包括内核同步,异常检查在内的诸多内容,希望可以突出重点。
2. FUSE下write的一般流程图1在基于FUSE的用户空间文件系统中执行write操作的流程如图1所示(由于版面关系,图中部分函数是缩写,请参考源码):1.客户端在mount目录下面,对一个regular file调用write, 这一步是在用户空间执行2.write内部会调用虚拟文件系统提供的一致性接口vfs_write3.根据FUSE模块注册的file_operations信息,vfs_write会调用fuse_file_aio_write,将写请求放入fuse connection的request pending queue, 随后进入睡眠等待应用程序reply4.用户空间的libfuse有一个守护进程通过函数fuse_session_loop轮询杂项设备/dev/fuse, 一旦request queue有请求即通过fuse_kern_chan_receive接收5.fuse_kern_chan_receive通过read读取request queue中的内容,read系统调用实际上是调用的设备驱动接口fuse_dev_read6.在用户空间读取并分析数据,执行用户定义的write操作,将状态通过fuse_reply_write返回给kernel7.fuse_reply_write调用VFS提供的一致性接口vfs_write8.vfs_write最终调用fuse_dev_write将执行结果返回给第3步中等待在waitq的进程,此进程得到reply 后,write返回3. 数据结构本节主要介绍了FUSE中比较重要的数据结构,需要说明的是图示中只列出了与叙述相关的数据成员,完整的数据结构细节请参考源码。
Linux内核源代码分析
Linux内核源代码的组成
核心模块
– 核心模块代码部分布在内核中,部分位 于modules包中。核心代码位于 kernel/modules.c且其数据结构与核心后 台进程kerneld消息位于 include/linux/module.h和 include/linux/kerneld.h目录中。必要时 需查阅inlude/linux/elf.h中的ELF文件格 式。
– 利用自由软件让个人计算机带十几个硬 盘实现阵列技术,及其亚微米超大规模 集成电路CAD系统,可直接输出生产线 控制数据等。
– Linux内核的许多面向通信的底层代码对 开发我国自己的信息安全产品极有参考 价值。
分析Linux内核的意义
开发高水平软件
– 目前Linux的源代码中包含了世界各地几 百名计算机高手的作品,分析这些源代 码对于我们掌握核心技术会起到事半功 倍的作用,尤其是各种驱动程序的编写, 对于我们把软硬件结合起来发展民族信 息产业至关重要。
Linux内核源代码的组成
核心
– 大多数通用代码位于kernel目录下,而处理器相 关代码被放在arch/kernel中,调度管理程序位 于kernel/sched.c ,fork代码位于kernel/fork.c。 底层部分处理及中断处理的代码位于 include/linux/interrupt.h里。在/linux/sched.h中 可以找到task_struct的描述。
– Windows下的一个阅读源代码的工具:Source Insight。该软件可从下载
– Linux下一个阅读源代码的工具是LXR
网络
– 网络代码位于net目录而大多数包含文件位于 include/net中,BSD套接字代码位于socket.c中。 IPv4的INET套接字代码位于net/ipv4/af_inet.c中。 通用协议支撑代码(包括sk_buff处理过程)位 于net/core中,同时TCP/IP网络代码位于 net/ipv4中。网络设备驱动位于drivers/net中。
从kernel原始码的角度分析signal的错误用法和注意事项(zt)
从kernel原始码的角度分析signal的错误用法和注意事项(zt)!声明:按照Linux的习惯,我的这篇文件也遵循GPL 协议:你能随意应用并修改本文件,必须发布你的修改,使其他人能获得一份Copy,尤其是给我一份Copy!我的mail :*********************|*********************均可。
欢迎论坛转载!目前有些内容已在中进行过讨论,能前往:/forum/showflat.php?Cat=&Board=linuxK&Number=60780 0&page=0&view=&sb=&o=&fpart=&vc=1和/forum/showflat.php?Cat=&Board=linuxK&Number=60722 8&page=1&view=collapsed&sb=5&o=7&fpart= 欢迎大家继续讨论,以便文件更加完善!多谢!周末愉快!--bob读这份文件之前,建议先浏览一下《Unix Advanced Programming》里面的signal一章和下面这份出自IBM论坛的文章:进程间通信信号(上)/developerworks/cn/linux/l-ipc/part2/index1.html,和进程间通信信号(下)/developerworks/cn/linux/l-ipc/part2/index2.html该作者写了一个系列的进程间通信的文章,我只是希望对该篇作个补充!因为他们都没有从原始码的角度分析,所以我尝试了一下把上层应用和kernel实现代码分析结合起来,这样使用者才可能真正的理解signal的用法和原理!目前介绍signal理论和用法书不少,缺点是只介绍其用法,非常深奥拗口,不容易理解;而介绍kernel原始码的书,侧重于代码分析,不讲实际应用!我就想到如果把两者结合起来,对上层使用signal函数的用户必然能知起所以然了,而且只要顺着我的代码注释大概粗读一下源码就能理解 signal的特性和用法及你碰到的种种疑惑和不解了。
kernel内核锁-完全解析精选全文完整版
Kernel Locking 中文版Unreliable Guide To LockingRusty Russell<******************.au>翻译:albcamus <****************>©Copyright 2003 Rusty RussellThis documentation is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USAFor more details see the file COPYING in the sourcedistribution of Linux.第1章. 介绍欢迎进入Rusty优秀的《Unreliable Guide to Kernel Locking》细节。
窥探kernel---分析sys_reboot
窥探kernel --- 分析sys_reboot系统调用的内容到这里已经讲述了很多,该到去kernel中窥看一个服务例程具体实现的时候了。
在linux中关机和重启命令有shutdown,reboot,init,poweroff,halt,telinit。
它们都是通过sys_reboot来实现的。
在kernel/sys.c中。
[cpp]view plaincopyprint?1./*2. *kernel/sys.c文件中定义3. * Reboot system call: for obvious reasons only root may call it,4. * and even root needs to set up some magic numbers in the registers5. * so that some mistake won't make this reboot the whole machine.6. * You can also set the meaning of the ctrl-alt-del-key here.7. *8. * reboot doesn't sync: do that yourself before calling this.9. */10.SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,11.void __user *, arg)12.{13.char buffer[256];14.int ret = 0;15.16./* We only trust the superuser with rebooting the system. */17./*18. *检查调用者是否有合法权限。
capable函数用于检查是否有操作指定资源的权限,19. *如果它返回非零值,则调用者有权进行操作,否则无权操作.20. *capable(CAP_SYS_BOOT)即检查调用者是否有权限使用reboot系统调用21. */22.if (!capable(CAP_SYS_BOOT))23.return -EPERM;24.25./* For safety, we require "magic" arguments. */26./*27. *通过对两个参数magic1和magic2的检测,判断reboot系统调用是不是被偶然调用到的。
内核源码分析
1.基础知识:Linux 核源代码位于/usr/src/linux 目录下,其结构分布如图1所示,每一个目录或子目录可以看作一个模块,其目录之间的连线表示“子目录或子模块”的关系。
图1 Linux 源代码的分布结构2.本文档分析Linux的中断机制部分的实现:1)Linux对8259A中断控制器的初始化,8259A 初始化的目的是写入有关命令字,8259A 内部有相应的寄存器来锁存这些命令字,以控制8259A 工作。
代码在/arch/i386/kernel/i8259.c 的函数init_8259A()中:/* 送数据到工作寄存器OCW1(又称中断屏蔽字), 屏蔽所有外部中断, 因为此时系统尚未初始化完毕, 不能接收任何外部中断请求*/outb(0xff, 0x21);outb(0xff, 0xA1);/*送0x11 到ICW1(通过端口0x20),启动初始化编程。
0x11 表示外部中断请求信号为上升沿有效,系统中有多片8295A 级连,还表示要向ICW4 送数*/outb_p(0x11, 0x20);/* 送0x20 到ICW2,写入高五位作为中断向量的高五位,低3 位根据中断源(管脚)填入中断号0~7,因此把IRQ0-7 映射到向量0x20-0x27 */outb_p(0x20+ 0, 0x21);/* 送0x04 到ICW3,ICW3 是8259 的级连命令字,0x04 表示8259A-1 是主片*/outb_p(0x04, 0x21);/* 用ICW1 初始化8259A-2 */outb_p(0x11, 0xA0);/* 用ICW2 把8259A-2 的IRQ0-7 映射到0x28-0x2f */outb_p(0x20 + 8, 0xA1);/* 送0x04 到ICW3。
表示8259A-2 是从片,并连接在8259A_1 的2 号管脚上*/ outb_p(0x02, 0xA1);/* 把0x01 送到ICW4*/outb_p(0x01, 0xA1);2)中断描述符表IDT 的预初始化第一步:中断描述表寄存器IDTR 的初始化用汇编指令LIDT 对中断向量表寄存器IDTR 进行初始化,其代码在arch/i386/boot/setup.S 中:lidt idt_48 # load idt with0,0…idt_48:.word 0 # idt limit = 0.word 0, 0 # idtbase = 0L第二步:把IDT 表的起始地址装入IDTR用汇编指令LIDT 装入IDT 的大小和它的地址,其代码在arch/i386/kernel/head.S 中:#define IDT_ENTRIES 256#其中idt 为一个全局变量,核对这个变量的引用就可以获得IDT 表的地址。
FBOS_Kernel
table[i] = (i * PAGE_SIZE) | 7; for (i = 0; i < MEM_SIZE / PAGE_SIZE / 1024; i++) // fill used page dirs
dir[i] = (PAGE_TABLE + PAGE_SIZE * i) | 7; for (i = MEM_SIZE / PAGE_SIZE / 1024; i < 1024; i++) // fill remaining page dirs
还是顺着启动流程看代码吧,接着到了 kernel 下的 time.c, void set_start_time(void) {
// zero memory memset(&start_time, 0, sizeof(struct time_entry)); // read cmos to get current time do {
四、i386 的初始化
设置 gdt 和设置 idt 等都是在 dt 目录下实现的。dt 就是 descriptor table,而不是地图,描 述符表包括 global descriptor table 和 interrupt descriptor table 两种。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
IPOS: EV:281727 rbn: ctx: fix return value at deletion Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
增加rbn_ctx ioctl调试信息
IPOS: EV:281727 rbn: ctx: verbose trace for ioctl() Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
引入一个新的功能:当默认第一分区mount失败是可以使用第二分区恢复
IPOS: Introduce a recovery= command line option. Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
使能GCOV_PROFILE
增加动态debug特性:RBN_PR_DEBUG1/RBN_PR_DEBUG2
IPOS: rbn: introduce dynamic debug print Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
删除一些没用的注释
修正delete ctx的bug
IPOS: rbn: ctx: fix all context clear with tag==0 Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
打印log的判断条件错误,lizheng修改的
IPOS: xcrp: Use GFP_ATOMIC when allocating from atomic context Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
增加死锁检查机制lockdep
IPOS: rbn_ctx: set lockdep class for device locks Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
IPOS: MH: HV24408 Correct the judgement of condition when turn off log by cli. Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
增加CONFIG_DYNAMIC_DEBUG=y 内核配置
增加动态debug特性:RBN_PR_DEBUG_FTRACE---需要内核配置trace开关,然后开启这个debug开关
IPOS: rbn: enable ftrace print with BIT(31) of print_mask Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
1.addrconf_notify 内核事件
NETDEV_UP
NETDEV_DOWN----addrconf_ifdown
适配新的内核 4.1.21
IPOS: Adapt all IPOS changes to 4.1.y kernel API. add arch/arm/configs/rbos_r... Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 25
IPOS: HU87194: prevent cmsg from crash kernel Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
修正ctx_delete_context的返回值错误,同时增加ctx_delete_context详细的debug信息
修改前的读写锁被封装到了一个自己的函数portlock, 该函数会在中断中调用,故应该使用中断读写锁:
IPOS: rbn/tcpnsr: Use irqsave/irqrestore for portlock Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
修正iph->tot_len字节序问题
IPOS: EV:282332 Fix byte order of iphdr Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
cmsg引起内核crash问题,cmsg信息错误应该返回一个错误的返回值
IPOS: enable gcov Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
写锁中使用可能引起睡眠的申请内存标记GFP_KERNEL
IPOS: ip4v: Remove sleep under lock for modified do_raw_setsockopt() Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
IPOS: rbn: remove unnecessary comment out Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
内核配置文件修改
IPOS: working test defconfigs Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
在锁外面先申请好内存
IPOS: xcrp: Avoid memory allocation under spinlock Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
支持动态指定管理口,默认是eth0
IPOS: add isis module mgmt interface parameter support Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
中断上下文应该使用中断读写锁:
IPOS: rbn_nf_ips: Fix incorrectt use of rwlock with potential deadlock Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y No标记可能会引起睡眠
(修改引入的bug,采用和源码一致的逻辑)
IPOS: fix ip_send_unicast_reply() kernel panic Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
isis返回值错误
涉及到xcrp设备注册,此函数中可能会sleep,根据锁机制,应该使用互斥锁,自旋锁应该是用于占用时间很短的场景
IPOS: af_rbn_pakio: Change refcnt_lock to mutex Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
IPOS: Enable dynamic debug. Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24
修正未处理的宏分支:RBN_TYPE_L2HDR
IPOS: HU46605 [Spitfire] IPv6 ping: control message 8 not implemented Merged Jinsen Fu pduip-os/linux-rbos ipos-4.1.y Nov 24