人体大脑解剖

进程:
每个逻辑控制流都是一个进程,由内核来调度和维护
因为进程有独立的虚拟地址空间
想要和其他流通信,控制流必须使用某种显式的进程通信(IPC)机制

I/O多路复用:
在这种形式的并发编程中,应用程序在一个进程的上下文中显式地调用他们自己的逻辑流。
逻辑流被模型化为状态机,数据到达文件描述服后,主程序显式地从一个状态转换为另一个状态
因为程序是一个单独的进程,所以所有的流都共享同一个地址空间

线程:
线程是运行在一个单一进程上下文中的逻辑流,由内核进行调度。你可以把线程看成是其他两种方式的混合体,
像进程流一样的内核进行调度,而像I/O多路复用流一样共享同一个虚拟地址空间



控制流:控制转移序列
控制转移:从一条指令到下一条指令
异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流

异常的剖析。处理器状态中的变化(事件)触发从应用程序








进程间通信机制有哪些

1,管道及有名管道

管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系



有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信


2,信号

信号量是一个计数器,可以用来控制多个进程对共享资源的访问

它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进

程间以及同一进程内不同线程之间的同步手段。


3,消息队列

 

   消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照

一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。    


   其基本思想是:根据”生产者-消费者”原理,利用内存中公用消息缓冲区实现进程之间的信息交换.  

   内存中开辟了若干消息缓冲区,用以存放消息.每当一个进程向另一个进程发送消息时,便申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知接收进程.接收进程收到发送里程发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需的信息,然后把消息缓冲区不定期给系统.系统负责管理公用消息缓冲区以及消息的传递.  
   一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息.显然,进程中关于消息队列的操作是临界区.当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中到出消息:反之也一样. 




4,共享内存

可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存,不同进程可以

及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,

如互斥锁和信号量

    这种通信模式需要解决两个问题:第一个问题是怎样提供共享内存;第二个是公共内存的互斥关系则是程序开发人员的责任。  


5,信号量

主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段


   6.套接字(socket);

     套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

用法:

    1 管道 

  它包括无名管道有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 
  1.1 无名管道由pipe()函数创建: 
  

  
  
  1. #include <unistd.h>
  2. int pipe(int filedis[2]);//参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
  3. //下面的例子示范了如何在父进程和子进程间实现通信。 
  4. #define INPUT 0
  5. #define OUTPUT 1
  6. void main() 
  7. {
  8.     int file_descriptors[2];
  9.     /*定义子进程号 */
  10.     pid_t pid;
  11.     char buf[256];
  12.     int returned_count;
  13.     /*创建无名管道*/
  14.     pipe(file_descriptors);
  15.     /*创建子进程*/
  16.     if((pid = fork()) == -1) {
  17.         printf("Error in fork\n");
  18.         exit(1);
  19.     }
  20.     /*执行子进程*/
  21.     if(pid == 0) {
  22.         printf("in the spawned (child) process...\n");
  23.         /*子进程向父进程写数据,关闭管道的读端*/
  24.         close(file_descriptors[INPUT]);
  25.         write(file_descriptors[OUTPUT], "test data", strlen("test data"));
  26.         exit(0);
  27.     }else{
  28.         /*执行父进程*/
  29.         printf("in the spawning (parent) process...\n");
  30.         /*父进程从管道读取子进程写的数据,关闭管道的写端*/
  31.         close(file_descriptors[OUTPUT]);
  32.         returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
  33.         printf("%d bytes of data received from spawned process: %s\n",
  34.         returned_count, buf);
  35.     }
  36. }

        1.2 有名管道可由两种方式创建

   方式一:mkfifo("myfifo","rw"); 
   方式二:mknod myfifo p 

   生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作

  
  
  1. /* 进程一:读有名管道*/
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. void main() 
  5. {
  6.     FILE * in_file;
  7.     int count = 1;
  8.     char buf[80];
  9.     in_file = fopen("mypipe", "r");
  10.     if (in_file == NULL) {
  11.         printf("Error in fdopen.\n");
  12.         exit(1);
  13.     }
  14.     while ((count = fread(buf, 1, 80, in_file)) > 0)
  15.         printf("received from pipe: %s\n", buf);
  16.     fclose(in_file);
  17. }
  18. /* 进程二:写有名管道*/
  19. #include <stdio.h>
  20. #include <unistd.h>
  21. void main() 
  22. {
  23.     FILE * out_file;
  24.     int count = 1;
  25.     char buf[80];
  26.     out_file = fopen("mypipe", "w");
  27.     if (out_file == NULL) {
  28.         printf("Error opening pipe.");
  29.         exit(1);
  30.     }
  31.     sprintf(buf,"this is test data for the named pipe example\n");
  32.     fwrite(buf, 1, 80, out_file);
  33.     fclose(out_file);
  34. }
    
     2 消息队列  

   消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。
      事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。
 


 3 共享内存 
    共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。

       得到共享内存有两种方式:映射/dev/mem设备内存映像文件前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是 实际的物理内存

   首先要用的函数是shmget,它获得一个共享存储标识符。 
     #include <sys/types.h> 
     #include <sys/ipc.h> 
     #include <sys/shm.h> 
    

                  int shmget(key_t key, int size, int flag); 

    这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存  
         当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中
 

       void *shmat(int shmid, void *addr, int flag); 
     shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。 

      使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。 


   4 信号量 
   信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作: 
   (1) 测试控制该资源的信号量。 
   (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。 
   (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。 
   (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。 

  
  
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/sem.h>
  4. int semget(key_t key, int nsems, int flag);
  5. struct sem {
  6. short sempid;/* pid of last operaton */
  7. ushort semval;/* current value */
  8. ushort semncnt;/* num procs awaiting increase in semval */
  9. ushort semzcnt;/* num procs awaiting semval = 0 */
  10. }  

       key是前面讲过的IPC结构的关键字,flag将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新 集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。 

   semctl函数用来对信号量进行操作。 

   int semctl(int semid, int semnum, int cmd, union semun arg); 
   不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。 
   
    
     semop函数自动执行信号量集合上的操作数组。 

   int semop(int semid, struct sembuf semoparray[], size_t nops); 
   semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量


  
  
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/sem.h>
  4. #include <sys/ipc.h>
  5. void main() 
  6. {
  7.     key_t unique_key; /* 定义一个IPC关键字*/
  8.     int id;
  9.     struct sembuf lock_it;
  10.     union semun options;
  11.     int i;
  12.     unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
  13.     
  14.     /* 创建一个新的信号量集合*/
  15.     id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
  16.     printf("semaphore id=%d\n", id);
  17.     options.val = 1; /*设置变量值*/
  18.     semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/
  19.     /*打印出信号量的值*/
  20.     i = semctl(id, 0, GETVAL, 0);
  21.     printf("value of semaphore at index 0 is %d\n", i);
  22.     /*下面重新设置信号量*/
  23.     lock_it.sem_num = 0; /*设置哪个信号量*/
  24.     lock_it.sem_op = -1; /*定义操作*/
  25.     lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
  26.     if (semop(id, &lock_it, 1) == -1) {
  27.         printf("can not lock semaphore.\n");
  28.         exit(1);
  29.     }
  30.     i = semctl(id, 0, GETVAL, 0);
  31.     printf("value of semaphore at index 0 is %d\n", i);
  32.     /*清除信号量*/
  33.     semctl(id, 0, IPC_RMID, 0);
  34. }

         可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:

            intsemget(key_t key,int nsems,int semflg);
     下面是一个打开和创建信号量集的程序:
    
    
  1. intopen_semaphore_set(key_t keyval,int numsems)
  2. {
  3.     intsid;
  4.     if(!numsems)
  5.         return(-1);
  6.     if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
  7.     {
  8.         return(-1);
  9.     }
  10.     return(sid);
  11. }

        系统调用:semop();
        调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);

        返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)

系统调用:semctl();

原型:int semctl(int semid,int semnum,int cmd,union semunarg);

返回值:如果成功,则为一个正数。如果失败,则为-1:errno=EACCESS(权限不够)







++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


Linux pipe 源码分析


     管道pipe作为Unix中历史最悠久的IPC机制,存在各个版本的Unix中,主要用于父子进程之间的通信(使用fork,从而子进程会获得父进程的打开文件表),pipe()系统调用底层的实现就相当于一个特殊的文件系统,每次调用的时候创建一个inode关联着两个file,一个用于读,一个用于写,从而实现数据的单向流动。


用户层API:
  1. #include <unistd.h>  
  2.   
  3.       int pipe(int pipefd[2]);  
  4.   
  5.       #define _GNU_SOURCE             /* See feature_test_macros(7) */  
  6.       #include <unistd.h>  
  7.   
  8.       int pipe2(int pipefd[2], int flags);  


内核源码路径如下:
  1. // sys_pipe(.......)  
  2. SYSCALL_DEFINE1(pipe, int __user *, fildes)  
  3. {  
  4.      return sys_pipe2(fildes, 0);  
  5. }  
  6.   
  7. SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)  
  8. {  
  9.      struct file *files[2];  
  10.      int fd[2];  
  11.      int error;  
  12.      // 核心是do_pipe  
  13.      error = __do_pipe_flags(fd, files, flags);  
  14.      if (!error) {  
  15.           // 一切准备就绪后 把刚才和管道关联的2个fd拷贝到用户空间  
  16.           if (unlikely(copy_to_user(fildes, fd, sizeof(fd)))) {  
  17.                fput(files[0]);  
  18.                fput(files[1]);  
  19.                put_unused_fd(fd[0]);  
  20.                put_unused_fd(fd[1]);  
  21.                error = -EFAULT;  
  22.           } else {  
  23.                // 把fd和file的映射关系更新到该进程的文件描述表中fdtable  
  24.                fd_install(fd[0], files[0]);  
  25.                fd_install(fd[1], files[1]);  
  26.           }  
  27.      }  
  28.      return error;  
  29. }  
  30.   
  31. static int __do_pipe_flags(int *fd, struct file **files, int flags)  
  32. {  
  33.      int error;  
  34.      int fdw, fdr;  
  35.   
  36.      if (flags & ~(O_CLOEXEC | O_NONBLOCK | O_DIRECT))  
  37.           return -EINVAL;  
  38.      // 为该管道创建俩struct file  
  39.      error = create_pipe_files(files, flags);  
  40.      if (error)  
  41.           return error;  
  42.      // 获得两个能用的文件描述符  
  43.      error = get_unused_fd_flags(flags);  
  44.      if (error < 0)  
  45.           goto err_read_pipe;  
  46.      fdr = error;  
  47.   
  48.      error = get_unused_fd_flags(flags);  
  49.      if (error < 0)  
  50.           goto err_fdr;  
  51.      fdw = error;  
  52.   
  53.      audit_fd_pair(fdr, fdw);  
  54.      fd[0] = fdr;  
  55.      fd[1] = fdw;  
  56.      return 0;  
  57.   
  58. err_fdr:  
  59.      put_unused_fd(fdr);  
  60. err_read_pipe:  
  61.      fput(files[0]);  
  62.      fput(files[1]);  
  63.      return error;  
  64. }  
  65.   
  66.   
  67. /* 
  68. * 为管道创建两个file实例 
  69. */  
  70. int create_pipe_files(struct file **res, int flags)  
  71. {  
  72.      int err;  
  73.      // 为pipe创建一个inode并做一定的初始化  
  74.      struct inode *inode = get_pipe_inode();  
  75.      struct file *f;  
  76.      struct path path;  
  77.      static struct qstr name = { .name = "" }; // quick string ??  
  78.   
  79.      if (!inode)  
  80.           return -ENFILE;  
  81.   
  82.      err = -ENOMEM;  
  83.      // 分配一个directory entry  
  84.      path.dentry = d_alloc_pseudo(pipe_mnt->mnt_sb, &name);  
  85.      if (!path.dentry)  
  86.           goto err_inode;  
  87.      path.mnt = mntget(pipe_mnt);  // 引用计数加1  
  88.   
  89.      d_instantiate(path.dentry, inode);  
  90.   
  91.      err = -ENFILE;  
  92.      f = alloc_file(&path, FMODE_WRITE, &pipefifo_fops);  
  93.      if (IS_ERR(f))  
  94.           goto err_dentry;  
  95.   
  96.      f->f_flags = O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT));  
  97.      f->private_data = inode->i_pipe;  
  98.      // 所以你会明白 fd[0]是读 fd[1]是写  
  99.      res[0] = alloc_file(&path, FMODE_READ, &pipefifo_fops);  
  100.      if (IS_ERR(res[0]))  
  101.           goto err_file;  
  102.   
  103.      path_get(&path);  
  104.      res[0]->private_data = inode->i_pipe;  
  105.      res[0]->f_flags = O_RDONLY | (flags & O_NONBLOCK);  
  106.      res[1] = f;  
  107.      return 0;  
  108.   
  109. err_file:  
  110.      put_filp(f);  
  111. err_dentry:  
  112.      free_pipe_info(inode->i_pipe);  
  113.      path_put(&path);  
  114.      return err;  
  115.   
  116. err_inode:  
  117.      free_pipe_info(inode->i_pipe);  
  118.      iput(inode);  
  119.      return err;  
  120. }  
  121.   
  122.   
  123. static struct inode * get_pipe_inode(void)  
  124. {  
  125.      struct inode *inode = new_inode_pseudo(pipe_mnt->mnt_sb);  
  126.      struct pipe_inode_info *pipe;  
  127.   
  128.      if (!inode)  
  129.           goto fail_inode;  
  130.      // 分配一个inode号  
  131.      inode->i_ino = get_next_ino();  
  132.      // 分配一个pipe的内核级对象  
  133.      pipe = alloc_pipe_info();  
  134.      if (!pipe)  
  135.           goto fail_iput;  
  136.   
  137.      inode->i_pipe = pipe;  
  138.      pipe->files = 2;  
  139.      pipe->readers = pipe->writers = 1;  
  140.      inode->i_fop = &pipefifo_fops;  
  141.   
  142.      /* 
  143.      * Mark the inode dirty from the very beginning, 
  144.      * that way it will never be moved to the dirty 
  145.      * list because "mark_inode_dirty()" will think 
  146.      * that it already _is_ on the dirty list. 
  147.      */  
  148.      inode->i_state = I_DIRTY;  
  149.      inode->i_mode = S_IFIFO | S_IRUSR | S_IWUSR;  
  150.      inode->i_uid = current_fsuid();  
  151.      inode->i_gid = current_fsgid();  
  152.      inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;  
  153.   
  154.      return inode;  
  155.   
  156. fail_iput:  
  157.      iput(inode);  
  158.   
  159. fail_inode:  
  160.      return NULL;  
  161. }  
  162.   
  163.   
  164. // 针对pipe的文件操作实例  
  165. const struct file_operations pipefifo_fops = {  
  166.      .open          = fifo_open,  
  167.      .llseek          = no_llseek,  
  168.      .read          = new_sync_read,  
  169.      .read_iter     = pipe_read,  
  170.      .write          = new_sync_write,  
  171.      .write_iter     = pipe_write,  
  172.      .poll          = pipe_poll,  
  173.      .unlocked_ioctl     = pipe_ioctl,  
  174.      .release     = pipe_release,  
  175.      .fasync          = pipe_fasync,  
  176. };  



整体的逻辑图可以这样:


TODO:具体读写的实现细节new_sync_read/write()有待分析。


参考:
(1)Linux kernel 3.18 source code 
(2)Linux man page
(3)Linux内核源码情景分析

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Linux的inode的理解


文件名 -> inode -> device block 

 

转自:
http://www.ruanyifeng.com/blog/2011/12/inode.html
http://blog.s135.com/post/295/ 
http://hi.baidu.com/leejun_2005/blog/item/d9aa13a53b3af6e99152ee7e.html 

 

一、inode是什么?

理解inode,要从文件储存说起。
文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。

 

操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。

 

文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。

 

二、inode的内容
inode包含文件的元信息,具体来说有以下内容:
  * 文件的字节数
  * 文件拥有者的User ID
  * 文件的Group ID
  * 文件的读、写、执行权限
  * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
  * 链接数,即有多少文件名指向这个inode
  * 文件数据block的位置

 

可以用stat命令,查看某个文件的inode信息:
stat example.txt


总之,除了文件名以外的所有文件信息,都存在inode之中。至于为什么没有文件名,下文会有详细解释。

 

三、inode的大小
inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

 

查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令。
df -i
查看每个inode节点的大小,可以用如下命令:
sudo dumpe2fs -h /dev/hda | grep "Inode size"
由于每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。

 

四、inode号码
每个inode都有一个号码,操作系统用inode号码来识别不同的文件。

 

这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。
 

使用ls -i命令,可以看到文件名对应的inode号码:

ls -i example.txt

 

五、目录文件
Unix/Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。

 

目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。

 

ls命令只列出目录文件中的所有文件名:
ls /etc
ls -i命令列出整个目录文件,即文件名和inode号码:
ls -i /etc
如果要查看文件的详细信息,就必须根据inode号码,访问inode节点,读取信息。ls -l命令列出文件的详细信息。
ls -l /etc

 

六、硬链接
一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。
 

ln命令可以创建硬链接:

ln 源文件 目标文件
运行上面这条命令以后,源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中有一项叫做"链接数",记录指向该inode的文件名总数,这时就会增加1。反过来,删除一个文件名,就会使得inode节点中的"链接数"减1。当这个值减到0,表明没有文件名指向这个inode,系统就会回收这个inode号码,以及其所对应block区域。

 

这里顺便说一下目录文件的"链接数"。创建目录时,默认会生成两个目录项:"."和".."。前者的inode号码就是当前目录的inode号码,等同于当前目录的"硬链接";后者的inode号码就是当前目录的父目录的inode号码,等同于父目录的"硬链接"。所以,任何一个目录的"硬链接"总数,总是等于2加上它的子目录总数(含隐藏目录),这里的2是父目录对其的“硬链接”和当前目录下的".硬链接“。

 

七、软链接
除了硬链接以外,还有一种特殊情况。文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。

 

这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。

 

ln -s命令可以创建软链接。
ln -s 源文文件或目录 目标文件或目录

 

八、inode的特殊作用
由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象。
  1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用。
  2. 移动文件或重命名文件,只是改变文件名,不影响inode号码。
  3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。
      第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。

 

九 实际问题

在一台配置较低的Linux服务器(内存、硬盘比较小)的/data分区内创建文件时,系统提示磁盘空间不足,用df -h命令查看了一下磁盘使用情况,发现/data分区只使用了66%,还有12G的剩余空间,按理说不会出现这种问题。  后来用df -i查看了一下/data分区的索引节点(inode),发现已经用满(IUsed=100%),导致系统无法创建新目录和文件。 

 

 

查找原因:

  /data/cache目录中存在数量非常多的小字节缓存文件,占用的Block不多,但是占用了大量的inode。 

 

解决方案:
  1、删除/data/cache目录中的部分文件,释放出/data分区的一部分inode。
  2、用软连接将空闲分区/opt中的newcache目录连接到/data/cache,使用/opt分区的inode来缓解/data分区inode不足的问题:
   ln -s /opt/newcache /data/cache 

 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值