posts - 134,comments - 22,trackbacks - 0

          來源: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庫的源代碼。
          另外,這是一個測試版的文章,請各位朋友們多提意見和建議,非常感謝!
          http://blog.csdn.net/lphpc/category/170686.aspx

          posted on 2010-08-06 10:00 何克勤 閱讀(222) 評論(0)  編輯  收藏 所屬分類: GNU Linux/Unix
          主站蜘蛛池模板: 颍上县| 威信县| 千阳县| 台州市| 霍邱县| 四川省| 涟水县| 崇左市| 阿城市| 镇赉县| 同仁县| 集安市| 三河市| 芦溪县| 新和县| 永吉县| 景洪市| 略阳县| 永寿县| 信阳市| 镇平县| 芒康县| 天全县| 印江| 酉阳| 庆城县| 新津县| 长汀县| 玉山县| 普兰店市| 吕梁市| 昂仁县| 宜章县| 鄂尔多斯市| 台北县| 闽侯县| 永平县| 东乌珠穆沁旗| 涿州市| 扬中市| 浮梁县|