linux内核文章

Linux内核

总结 Linux内核的内存管理. 进程管理. 文件管理. 设备管理. 中断等内容

作者:coly(李勇)

来源:http://www.linuxforum.net/doc/ipc-coly.html

摘要:介绍了Linux中常用的几种IPC:信号、信号量、消息队列、共享内存

     现在最常用的进程间通信的方式有:信号,信号量,消息队列,共享内存。
      所谓进程通信,就是不同进程之间进行一些"接触",这种接触有简单,也有复杂。机制不
      同,复杂度也不一样。通信是一个广义上的意义,不仅仅指传递一些massege。
      他们的使用方法是基本相同的,所以只要掌握了一种的使用方法,然后记住其他的使用方
      法就可以了。

      1. 信号
      在我学习的内容中,主要接触了信号来实现同步的机制,据说信号也可以用来做其它的事
      情,但是我还不知道做什么。
      信号和信号量是不同的,他们虽然都可用来实现同步和互斥,但前者是使用信号处理器来
      进行的,后者是使用P,V操作来实现的。
      使用信号要先知道有哪些信号,在Linux下有31个需要记住的通用信号,据说也是system
      V中最常用的那些。这里略。
      1. 1信号相关函数:
      #include
      int sigaction(int signo, const struct sigaction *act, struct sigaction
      *oact);
      该函数用来为进程安装信号处理器,struct sigaction数据是用来保存信号处理器的相
      关信息。

      #include
      int sigemptyset(sigset_t *set);
      将信号集合清空。
      int sigfillset(sigset_t *set);
      将信号集合设置成包含所有的信号。在对信号进行操作以前一定要对信号集进行初始化。

      int sigaddset(sigset_t *set, int signo);
      向信号集中加入signo对应的新信号。
      int sigdelset(sigset_t *set, int signo);
      从信号集中删除signo对应的一个信号。
      int sigismember(const sigset_t *set, int signo);
      判断某个信号是否在信号集中。返回1则在,0则不在。

      #include
      int sigprocmask(int how,const sigset_t *set, sigset_t *oset);
      用来设置进程的信号屏蔽码。信号屏蔽码可以用来在某段时间内阻塞一些信号集中的信
      号,如果信号不在信号集中,就不必讨论它,因为肯定不响应,是否能生成也不肯定,我
      没有做过试验。

      1.2我所理解的使用信号机制的方法:
      使用信号,主要做的事情就是信号处理器的工作,这里面是你想做的事情。就像中断处理
      函数一样。
      在使用信号以前,首先要初始化信号集,只有在信号集里面的信号才会被考虑。
      有两种方法可以初始化信号集,一种是设置空信号集,一种是将所有的信号都加到信号集
      中。如果你自己想要的信号集不是这两种,可以在初始化了以后通过添加和删除信号进行
      定制。
      如果在进程执行的一段时间内不想对某些信号进行响应,则可以使用sigprocmask对当前
      的信号集中的一些信号进行阻塞,稍后再执行。
      当你将信号集设置完毕后,在让他工作之前需要安装信号处理器。安装信号处理器可以实
      现这几个功能:
      指定信号处理函数的入口;指定信号屏蔽集合;指定信号处理器的一些标志。所谓信号处
      理器,就是指定了一些处理方法,关键在于安装信号处理器,这是使正确的信号进行正确
      的处理关键。在安装的时候,一定要对特定的信号赋予正确的信号处理函数。
      我不知道不同进程之间的信号处理器能否混用,但是像一个特定的进程中有多少个信号处
      理器这样的问题是不能提的。因为信号处理器是一个概念,他针对的是信号,就是说如果
      你指定了一个数据结构,用它来存储针对某个信号的处理信息,那么安装信号处理器就是
      赋予这个数据结构一些相关信息,使用信号处理器就是用这个数据结构存储的信息来组织
      一种机制当发生这个信号的时候会做一些你实现设置好的处理。但是如果区分不同进程中
      对同一个信号的不同处理器?我想处理器可能只对核它所属的进程有关的信号进行响应,
      但是如果是这样的话,那这是怎么实现的呢?
      不过有一点是可以知道的,那就是每一个信号都有一个信号处理器(确定的),可以动过
      安装信号处理器来指定她的行为。信号处理器由他自己的信息存储区域(我不知道在什么
      地方),但是可以通过向sigaction类型的数据结构向信号处理器的信息存储区域中传递
      信息。这个数据结构由一个就可以了,因为它只是临时传递数据的载体。
      但是sigpromask和信号处理器里面的sigmask是不一样的,前者是在进程当前流程设置信
      号屏蔽,后者是指定在信号处理器作用时需要屏蔽掉的信号。例如,在设置某个特定信号
      的信号处理器时,我们当然不能让它的信号处理器工作了,因为还没有设置完吗,这是我
      们可以使用sigprocmask来让当前的流程开始阻塞该信号,当设置完信号处理器以后,再
      用sigprocmask恢复被阻塞的信号。而以后再接收到该信号时,信号处理器就可以工作了。
      我的想法是,同一个信号在不同的进程里可以有不同的信号处理器(一般应该有一个缺省
      处理),当系统中发生一个信号时,所有能接受到的进程都可以接收到这个信号,并用他
      们自己的信号处理器对这个信号做出各自的响应。

      1.3如何用信号来进行进程间的同步
      同步的实现主要是通过在接受信号之前挂起进程,等待相关信号。所以涉及到异步信号安
      全函数的概念。

      不过信号如何来实现进程间的互斥,我理解不是很多,我想信号的主要用处还是在软中断
      处理和进程同步。


      2.信号量
      信号量和信号是不同的东西,仔细想想就可以理解:信号是实现约定的固定的值,而信号
      量是一个变量记录着某些特定信息。
      信号量这种东西我们在操作系统课程中就已经接触过了,这里只是再草草说几句。信号量
      分为有名和无名两种。进程间通信用有名信号量,同一进程内部通信一般用无名信号量。
      这个我不再多说。
      2.1信号量相关函数
      #include
      #include
      #include
      int semget(key_t key, int nsems, int semflg);
      创建一个新的信号量组或获取一个已经存在的信号量组。

      #include
      #include
      #include
      int semop(int semid, struct sembuf *sop, int nsops);
      semop函数可以一次对一个或多个信号量进行操作。
      Int semctl(int sem_id, int semnum, int cmd,/*union semun arg*/…);
      该函数可以用来获取一些信号量的使用信息或者是来对信号量进行控制。

      2.2我对信号量机制的理解
      对信号量的操作只有两个:P, V。
      为了在逻辑上便于组织信号量,信号量机制中有一个概念是信号量组。我们可以把一个信
      号量组中创建相关的信号量,这样逻辑上清晰也便于管理。在使用之前你同样需要对他们
      进行初始化:生成或打开信号量组,向其中生成或删除你指定的信号量。
      对信号量的操作只用两种,他都是通过semop函数中的sops参数来指定的,如果这个参数
      是一个数组的话,那么就是对多个信号量进行操作。Sops参数中的sem_op字段指明了对信
      号量进行的是P操作还是V操作。你只要指定就行了,具体的操作不需要你去实现,函数中
      都已经提供了。使用信号量,你得清楚信号量组id和信号量在信号量组中的位置(其实也
      就是另一个id)。一个信号量必须属于一个信号量组,否则不能被系统所使用。切记!
      信号量和信号量组是不会被系统所自动清理的,所以当你的进程退出前,千万别忘了清理
      你生成的那些信号量们。
      信号量既可以实现互斥,也可以实现同步,这里就不说了,操作系统课程中是有介绍的。



      3.消息队列
      消息队列是比较高级的一种进程间通信方法,因为它真的可以在进程间传送massege,你
      传送一个"I seek you"都可以。
      一个消息队列可以被多个进程所共享(IPC就是在这个基础上进行的);如果一个进程的
      消息太多一个消息队列放不下,也可以用多于一个的消息队列(不过可能管理会比较复
      杂)。共享消息队列的进程所发送的消息中除了massege本身外还有一个标志,这个标志
      可以指明该消息将由哪个进程或者是哪类进程接受。每一个共享消息队列的进程针对这个
      队列也有自己的标志,可以用来声明自己的身份。
      对于系统中的每一个消息队列,都有一个数据结构来代表它,这个数据结构是msqid_ds,
      这里略去不讲,在中可以看到它的原型。

      3.1消息队列相关函数
      使用消息队列之前,你要么获得这个消息队列,要么自己建立一个,否则是不能使用消息
      队列的(我觉得这都像是多余的话,请见谅)。当这个消息队列不再使用时,也一定要有
      一个进程来删除消息队列,系统是不会自动的清理消息队列和msgid_ds的。

      Int msgget(key_t key, int msgflg);
      获取一个存在的消息队列的ID,或者是根据跟定的权限创建一个消息队列。但是怎么样去
      删除这个消息队列,我还不十分清楚。
      Int msgctl(int msqid, int cmd, struct msqid_ds *buf);
      用来从msqid_ds中获取很多消息队列本身的信息。
      Int msgsnd(int msqid, void *msgp, size_t msgsz, int msgflg);
      用于向队列发送消息。
      Int msgrcv(int msqid, void *msgp, size_t msgsz, long int msgtyp, int
      msgflg);
      从队列中接收消息。
      我这个文档里面对消息队列中的一些临界情况所述不多,因为这是我的小结,而非介绍。
      在GNU C库技术中可以看到它的详细介绍。


作者:coly(李勇)

来源:http://www.linuxforum.net/doc/write-coly.html

摘要:介绍了一个简单的字符设备驱动程序,深入剖析了write函数的工作原理

     在Linux下我们在使用设备的时候,都会用到write这个函数,通过这个函数我们可以象使
      用文件那样向设备传送数据。可是为什么用户使用write函数就可以把数据写到设备里面
      去,这个过程到底是怎么实现的呢? 
      
      这个奥秘就在于设备驱动程序的write实现中,这里我结合一些源代码来解释如何使得一
      个简简单单的write函数能够完成向设备里面写数据的复杂过程。
       
      这里的源代码主要来自两个地方。第一是oreilly出版的《Linux device driver》中的
      实例,第二是Linux Kernel 2.2.14核心源代码。我只列出了其中相关部分的内容,如果
      读者有兴趣,也可以查阅其它源代码。不过我不是在讲解如何编写设备驱动程序,所以不
      会对每一个细节都进行说明,再说有些地方我觉得自己还没有吃透。
       
      由于《Linux device driver》一书中的例子对于我们还是复杂了一些,我将其中的一个
      例程简化了一下。这个驱动程序支持这样一个设备:核心空间中的一个长度为10的数组
      kbuf[10]。我们可以通过用户程序open它,read它,write它,close它。这个设备的名
      字我称为short_t。 
      
      现在言归正传。 
      对于一个设备,它可以在/dev下面存在一个对应的逻辑设备节点,这个节点以文件的形式
      存在,但它不是普通意义上的文件,它是设备文件,更确切的说,它是设备节点。这个节
      点是通过mknod命令建立的,其中指定了主设备号和次设备号。主设备号表明了某一类设
      备,一般对应着确定的驱动程序;次设备号一般是区分是标明不同属性,例如不同的使用
      方法,不同的位置,不同的操作。这个设备号是从/proc/devices文件中获得的,所以一
      般是先有驱动程序在内核中,才有设备节点在目录中。这个设备号(特指主设备号)的主
      要作用,就是声明设备所使用的驱动程序。驱动程序和设备号是一一对应的,当你打开一
      个设备文件时,操作系统就已经知道这个设备所对应的驱动程序是哪一个了。这个"知道"
      的过程后面就讲。 
      
      我们再说说驱动程序的基本结构吧。这里我只介绍动态模块型驱动程序(就是我们使用
      insmod加载到核心中并使用rmmod卸载的那种),因为我只熟悉这种结构。 
      模块化的驱动程序由两个函数是固定的:int init_module(void) ;void
      cleanup_module(void)。前者在insmod的时候执行,后者在rmmod的时候执行。 
      init_nodule在执行的时候,进行一些驱动程序初始化的工作,其中最主要的工作有三
      件:注册设备;申请I/O端口地址范围;申请中断IRQ。这里和我们想知道的事情相关的只
      有注册设备。
      
      下面是一个典型的init_module函数: 
      
      int init_module(void){ 
      int result = check_region(short_base,1);/* 察看端口地址*/ 
      …… 
      request_region(short_base,1,"short"); /* 申请端口地址*/ 
      …… 
      result = register_chrdev(short_major, "short", &short_fops); /* 注册设备
      */ 
      …… 
      result = request_irq(short_irq, short_interrupt, SA_INTERRUPT, "short",
      NULL); /* 申请IRQ */ 
      …… 
      return 0; 
      }/* init_module*/ 
      
      上面这个函数我只保留了最重要的部分,其中最重要的函数是 
      result = register_chrdev(short_major, "short", &short_fops); 
      这是一个驱动程序的精髓所在!!当你执行indmod命令时,这个函数可以完成三件大事:
      第一,申请主设备号(short_major),或者指定,或者动态分配;第二,在内核中注册设
      备的名字("short");第三,指定fops方法(&short_fops)。其中所指定的fops方法就是
      我们对设备进行操作的方法(例如read,write,seek,dir,open,release等),如何实现
      这些方法,是编写设备驱动程序大部分工作量所在。 
      
      现在我们就要接触关键部分了--如何实现fops方法。 
      我们都知道,每一个文件都有一个file的结构,在这个结构中有一个file_operations的
      结构体,这个结构体指明了能够对该文件进行的操作。
      
      下面是一个典型的file_operations结构: 
      struct file_operations { 
      loff_t (*llseek) (struct file *, loff_t, int); 
      ssize_t (*read) (struct file *, char *, size_t, loff_t *); 
      ssize_t (*write) (struct file *, const char *, size_t, loff_t *); 
      int (*readdir) (struct file *, void *, filldir_t); 
      unsigned int (*poll) (struct file *, struct poll_table_struct *); 
      int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned
      long); 
      int (*mmap) (struct file *, struct vm_area_struct *); 
      int (*open) (struct inode *, struct file *); 
      int (*flush) (struct file *); 
      int (*release) (struct inode *, struct file *); 
      int (*fsync) (struct file *, struct dentry *); 
      int (*fasync) (int, struct file *, int); 
      int (*check_media_change) (kdev_t dev); 
      int (*revalidate) (kdev_t dev); 
      int (*lock) (struct file *, int, struct file_lock *); 
      }; 
      
      我们可以看到它实际上就是许多文件操作的函数指针,其中就有write,其它的我们就不
      去管它了。这个write指针在实际的驱动程序中会以程序员所实现的函数名字出现,它指
      向程序员实现的设备write操作函数。下面就是一个实际的例子,这个write函数可以向核
      心内存的一个数组里输入一个字符串。 
      
      int short_write (struct inode *inode, struct file *filp, const char *buf,
      int count){ 
      int retval = count; 
      extern unsigned char kbuf[10]; 

      if(count>10) 
      count=10; 
      copy_from_user(kbuf, buf, count); 
      return retval; 
      }/* short_write */ 
      设备short_t对应的fops方法是这样声明的: 
      struct file_operations short_fops = { 
      NULL, /* short_lseek */ 
      short_read, 
      short_write, 
      NULL, /* short_readdir */ 
      NULL, /* short_poll */ 
      NULL, /* short_ioctl */ 
      NULL, /* short_mmap */ 
      short_open, 
      short_release, 
      NULL, /* short_fsync */ 
      NULL, /* short_fasync */ 
      /* nothing more, fill with NULLs */ 
      }; 
      
      其中NULL的项目就是不提供这个功能。所以我们可以看出short_t设备只提供了
      read,write,open,release功能。其中write功能我们在上面已经实现了,具体的实现函
      数起名为short_write。这些函数就是真正对设备进行操作的函数,这就是驱动程序的一
      大好处:不管你实现的时候是多么的复杂,但对用户来看,就是那些常用的文件操作函数。
       
      但是我们可以看到,驱动程序里的write函数有四个参数,函数格式如下: 
      short_write (struct inode *inode, struct file *filp, const char *buf, int count) 
      而用户程序中的write函数只有三个参数,函数格式如下: 
      write(inf fd, char *buf, int count) 
      那他们两个是怎么联系在一起的呢?这就要靠操作系统核心中的函数sys_write了,下面
      是Linux Kernel 2.2.14中sys_write中的源代码: 
      asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count) 
      { 
      ssize_t ret; 
      struct file * file; 
      struct inode * inode; 
      ssize_t (*write)(struct file *, const char *, size_t, loff_t *); /* 指向
      驱动程序中的wirte函数的指针*/ 

      lock_kernel(); 
      ret = -EBADF; 
      file = fget(fd); /* 通过文件描述符得到文件指针 */ 
      if (!file) 
      goto bad_file; 
      if (!(file->f_mode & FMODE_WRITE)) 
      goto out; 
      inode = file->f_dentry->d_inode; /* 得到inode信息 */ 
      ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file, file->f_pos,
      count); 
      if (ret) 
      goto out; 
      ret = -EINVAL; 
      if (!file->f_op || !(write = file->f_op->write)) /* 将函数开始时声明的
      write函数指针指向fops方法中对应的write函数 */ 
      goto out; 
      down(&inode->i_sem); 
      ret = write(file, buf, count, &file->f_pos); /* 使用驱动程序中的write函数
      将数据输入设备,注意看,这里就是四个参数了 */ 
      up(&inode->i_sem); 
      out: 
      fput(file); 
      bad_file: 
      unlock_kernel(); 
      return ret; 
      } 

      我写了一个简单的程序来测试这个驱动程序,该程序源代码节选如下(该省的我都省了): 
      
      main(){ 
      int fd,count=0; 
      unsigned char buf[10]; 
      fd=open("/dev/short_t",O_RDWR); 
      printf("input string:"); 
      scanf("%s",buf); 
      count=strlen(buf); 
      if(count>10) 
      count=10; 
      count=write(fd,buf,count); 
      close(fd); 
      return 1; 
      } 

      现在我们就演示一下用户使用write函数将数据写到设备里面这个过程到底是怎么实现的: 
      1,insmod驱动程序。驱动程序申请设备名和主设备号,这些可以在/proc/devieces中获得。 
      2,从/proc/devices中获得主设备号,并使用mknod命令建立设备节点文件。这是通过主
      设备号将设备节点文件和设备驱动程序联系在一起。设备节点文件中的file属性中指明了
      驱动程序中fops方法实现的函数指针。 
      3,用户程序使用open打开设备节点文件,这时操作系统内核知道该驱动程序工作了,就
      调用fops方法中的open函数进行相应的工作。open方法一般返回的是文件标示符,实际
      上并不是直接对它进行操作的,而是有操作系统的系统调用在背后工作。 
      4,当用户使用write函数操作设备文件时,操作系统调用sys_write函数,该函数首先通
      过文件标示符得到设备节点文件对应的inode指针和flip指针。inode指针中有设备号信
      息,能够告诉操作系统应该使用哪一个设备驱动程序,flip指针中有fops信息,可以告诉
      操作系统相应的fops方法函数在那里可以找到。 
      5,然后这时sys_write才会调用驱动程序中的write方法来对设备进行写的操作。 
      其中1-3都是在用户空间进行的,4-5是在核心空间进行的。用户的write函数和操作系统
      的write函数通过系统调用sys_write联系在了一起。 
      注意: 
      对于块设备来说,还存在写的模式的问题,这应该是由GNU C库来解决的,这里不予讨
      论,因为我没有看过GNU C库的源代码。 
      另外,这是一个测试版的文章,请各位朋友们多提意见和建议,非常感谢! 


        
 
 
中国Linux论坛 版权所有



    摘要:介绍linux驱动程序中ioctl的概念、意义和用法    (全文共4709字)——点击 此处阅读全文

http://www-128.ibm.com/developerworks/cn/linux/l-module26/

作者:不详
来源:http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=114089
摘要: 2.6内核有很多地方与2.4内核不同,这里就介绍了2.6内核中内核模块的编写框架和编译方法.


前一阵子在ml上升级了内核2.6.11.7,感觉速度还可以,这次和大家分享下关于
内核模块的编写过程。如果你想深入系统内部,自己写点东西看看内核是很有
意思的。

2.6内核的模块无论是编写框架还是编译方法都和2.4版有了很大不同,网络上有很多
关于这个问题的文章,但是大多没有对一些细节问题说清楚,导致了编译hello world
级的模块都会出问题,这对于刚刚开始学习内核模块编程的朋友来说是非常不好的,
下面我就来说一下整个框架的编写和编译方法,目的就是能够作出一个可以看到结果
模块。

说明
在你进行任何具体的编程前,你应该看看你自己源代码目录树中 Documentation / kbuild
中的几篇文章,对你编程大有益处的。当然如果你不愿意看,就凑合看我说的吧。:D

使用模块进行编程最大的好处就是可以和内核有个亲密接触,对于所有的内核变量和CPU
特权指令都可以在模块中使用,这里的例子是根据<<Linux Device Driver 3nd Edition>>
中的helloworld改写的,功能包括current符号和%cr3寄存器的读取,这些在Ring3下都是
不可能的。

程序框架

代码:
#include <linux/init.h>
#include <linux/sched.h>    /*为了引用current而加入的头文件*/
#include <linux/module.h>

MODULE_LICENSE("GPL");   /*这行用于告诉内核该模块拥有free license,在2.6中这是必须的*/

/*执行真正的初始化工作*/
static int hello_init(void) {
   unsigned int cr3;
   __asm__ ("movl %%cr3, %0":"=a"(cr3));
   printk(KERN_ALERT "Hello, world/n");
   printk(KERN_ALERT "The process is /"%s/" (pid %i)/n", current->comm, current->pid);
   printk(KERN_ALERT "The cr3 register is /"0x%08X/"/n", cr3);
   return 0;
}

/*执行真正的析构工作*/
static void hello_exit(void) {
   printk(KERN_ALERT "Goodbye, cruel world/n");
}

/*该函数注册模块的构造函数*/
module_init(hello_init);
/*该函数注册模块的析构函数*/
module_exit(hello_exit);


编译模块
值得说明的是,编译模块的make file的文件名必须是Makefile,而不能是makefile

Makefile的代码
代码:
ifneq ($(KERNELRELEASE),)
   obj-m := helloworld.o
else
   KERNELDIR ?= /lib/modules/$(shell uname -r)/build
   PWD := $(shell pwd)
default:
   $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif


关于GNU make工具扩展的说明
在上面的Makefile中使用了
obj-m := 这个赋值语句的含义说明要使用目标文件helloworld.o建立一个模块,最后生成
的模块的名字就是helloworld.ko,如果你有一个名为module.ko的模块依赖于两个文件
file1.o和file2.o,那么我们可以使用module-obj扩展,如下所示
obj-m := module.o
module-objs := file1.o file2.o

编译模块
只要在helloword.c所在目录执行make就好了,在编译完成后,用root身份输入/sbin/init 3
进入text mode

测试
输入 insmod ./helloworld.ko 应该能看到三行信息
输入 rmmod ./helloworld.ko 看到goodbye...

至于2.6和2.4内核模块的区别,网上的文章很多,就不在多说了。另外,关于模块,还有一些
平台相关和版本依赖的问题。小弟我目前正在学习字符设备的驱动程序,等有所心得的时候,
一起和大家分享。

作者:不详

来源:http://www.i170.com/user/grip2/Article_8613

在容易引起混淆的地方我将把把内存中的inode结构称为VFS inode,而文件
系统以EXT2为代表,把Ext2 inode作为磁盘上的inode代表。
首先需要分别对内存中的inode与磁盘上的inode做一下简单的描述:
<内存中的inode结构:>
   VFS inode包含文件访问权限、属主、组、大小、生成时间、访问时间、
最后修改时间等信息。它是linux管理文件系统的最基本单位,也是文件系
统连接任何子目录、文件的桥梁。inode结构中的静态信息取自物理设备上
的文件系统,由文件系统指定的函数填写,它只存在于内存中,可以通过
inode缓存访问。虽然每个文件都有相应的inode结点,但是只有在需要的时
候系统才会在内存中为其建立相应的inode数据结构,建立的inode结构将形
成一个链表,我们可以通过遍历这个链表去得到我们需要的文件结点,VFS也
为已分配的inode构造缓存和hash table,以提高系统性能。inode结构中的
struct inode_operations *iop为我们提供了一个inode操作列表,通过这个
列表提供的函数我们可以对VFS inode结点进行各种操作。每个inode结构都
有一个i结点号i_ino,在同一个文件系统中每个i结点号是唯一的。

<磁盘上的inode:>
   EXT2通过使用inode来定义文件系统的结构以及描述系统中每个文件的
管理信息,每个文件都有一个inode且只有一个,即使文件中没有数据,其
索引结点也是存在的。每个文件用一个单独的Ext2 inode结构来描述,而且
每一个inode都有唯一的标志号。Ext2 inode为内存中的inode结构提供了文
件的基本信息,随着内存中inode结构的变化,系统也将更新Ext2 inode中
相应的内容。Ext2 inode对应的是Ext2_inode结构。

从上面的描述,我们可以对内存中inode与磁盘中inode做出比较:
位置:VFS inode结构位于内存中,而Ext2_inode位于磁盘。
生存期:VFS inode在需要时才会被建立,如果系统断电,此结构也随之消失。
       而Ext2_inode的存在与系统是否上电无关,而且无论文件是否包含
       数据,Ext2_inode都是存在的。
唯一性:两者在自己的作用域中都是唯一的。
关系:VFS inode是Ext2 inode的抽象、映射与扩充,而后者是前者的静态
     信息部分,也是对前者的具体化、实例化和持久化。
操作:对VFS inode的操作具有通用性,对文件系统inode的操作则是文件系
     统相关的,依赖于特定的实现。
组织管理:系统通过VFS inode链表来对其进行组织,并且为了提高访问效率
         相应地构造了inode构造缓存和hash table。
         Ext2 inode的信息位于EXT2文件系统的划分的块组中,在每个块组
         中包含相应的inode位图、inode表指定具体的inode信息,每个
         inode对应Ext2_inode结构。


上面是从原理上对内存中inode与磁盘中inode进行比较,实际上在代码上也体
现出它们的不同。在下面我把在内核中两者对应的结构代码贴出来,虽然长了
一些,但是对进一步的比较还是很有好处。
struct inode {
struct list_headi_hash;
struct list_headi_list;
struct list_headi_dentry;

struct list_headi_dirty_buffers;

unsigned longi_ino;
atomic_ti_count;
kdev_ti_dev;
umode_ti_mode;
nlink_ti_nlink;
uid_ti_uid;
gid_ti_gid;
kdev_ti_rdev;
loff_ti_size;
time_ti_atime;
time_ti_mtime;
time_ti_ctime;
unsigned longi_blksize;
unsigned longi_blocks;
unsigned longi_version;
unsigned shorti_bytes;
struct semaphorei_sem;
struct rw_semaphorei_truncate_sem;
struct semaphorei_zombie;
struct inode_operations*i_op;
struct file_operations*i_fop;/* former ->i_op->default_file_ops */
struct super_block*i_sb;
wait_queue_head_ti_wait;
struct file_lock*i_flock;
struct address_space*i_mapping;
struct address_spacei_data;
struct dquot*i_dquot[MAXQUOTAS];
/* These three should probably be a union */
struct pipe_inode_info*i_pipe;
struct block_device*i_bdev;
struct char_device*i_cdev;

unsigned longi_dnotify_mask; /* Directory notify events */
struct dnotify_struct*i_dnotify; /* for directory notifications */

unsigned longi_state;

unsigned inti_flags;
unsigned chari_sock;

atomic_ti_writecount;
unsigned inti_attr_flags;
__u32i_generation;
union {
struct minix_inode_infominix_i;
struct ext2_inode_infoext2_i;
struct ext3_inode_infoext3_i;
struct hpfs_inode_infohpfs_i;
struct ntfs_inode_infontfs_i;
struct msdos_inode_infomsdos_i;
struct umsdos_inode_infoumsdos_i;
struct iso_inode_infoisofs_i;
struct sysv_inode_infosysv_i;
struct affs_inode_infoaffs_i;
struct ufs_inode_infoufs_i;
struct efs_inode_infoefs_i;
struct romfs_inode_inforomfs_i;
struct shmem_inode_infoshmem_i;
struct coda_inode_infocoda_i;
struct smb_inode_infosmbfs_i;
struct hfs_inode_infohfs_i;
struct adfs_inode_infoadfs_i;
struct qnx4_inode_infoqnx4_i;
struct reiserfs_inode_inforeiserfs_i;
struct bfs_inode_infobfs_i;
struct udf_inode_infoudf_i;
struct ncp_inode_infoncpfs_i;
struct proc_inode_infoproc_i;
struct socketsocket_i;
struct usbdev_inode_info        usbdev_i;
struct jffs2_inode_infojffs2_i;
void*generic_ip;
} u;
};


struct ext2_inode {
__u16i_mode;/* File mode */
__u16i_uid;/* Low 16 bits of Owner Uid */
__u32i_size;/* Size in bytes */
__u32i_atime;/* Access time */
__u32i_ctime;/* Creation time */
__u32i_mtime;/* Modification time */
__u32i_dtime;/* Deletion Time */
__u16i_gid;/* Low 16 bits of Group Id */
__u16i_links_count;/* Links count */
__u32i_blocks;/* Blocks count */
__u32i_flags;/* File flags */
union {
struct {
__u32  l_i_reserved1;
} linux1;
struct {
__u32  h_i_translator;
} hurd1;
struct {
__u32  m_i_reserved1;
} masix1;
} osd1;/* OS dependent 1 */
__u32i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
__u32i_generation;/* File version (for NFS) */
__u32i_file_acl;/* File ACL */
__u32i_dir_acl;/* Directory ACL */
__u32i_faddr;/* Fragment address */
union {
struct {
__u8l_i_frag;/* Fragment number */
__u8l_i_fsize;/* Fragment size */
__u16i_pad1;
__u16l_i_uid_high;/* these 2 fields    */
__u16l_i_gid_high;/* were reserved2[0] */
__u32l_i_reserved2;
} linux2;
struct {
__u8h_i_frag;/* Fragment number */
__u8h_i_fsize;/* Fragment size */
__u16h_i_mode_high;
__u16h_i_uid_high;
__u16h_i_gid_high;
__u32h_i_author;
} hurd2;
struct {
__u8m_i_frag;/* Fragment number */
__u8m_i_fsize;/* Fragment size */
__u16m_pad1;
__u32m_i_reserved2[2];
} masix2;
} osd2;/* OS dependent 2 */
};

从结构的定义中可以看出来inode(VFS inode)与ext2_inode的差别是很大的,怎么说呢,
除了相同的都是不同。它们都包含动态信息和静态信息,通过union指定的内容那一定
是动态的了。inode结构中的union u实际上反映了VFS支持的文件系统。
可以看出inode结构与ext2_inode结构有些内容是相似的,如inode定义的
       unsigned longi_ino;
umode_ti_mode;
nlink_ti_nlink;
uid_ti_uid;
gid_ti_gid;
loff_ti_size;
time_ti_atime;
time_ti_mtime;
time_ti_ctime;
unsigned longi_blksize;
unsigned longi_blocks;
和ext2_inode定义的定义部分
       __u16i_mode;/* File mode */
__u16i_uid;/* Low 16 bits of Owner Uid */
__u32i_size;/* Size in bytes */
__u32i_atime;/* Access time */
__u32i_ctime;/* Creation time */
__u32i_mtime;/* Modification time */
__u32i_dtime;/* Deletion Time */
__u16i_gid;/* Low 16 bits of Group Id */
__u16i_links_count;/* Links count */
__u32i_blocks;/* Blocks count */
__u32i_flags;/* File flags */
这些都可以对应上,当然还有一些不同的地方,如inode中定义的
kdev_ti_rdev;
kdev_ti_dev;
kdev_ti_dev;
unsigned shorti_bytes;
struct semaphorei_sem;
在ext2_inode中没有体现,不过这部分对ext2_inode是没有用途而且无法确定的。
类似的,可以推广到两个结构的其余部分,最终在代码中的区别还是与原理中分析的
区别相关的,也是原理的具体体现。



    摘要:比较直观地介绍了Linux设备驱动程序的开发原理    (全文共37074字)——点击 此处阅读全文


    摘要:几本比较经典的书    (全文共102字)——点击 此处阅读全文


    摘要:Linux内核编程的概念、原理和基本操作    (全文共153字)——点击 此处阅读全文

一. 采用虚拟内存技术

 

Linux页表--三级页表

 



二. 页面分配与回收
    所有的物理页面用包含mem_map_t结构的链表mem_map来描叙,这些结构在系统启动时初始化。每个 mem_map_t描叙了一个物理页面。

    使用free_area数组管理空闲块,实现页面的分配与回收


三. 内存映射
    将映象连接到进程虚拟地址空间的过程称为内存映射。
    映象执行时,可执行映象的内容将被调入进程虚拟地址空间中。可执行映象使用的共享库同样如此。然而可执行文件实际上并没有调入物理内存,而是仅仅连接到进程的虚拟内存。当程序的其他部分运行时引用到这部分时才把它们从磁盘上调入内存。


    可执行映象映射到进程虚拟地址时将产生一组相应的vm_area_struct数据结构。每个vm_area_struct数据结构表示可执行映象的一部分:可执行代码、初始化数据(变量)、未初始化数据等等。Linux支持许多标准的虚拟内存操作函数,创建vm_area_struct数据结构时有一组相应的虚拟内存操作函数与之对应。
    每个进程的虚拟内存用一个mm_struct来表示。它包含当前执行的映象(如BASH)以及指向vm_area_struct 的大量指针。每个vm_area_struct数据结构描叙了虚拟内存的起始与结束位置,进程对此内存区域的存取权限以及一组内存操作函数。这些函数都是 Linux在操纵虚拟内存区域时必须用到的子程序。其中一个负责处理进程试图访问不在当前物理内存中的虚拟内存(通过页面失效)的情况。此函数叫 nopage。它用在Linux试图将可执行映象的页面调入内存时。

四. 请求换页  

    当可执行映象到进程虚拟地址空间的映射完成后,它就可以开始运行了。由于只有很少部分的映象调入内存,所以很快就会发生对不在物理内存中的虚拟内存区域的访问。当进程访问无有效页表入口的虚拟地址时,处理器将向Linux报告一个页面错误。
    页面错误带有失效发生的虚拟地址及引发失效的访存方式。Linux必须找到表示此区域的vm_area_struct结构。对 vm_area_struct数据结构的搜寻速度决定了处理页面错误的效率,而所有vm_area_struct结构是通过一种AVL(Adelson- Velskii and Landis) 树结构连在一起的。如果无法找到vm_area_struct与此失效虚拟地址的对应关系,则系统认为此进程访问了非法虚拟地址。这时Linux将向进程发送SIGSEGV信号,如果进程没有此信号的处理过程则终止运行。
    如果找到此对应关系,Linux接下来检查引起该页面错误的访存类型。如果进程以非法方式访问内存,比如对不可写区域进行写操作,系统将产生内存错误的信号。

    如果Linux认为页面出错是合法的,那么它需要对这种情况进行处理。

    首先Linux必须区分位于交换文件中的页面和那些位于磁盘上的可执行映象。

    不是所有的vm_area_struct数据结构都有一组虚拟内存操作函数,它们有的甚至没有nopage函数。这是因为 Linux通过分配新的物理页面并为其创建有效的页表入口来修正这次访问。如果这个内存区域存在nopage操作函数,Linux将调用它。

    一般Linux nopage函数被用来处理内存映射可执行映象,同时它使用页面cache将请求的页面调入物理内存中去。

    当请求的页面调入物理内存时,处理器页表也必须更新。更新这些入口必须进行相关硬件操作,特别是处理器使用TLB时。这样当页面失效被处理完毕后,进程将从发生失效虚拟内存访问的位置重新开始运行。

五. Linux页面cache

六. 换出与丢弃页面

八. swap cache

九. 页面的换入




参考文献:
 http://net.pku.edu.cn/~yhf/lyceum/linuxK/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值