乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Linux下的C編程實(shí)戰(zhàn)(開發(fā)平臺(tái)搭建,文件系統(tǒng)編程,進(jìn)程控制與進(jìn)程通信編程,“線程”控制與“線程”通信編程,驅(qū)動(dòng)程序設(shè)計(jì),專家問答)

       leon0821 2014-04-24

      Linux下的C編程實(shí)戰(zhàn)(一)

      ――開發(fā)平臺(tái)搭建


      1.引言

          Linux操作系統(tǒng)在服務(wù)器領(lǐng)域的應(yīng)用和普及已經(jīng)有較長的歷史,這源于它的開源特點(diǎn)以及其超越Windows的安全性和穩(wěn)定性。而近年來,Linux操作系統(tǒng)在嵌入式系統(tǒng)領(lǐng)域的延伸也可謂是如日中天,許多版本的嵌入式Linux系統(tǒng)被開發(fā)出來,如ucLinux、RTLinux、ARM-Linux等等。在嵌入式操作系統(tǒng)方面,Linux的地位是不容懷疑的,它開源、它包含TCP/IP協(xié)議棧、它易集成GUI。

          鑒于Linux操作系統(tǒng)在服務(wù)器和嵌入式系統(tǒng)領(lǐng)域愈來愈廣泛的應(yīng)用,社會(huì)上越來越需要基于Linux操作系統(tǒng)進(jìn)行編程的開發(fā)人員。瀏覽許多論壇,經(jīng)常碰到這樣的提問:“現(xiàn)在是不是很流行unix/linux下的c編程?所以想學(xué)習(xí)一下!但是不知道該從何學(xué)起,如何下手!有什么好的建議嗎?各位高手!哪些書籍比較合適初學(xué)者?在深入淺出的過程中應(yīng)該看哪些不同層次的書?比如好的網(wǎng)站、論壇請(qǐng)大家賜教!不慎感激!”鑒于讀者的需求,在本文中,筆者將對(duì)Linux平臺(tái)下C編程的幾個(gè)方面進(jìn)行實(shí)例講解,并力求回答讀者們關(guān)心的問題,以與讀者朋友們進(jìn)行交流,共同提高。在本文的連載過程中,有任何問題或建議,您可以給筆者發(fā)送email:21cnbao@21cn.com,您也可以進(jìn)入筆者的博客參與討論:

      http://blog.donews.com/21cnbao。筆者建議在PC內(nèi)存足夠大的情況下,不要直接安裝Linux操作系統(tǒng),最好把它安裝在運(yùn)行VMWare虛擬機(jī)軟件的Windows平臺(tái)上.

          在Linux平臺(tái)下,可用任意一個(gè)文本編輯工具編輯源代碼,但筆者建議使用emacs軟件,它具備語法高亮、版本控制等附帶功能.

       

      2.GCC編譯器

          GCC是Linux平臺(tái)下最重要的開發(fā)工具,它是GNU的C和C++編譯器,其基本用法為:

      gcc [options] [filenames]

      options為編譯選項(xiàng),GCC總共提供的編譯選項(xiàng)超過100個(gè),但只有少數(shù)幾個(gè)會(huì)被頻繁使用,   我們僅對(duì)幾個(gè)常用選項(xiàng)進(jìn)行介紹。

      假設(shè)我們編譯一輸出“Hello World”的程序:

      /* Filename:helloworld.c */

      main()

      {

          printf("HelloWorld"n");

      }

      最簡單的編譯方法是不指定任何編譯選項(xiàng):

      gcc helloworld.c

      它會(huì)為目標(biāo)程序生成默認(rèn)的文件名a.out,我們可用-o編譯選項(xiàng)來為將產(chǎn)生的可執(zhí)行文件指定一個(gè)文件名來代替a.out。例如,將上述名為helloworld.c的C程序編譯為名叫helloworld的可執(zhí)行文件,需要輸入如下命令:

      gcc –o helloworld helloworld.c

      -c選項(xiàng)告訴GCC僅把源代碼編譯為目標(biāo)代碼而跳過匯編和連接的步驟;

      -S 編譯選項(xiàng)告訴GCC 在為 C代碼產(chǎn)生了匯編語言文件后停止編譯。

      GCC 產(chǎn)生的匯編語言文件的缺省擴(kuò)展名是.s,上述程序運(yùn)行如下命令:

      gcc –S helloworld.c將生成helloworld.c的匯編代碼,使用的是AT&T匯編。

      -E選項(xiàng)指示編譯器僅對(duì)輸入文件進(jìn)行預(yù)處理。當(dāng)這個(gè)選項(xiàng)被使用時(shí),預(yù)處理器的輸出被送到標(biāo)準(zhǔn)輸出(默認(rèn)為屏幕)而不是儲(chǔ)存在文件里。

      -O選項(xiàng)告訴GCC對(duì)源代碼進(jìn)行基本優(yōu)化從而使得程序執(zhí)行地更快;而-O2選項(xiàng)告訴GCC產(chǎn)生盡可能小和盡可能快的代碼。使用-O2選項(xiàng)編譯的速度比使用-O時(shí)慢,但產(chǎn)生的代碼執(zhí)行速度會(huì)更快。

      -g選項(xiàng)告訴GCC產(chǎn)生能被GNU調(diào)試器使用的調(diào)試信息以便調(diào)試你的程序,可喜的是,在GCC里,我們能聯(lián)用-g和-O (產(chǎn)生優(yōu)化代碼)。

      -pg選項(xiàng)告訴GCC在你的程序里加入額外的代碼,執(zhí)行時(shí),產(chǎn)生gprof用的剖析信息以顯示你的程序的耗時(shí)情況。

       

      3.GDB調(diào)試器

          GCC用于編譯程序,而Linux的另一個(gè)GNU工具gdb則用于調(diào)試程序。gdb是一個(gè)用來調(diào)試C和C++程序的強(qiáng)力調(diào)試器,我們能通過它進(jìn)行一系列調(diào)試工作,包括設(shè)置斷點(diǎn)、觀查變量、單步等。

      其最常用的命令如下:

      file:裝入想要調(diào)試的可執(zhí)行文件。

      kill:終止正在調(diào)試的程序。

      list:列表顯示源代碼。

      next:執(zhí)行一行源代碼但不進(jìn)入函數(shù)內(nèi)部。

      step:執(zhí)行一行源代碼而且進(jìn)入函數(shù)內(nèi)部。

      run:執(zhí)行當(dāng)前被調(diào)試的程序

      quit:終止gdb

      watch:監(jiān)視一個(gè)變量的值

      break:在代碼里設(shè)置斷點(diǎn),程序執(zhí)行到這里時(shí)掛起

      make:不退出gdb而重新產(chǎn)生可執(zhí)行文件

      shell:不離開gdb而執(zhí)行shell

      下面我們來演示怎樣用GDB來調(diào)試一個(gè)求0+1+2+3+…+99的程序:

      /* Filename:sum.c */

      main()

      {

        int i, sum;

       

        sum = 0;

        for (i = 0; i < 100;i++)

        {

          sum +  =i;

        }

       

        printf("the sum of 1+2+...+ is %d",sum);

      }

      執(zhí)行如下命令編譯sum.c(加-g選項(xiàng)產(chǎn)生debug信息):

      gcc –g –o sum sum.c

      在命令行上鍵入gdb sum并按回車鍵就可以開始調(diào)試sum了,再運(yùn)行run命令執(zhí)行sum,屏幕上將看到如下內(nèi)容:


      list命令:

      list命令用于列出源代碼,對(duì)上述程序兩次運(yùn)行l(wèi)ist,將出現(xiàn)如下畫面(源代碼被標(biāo)行號(hào)):

       

      根據(jù)列出的源程序,如果我們將斷點(diǎn)設(shè)置在第5行,只需在gdb 命令行提示符下鍵入如下命令設(shè)置斷點(diǎn):(gdb) break 5,執(zhí)行情況如下圖:

       

      這個(gè)時(shí)候我們?cè)賠un,程序會(huì)停止在第5行,如下圖:

       

      設(shè)置斷點(diǎn)的另一種語法是 break <function>,它在進(jìn)入指定函數(shù)(function)時(shí)停住。

      相反的,clear用于清除所有的已定義的斷點(diǎn),clear <function>清除設(shè)置在函數(shù)上的斷點(diǎn),  clear <linenum>則清除設(shè)置在指定行上的斷點(diǎn)

      。

      watch命令:

      watch命令用于觀查變量或表達(dá)式的值,我們觀查sum變量只需要運(yùn)行watch sum:


      watch <expr>為表達(dá)式(變量)expr設(shè)置一個(gè)觀察點(diǎn),一量表達(dá)式值有變化時(shí),程序會(huì)停止執(zhí)行。

      要觀查當(dāng)前設(shè)置的watch,可以使用info watchpoints命令。

      next、step命令:

      next、step用于單步執(zhí)行,在執(zhí)行的過程中,被watch變量的變化情況將實(shí)時(shí)呈現(xiàn)(分別顯示Old value和New value),如下圖:

       

      next、step命令的區(qū)別在于step遇到函數(shù)調(diào)用,會(huì)跳轉(zhuǎn)到到該函數(shù)定義的開始行去執(zhí)行,而next則不進(jìn)入到函數(shù)內(nèi)部,它把函數(shù)調(diào)用語句當(dāng)作一條普通語句執(zhí)行。

       

      4.Make

          make是所有想在Linux系統(tǒng)上編程的用戶必須掌握的工具,對(duì)于任何稍具規(guī)模的程序,我們都會(huì)使用到make,幾乎可以說不使用make的程序不具備任何實(shí)用價(jià)值。在此,我們有必要解釋編譯和連接的區(qū)別。編譯器使用源碼文件來產(chǎn)生某種形式的目標(biāo)文件(object files),在編譯過程中,外部的符號(hào)參考并沒有被解釋或替換(即外部全局變量和函數(shù)并沒有被找到)。因此,在編譯階段所報(bào)的錯(cuò)誤一般都是語法錯(cuò)誤。而連接器則用于連接目標(biāo)文

      件和程序包,生成一個(gè)可執(zhí)行程序。在連接階段,一個(gè)目標(biāo)文件中對(duì)別的文件中的符號(hào)的參考被解釋,如果有符號(hào)不能找到,會(huì)報(bào)告連接錯(cuò)誤。

          編譯和連接的一般步驟是:第一階段把源文件一個(gè)一個(gè)的編譯成目標(biāo)文件,第二階段把所有的目標(biāo)文件加上需要的程序包連接成一個(gè)可執(zhí)行文件。這樣的過程很痛苦,我們需要使用大量的gcc命令。而make則使我們從大量源文件的編譯和連接工作中解放出來,綜合為一步完成。GNU Make的主要工作是讀進(jìn)一個(gè)文本文件,稱為makefile。這個(gè)文件記錄了哪些文件(目的文件,目的文件不一定是最后的可執(zhí)行程序,它可以是任何一種文件)由哪些文件(依靠文件)產(chǎn)生,用什么命令來產(chǎn)生。Make依靠此makefile中的信息檢查磁盤上的文件,如果目的文件的創(chuàng)建或修改時(shí)間比它的一個(gè)依靠文件舊的話,make就執(zhí)行相應(yīng)的命令,以便更新目的文件。

          假設(shè)我們寫下如下的三個(gè)文件,add.h用于聲明add函數(shù),add.c提供兩個(gè)整數(shù)相加的函數(shù)體,而main.c中調(diào)用add函數(shù):

      /* filename:add.h */

      extern int add(int i, int j);


       

      /* filename:add.c */

      int add(int i, int j)

      {

        return i + j;

      };


       

      /* filename:main.c */

      #include "add.h"

      main()

      {

        int a, b;

        a = 2;

        b = 3;

        printf("the sum of a+b is %d", add(a +b));

      };


      怎樣為上述三個(gè)文件產(chǎn)生makefile呢?如下:
      -------------------------
      test : main.o add.o
          gcc main.o add.o -o test
       
      main.o : main.c add.h
          gcc -c main.c -o main.o
       
      add.o : add.c add.h
          gcc -c add.c -o add.o 
      -----------------------

      (注意分割符為TAB鍵)

      上述makefile利用add.c和add.h文件執(zhí)行g(shù)cc -c add.c -o add.o命令產(chǎn)生add.o目標(biāo)代碼,利用main.c和add.h文件執(zhí)行g(shù)cc-c main.c -o

      main.o命令產(chǎn)生main.o目標(biāo)代碼,最后利用main.o和add.o文件(兩個(gè)模塊的目標(biāo)代碼)執(zhí)行g(shù)cc main.o add.o -o test命令產(chǎn)生可執(zhí)行文件test。

          我們可在makefile中加入變量,另外。環(huán)境變量在make過程中也被解釋成make的變量。這些變量是大小寫敏感的,一般使用大寫字母。Make變量可以做很多事情,例如:

      i) 存儲(chǔ)一個(gè)文件名列表;

      ii) 存儲(chǔ)可執(zhí)行文件名;

      iii) 存儲(chǔ)編譯器選項(xiàng)。

      要定義一個(gè)變量,只需要在一行的開始寫下這個(gè)變量的名字,后面跟一個(gè)=號(hào),再跟變量的值。引用變量的方法是寫一個(gè)$符號(hào),后面跟(變量名)。我們把前面的 makefile 利用變量重寫一遍(并假設(shè)使用-Wall -O –g編譯選項(xiàng)):     

      OBJS = main.o add.o

      CC = gcc

      CFLAGS = -Wall -O -g

          

      test : $(OBJS)

      $(CC) $(OBJS) -o test

          

      main.o : main.c add.h

      $(CC) $(CFLAGS) -c main.c -o main.o

          

      add.o : add.c add.h

      $(CC) $(CFLAGS) -c add.c -o add.o

      makefile 中還可定義清除(clean)目標(biāo),可用來清除編譯過程中產(chǎn)生的中間文件,例如在上述makefile文件中添加下列代碼:

      clean:

      rm -f *.o

      運(yùn)行make clean時(shí),將執(zhí)行rm -f *.o命令,刪除所有編譯過程中產(chǎn)生的中間文件。

      不管怎么說,自己動(dòng)手編寫makefile仍然是很復(fù)雜和煩瑣的,而且很容易出錯(cuò)。因此,GNU也為我們提供了Automake和Autoconf來輔助快速自動(dòng)產(chǎn)生makefile,讀者可以參閱相關(guān)資料。

       

      5.小結(jié)

      本章主要闡述了Linux程序的編寫、編譯、調(diào)試方法及make,實(shí)際上就是引導(dǎo)讀者學(xué)習(xí)怎樣在Linux下編程,為后續(xù)章節(jié)做好準(zhǔn)備。

       

       


      Linux下的C編程實(shí)戰(zhàn)(二)

      ――文件系統(tǒng)編程

       

      1.Linux文件系統(tǒng)

          Linux支持多種文件系統(tǒng),如ext、ext2、minix、iso9660、msdos、fat、vfat、nfs等。在這些具體文件系統(tǒng)的上層,Linux提供了虛擬文件系統(tǒng)(VFS)來統(tǒng)一它們的行為,虛擬文件系統(tǒng)為不同的文件系統(tǒng)與內(nèi)核的通信提供了一致的接口。

          在Linux平臺(tái)下對(duì)文件編程可以使用兩類函數(shù):

      (1)Linux操作系統(tǒng)文件API;

      (2)C語言I/O庫函數(shù)。   

      前者依賴于Linux系統(tǒng)調(diào)用,后者實(shí)際上與操作系統(tǒng)是獨(dú)立的,因?yàn)樵谌魏尾僮飨到y(tǒng)下,使用C語言I/O庫函數(shù)操作文件的方法都是相同的。本章將對(duì)這兩種方法進(jìn)行實(shí)例講解。

       

      2.Linux文件API

          Linux的文件操作API涉及到創(chuàng)建、打開、讀寫和關(guān)閉文件。

      創(chuàng)建

      int creat(const char *filename, mode_t mode);

      參數(shù)mode指定新建文件的存取權(quán)限,它同umask一起決定文件的最終權(quán)限(mode&umask),其中umask代表了文件在創(chuàng)建時(shí)需要去掉的一些存取權(quán)限。umask可通過系統(tǒng)調(diào)用umask()來改變:

      int umask(int newmask);

      該調(diào)用將umask設(shè)置為newmask,然后返回舊的umask,它只影響讀、寫和執(zhí)行權(quán)限。

       

      打開

      int open(const char *pathname, int flags);

      int open(const char *pathname, int flags, mode_t mode);

      open函數(shù)有兩個(gè)形式,其中pathname是我們要打開的文件名(包含路徑名稱,缺省是認(rèn)為在當(dāng)前路徑下面),

      flags可以去下面的一個(gè)值或者是幾個(gè)值的組合:

      標(biāo)志
       含義
       
      O_RDONLY
       以只讀的方式打開文件
       
      O_WRONLY
       以只寫的方式打開文件
       
      O_RDWR
       以讀寫的方式打開文件
       
      O_APPEND
       以追加的方式打開文件
       
      O_CREAT
       創(chuàng)建一個(gè)文件
       
      O_EXEC
       如果使用了O_CREAT而且文件已經(jīng)存在,就會(huì)發(fā)生一個(gè)錯(cuò)誤
       
      O_NOBLOCK
       以非阻塞的方式打開一個(gè)文件
       
      O_TRUNC
       如果文件已經(jīng)存在,則刪除文件的內(nèi)容
       

      O_RDONLY、O_WRONLY、O_RDWR三個(gè)標(biāo)志只能使用任意的一個(gè)。

      如果使用了O_CREATE標(biāo)志,則使用的函數(shù)是int open(const char*pathname,int flags,mode_t mode); 這個(gè)時(shí)候我們還要指定mode標(biāo)志,用來表示文件的訪問權(quán)限。mode可以是以下情況的組合:

      標(biāo)志
       含義
       
      S_IRUSR
       用戶可以讀
       
      S_IWUSR
       用戶可以寫
       
      S_IXUSR
       用戶可以執(zhí)行
       
      S_IRWXU
       用戶可以讀、寫、執(zhí)行
       
      S_IRGRP
       組可以讀
       
      S_IWGRP
       組可以寫
       
      S_IXGRP
       組可以執(zhí)行
       
      S_IRWXG
       組可以讀寫執(zhí)行
       
      S_IROTH
       其他人可以讀
       
      S_IWOTH
       其他人可以寫
       
      S_IXOTH
       其他人可以執(zhí)行
       
      S_IRWXO
       其他人可以讀、寫、執(zhí)行
       
      S_ISUID
       設(shè)置用戶執(zhí)行ID
       
      S_ISGID
       設(shè)置組的執(zhí)行ID
       

      除了可以通過上述宏進(jìn)行“或”邏輯產(chǎn)生標(biāo)志以外,我們也可以自己用數(shù)字來表示,Linux總共用5個(gè)數(shù)字來表示文件的各種權(quán)限:第一位表示設(shè)置用戶ID;第二位表示設(shè)置組ID;第三位表示用戶自己的權(quán)限位;第四位表示組的權(quán)限;最后一位表示其他人的權(quán)限。每個(gè)數(shù)字可以取1(執(zhí)行權(quán)限)、2(寫權(quán)限)、4(讀權(quán)限)、0(無)或者是這些值的和。例如,要?jiǎng)?chuàng)建一個(gè)用戶可讀、可寫、可執(zhí)行,但是組沒有權(quán)限,其他人可以讀、可以執(zhí)行的文件,并設(shè)置用戶ID位。那么,我們應(yīng)該使用的模式是1(設(shè)置用戶ID)、0(不設(shè)置組ID)、7(1+2+4,讀、寫、執(zhí)行)、0(沒有權(quán)限)、

      5(1+4,讀、執(zhí)行)即10705:

      open("test", O_CREAT, 10705);

      上述語句等價(jià)于:

      open("test", O_CREAT, S_IRWXU | S_IROTH |S_IXOTH | S_ISUID );

      如果文件打開成功,open函數(shù)會(huì)返回一個(gè)文件描述符,以后對(duì)該文件的所有操作就可以通過對(duì)這個(gè)文件描述符進(jìn)行操作來實(shí)現(xiàn)。

       

      讀寫

      在文件打開以后,我們才可對(duì)文件進(jìn)行讀寫了,Linux中提供文件讀寫的系統(tǒng)調(diào)用是read、write函數(shù):

      int read(int fd, const void *buf, size_t length);

      int write(int fd, const void *buf, size_t length);

      其中參數(shù)buf為指向緩沖區(qū)的指針,length為緩沖區(qū)的大小(以字節(jié)為單位)。函數(shù)read()實(shí)現(xiàn)從文件描述符fd所指定的文件中讀取length個(gè)字節(jié)到buf所指向的緩沖區(qū)中,返回值為實(shí)際讀取的字節(jié)數(shù)。函數(shù)write實(shí)現(xiàn)將把length個(gè)字節(jié)從buf指向的緩沖區(qū)中寫到文件描述符fd所指向的文件中,返回值為實(shí)際寫入的字節(jié)數(shù)。

      以O(shè)_CREAT為標(biāo)志的open實(shí)際上實(shí)現(xiàn)了文件創(chuàng)建的功能,因此,下面的函數(shù)等同creat()函數(shù):

      int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);

       

      定位

      對(duì)于隨機(jī)文件,我們可以隨機(jī)的指定位置讀寫,使用如下函數(shù)進(jìn)行定位:

      int lseek(int fd, offset_t offset, int whence);

      lseek()將文件讀寫指針相對(duì)whence移動(dòng)offset個(gè)字節(jié)。操作成功時(shí),返回文件指針相對(duì)于文件頭的位置。

      參數(shù)whence可使用下述值:

      SEEK_SET:相對(duì)文件開頭

      SEEK_CUR:相對(duì)文件讀寫指針的當(dāng)前位置

      SEEK_END:相對(duì)文件末尾

      offset可取負(fù)值,例如下述調(diào)用可將文件指針相對(duì)當(dāng)前位置向前移動(dòng)5個(gè)字節(jié):

      lseek(fd, -5, SEEK_CUR);

      由于lseek函數(shù)的返回值為文件指針相對(duì)于文件頭的位置,因此下列調(diào)用的返回值就是文件的長度:

      lseek(fd, 0, SEEK_END);

       

      關(guān)閉

      當(dāng)我們操作完成以后,我們要關(guān)閉文件了,只要調(diào)用close就可以了,其中fd是我們要關(guān)閉的文件描述符:

      int close(int fd);

      例程:編寫一個(gè)程序,在當(dāng)前目錄下創(chuàng)建用戶可讀寫文件“hello.txt”,在其中寫入“Hello, software weekly”,關(guān)閉該文件。再次打開該文件,讀取其中的內(nèi)容并輸出在屏幕上。

       

      #include <sys/types.h>

      #include <sys/stat.h>

      #include <fcntl.h>

      #include <stdio.h>

      #define LENGTH 100

      main()
      {

        int fd, len;

        char str[LENGTH];

       

        /* 創(chuàng)建并打開文件 */

        fd = open("hello.txt", O_CREAT | O_RDWR,S_IRUSR | S_IWUSR); 

        if(fd)  
        {
          /* 寫入Hello, software weekly字符串 */  

           write(fd, "Hello,Software Weekly",strlen("Hello,software weekly")); 

           close(fd);
        }

        fd = open("hello.txt", O_RDWR);

        len = read(fd, str, LENGTH); /* 讀取文件內(nèi)容 */

        str[len] = '"0';

        printf("%s"n", str);

        close(fd);

      };


      編譯并運(yùn)行,執(zhí)行


      3.C語言庫函數(shù)

      C庫函數(shù)的文件操作實(shí)際上是獨(dú)立于具體的操作系統(tǒng)平臺(tái)的,不管是在DOS、Windows、Linux還是在VxWorks中都是這些函數(shù):

       

      創(chuàng)建和打開

      FILE *fopen(const char *path, const char *mode);

      fopen()實(shí)現(xiàn)打開指定文件filename,其中的mode為打開模式,C語言中支持的打開模式如下表:

      標(biāo)志
       含義
       
      r, rb
       以只讀方式打開
       
      w, wb
       以只寫方式打開。如果文件不存在,則創(chuàng)建該文件,否則文件被截?cái)?br>  
      a, ab
       以追加方式打開。如果文件不存在,則創(chuàng)建該文件
       
      r+, r+b, rb+
       以讀寫方式打開
       
      w+, w+b, wh+
       以讀寫方式打開。如果文件不存在時(shí),創(chuàng)建新文件,否則文件被截?cái)?br>  
      a+, a+b, ab+
       以讀和追加方式打開。如果文件不存在,創(chuàng)建新文件
       

          其中b用于區(qū)分二進(jìn)制文件和文本文件,這一點(diǎn)在DOS、Windows系統(tǒng)中是有區(qū)分的,但Linux不區(qū)分二進(jìn)制文件和文本文件。

            

      讀寫

      C庫函數(shù)支持以字符、字符串等為單位,支持按照某中格式進(jìn)行文件的讀寫,這一組函數(shù)為:

      int fgetc(FILE *stream);

      int fputc(int c, FILE *stream);

      char *fgets(char *s, int n, FILE *stream);

      int fputs(const char *s, FILE *stream);

      int fprintf(FILE *stream, const char *format, ...);

      int fscanf (FILE *stream, const char *format, ...);

      size_t fread(void *ptr, size_t size, size_t n, FILE*stream);

      size_t fwrite (const void *ptr, size_t size, size_t n,FILE *stream);

      fread()實(shí)現(xiàn)從流stream中讀取加n個(gè)字段,每個(gè)字段為size字節(jié),并將讀取的字段放入ptr所指的字符數(shù)組中,返回實(shí)際已讀取的字段數(shù)。在讀取的字段數(shù)小于num時(shí),可能是在函數(shù)調(diào)用時(shí)出現(xiàn)錯(cuò)誤,也可能是讀到文件的結(jié)尾。所以要通過調(diào)用feof()和ferror()來判斷。

      write()實(shí)現(xiàn)從緩沖區(qū)ptr所指的數(shù)組中把n個(gè)字段寫到流stream中,每個(gè)字段長為size個(gè)字節(jié),返回實(shí)際寫入的字段數(shù)。

       

      另外,C庫函數(shù)還提供了讀寫過程中的定位能力,這些函數(shù)包括

      int fgetpos(FILE *stream, fpos_t *pos);

      int fsetpos(FILE *stream, const fpos_t *pos);

      int fseek(FILE *stream, long offset, int whence);

      等。

       

      關(guān)閉

      利用C庫函數(shù)關(guān)閉文件依然是很簡單的操作:

      int fclose (FILE *stream);

      例程:將第2節(jié)中的例程用C庫函數(shù)來實(shí)現(xiàn)。

      #include <stdio.h>

      #define LENGTH 100

      main()

      {

        FILE *fd;

        char str[LENGTH];

       

        fd = fopen("hello.txt", "w+");/* 創(chuàng)建并打開文件 */

        if (fd)

        {

          fputs("Hello, SoftwareWeekly", fd); /* 寫入Hello,software weekly字符串 */

          fclose(fd);

        }

       

        fd = fopen("hello.txt", "r");

        fgets(str, LENGTH, fd); /* 讀取文件內(nèi)容 */

        printf("%s"n", str);

        fclose(fd);

      }

      4.小結(jié)

           Linux提供的虛擬文件系統(tǒng)為多種文件系統(tǒng)提供了統(tǒng)一的接口,Linux的文件編程有兩種途徑:基于Linux系統(tǒng)調(diào)用;基于C庫函數(shù)。這兩種編程所涉及到文件操作有新建、打開、讀寫和關(guān)閉,對(duì)隨機(jī)文件還可以定位。本章對(duì)這兩種編程方法都給出了具體的實(shí)例。

       

       

      Linux下的C編程實(shí)戰(zhàn)(三)

      ――進(jìn)程控制與進(jìn)程通信編程


      1.Linux進(jìn)程

          Linux進(jìn)程在內(nèi)存中包含三部分?jǐn)?shù)據(jù):代碼段、堆棧段和數(shù)據(jù)段。代碼段存放了程序的代碼。代碼段可以為機(jī)器中運(yùn)行同一程序的數(shù)個(gè)進(jìn)程共享。堆棧段存放的是子程序(函數(shù))的返回地址、子程序的參數(shù)及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量、常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc函數(shù)申請(qǐng)的內(nèi)存)。與代碼段不同,如果系統(tǒng)中同時(shí)運(yùn)行多個(gè)相同的程序,它們不能使用同一堆棧段和數(shù)據(jù)段。

          Linux進(jìn)程主要有如下幾種狀態(tài):

      用戶狀態(tài)(進(jìn)程在用戶狀態(tài)下運(yùn)行的狀態(tài))、

      內(nèi)核狀態(tài)(進(jìn)程在內(nèi)核狀態(tài)下運(yùn)行的狀態(tài))、

      內(nèi)存中就緒(進(jìn)程沒有執(zhí)行,但處于就緒狀態(tài),只要內(nèi)核調(diào)度它,就可以執(zhí)行)、

      內(nèi)存中睡眠(進(jìn)程正在睡眠并且處于內(nèi)存中,沒有被交換到SWAP設(shè)備)、

      就緒且換出(進(jìn)程處于就緒狀態(tài),但是必須把它換入內(nèi)存,內(nèi)核才能再次調(diào)度它進(jìn)行運(yùn)行)、睡眠且換出(進(jìn)程正在睡眠,且被換出內(nèi)存)、

      被搶先(進(jìn)程從內(nèi)核狀態(tài)返回用戶狀態(tài)時(shí),內(nèi)核搶先于它,做了上下文切換,調(diào)度了另一個(gè)       進(jìn)程,原先這個(gè)進(jìn)程就處于被搶先狀態(tài))、

      創(chuàng)建狀態(tài)(進(jìn)程剛被創(chuàng)建,該進(jìn)程存在,但既不是就緒狀態(tài),也不是睡眠狀態(tài),這個(gè)狀態(tài)是       除了進(jìn)程0以外的所有進(jìn)程的最初狀態(tài))、

      僵死狀態(tài)(進(jìn)程調(diào)用exit結(jié)束,進(jìn)程不再存在,但在進(jìn)程表項(xiàng)中仍有記錄,該記錄可由父    進(jìn)程收集)。

       

      下面我們來以一個(gè)進(jìn)程從創(chuàng)建到消亡的過程講解Linux進(jìn)程狀態(tài)轉(zhuǎn)換的“生死因果”。

      (1)進(jìn)程被父進(jìn)程通過系統(tǒng)調(diào)用fork創(chuàng)建而處于創(chuàng)建態(tài);

      (2)fork調(diào)用為子進(jìn)程配置好內(nèi)核數(shù)據(jù)結(jié)構(gòu)和子進(jìn)程私有數(shù)據(jù)結(jié)構(gòu)后,子進(jìn)程進(jìn)入就緒態(tài)(或者在內(nèi)存中就緒,或者因?yàn)閮?nèi)存不夠而在SWAP設(shè)備中就緒);

      (3)若進(jìn)程在內(nèi)存中就緒,進(jìn)程可以被內(nèi)核調(diào)度程序調(diào)度到CPU運(yùn)行;

      (4)內(nèi)核調(diào)度該進(jìn)程進(jìn)入內(nèi)核狀態(tài),再由內(nèi)核狀態(tài)返回用戶狀態(tài)執(zhí)行。該進(jìn)程在用戶狀態(tài)運(yùn)行一定時(shí)間后,又會(huì)被調(diào)度程序所調(diào)度而進(jìn)入內(nèi)核狀態(tài),由此轉(zhuǎn)入就緒態(tài)。有時(shí)進(jìn)程在用戶狀態(tài)運(yùn)行時(shí),也會(huì)因?yàn)樾枰獌?nèi)核服務(wù),使用系統(tǒng)調(diào)用而進(jìn)入內(nèi)核狀態(tài),服務(wù)完畢,會(huì)由內(nèi)核狀態(tài)轉(zhuǎn)回用戶狀態(tài)。要注意的是,進(jìn)程在從內(nèi)核狀態(tài)向用戶狀態(tài)返回時(shí)可能被搶占,這是由于有優(yōu)先級(jí)更高的進(jìn)程急需使用CPU,不能等到下一次調(diào)度時(shí)機(jī),從而造成搶占;

      (5)進(jìn)程執(zhí)行exit調(diào)用,進(jìn)入僵死狀態(tài),最終結(jié)束。

       

      2.進(jìn)程控制

          進(jìn)程控制中主要涉及到進(jìn)程的創(chuàng)建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的進(jìn)程創(chuàng)建方法,sleep的進(jìn)程睡眠和exit的進(jìn)程退出調(diào)用,另外Linux還提供了父進(jìn)程等待子進(jìn)程結(jié)束的系統(tǒng)調(diào)用wait。

       

      fork

      對(duì)于沒有接觸過Unix/Linux操作系統(tǒng)的人來說,fork是最難理解的概念之一,它執(zhí)行一次卻返回兩個(gè)值,完全“不可思議”。先看下面的程序:

      int main()
      {
        int i;
        if (fork() == 0)
        {
          for (i = 1; i < 3; i++)
            printf("This is childprocess"n");
        }
        else
        {
          for (i = 1; i < 3; i++)
            printf("This is parentprocess"n");
        }
      }

      執(zhí)行結(jié)果為:

      This is child process

      This is child process

      This is parent process

      This is parent process

      fork在英文中是“分叉”的意思,這個(gè)名字取得很形象。一個(gè)進(jìn)程在運(yùn)行中,如果使用了fork,就產(chǎn)生了另一個(gè)進(jìn)程,于是進(jìn)程就“分叉”了。當(dāng)前進(jìn)程為父進(jìn)程,通過fork()會(huì)產(chǎn)生一個(gè)子進(jìn)程。對(duì)于父進(jìn)程,fork函數(shù)返回子程序的進(jìn)程號(hào)而對(duì)于子程序,fork函數(shù)則返回零,這就是一個(gè)函數(shù)返回兩次的本質(zhì)。可以說,fork函數(shù)是Unix系統(tǒng)最杰出的成就之一,它是七十年代Unix早期的開發(fā)者經(jīng)過理論和實(shí)踐上的長期艱苦探索后取得的成果。

      如果我們把上述程序中的循環(huán)放的大一點(diǎn):

      int main()
      {
        int i;
        if (fork() == 0)
        {
          for (i = 1; i < 10000; i++)
            printf("This is childprocess"n");
        }
        else
        {
          for (i = 1; i < 10000; i++)
            printf("This is parentprocess"n");
        }
      };


      則可以明顯地看到父進(jìn)程和子進(jìn)程的并發(fā)執(zhí)行,交替地輸出“This ischild process”和“This is parent process”。

      此時(shí)此刻,我們還沒有完全理解fork()函數(shù),再來看下面的一段程序,看看究竟會(huì)產(chǎn)生多少個(gè)進(jìn)程,程序的輸出是什么?

      int main()
      {
        int i;
        for (i = 0; i < 2; i++)
        {
          if (fork() == 0)
          {
            printf("This is childprocess"n");
          }
          else
          {
            printf("This is parentprocess"n");
          }
        }
      };


      exec

      在Linux中可使用exec函數(shù)族,包含多個(gè)函數(shù)(execl、execlp、execle、execv、execve和execvp),被用于啟動(dòng)一個(gè)指定路徑和文件名的進(jìn)程。

      exec函數(shù)族的特點(diǎn)體現(xiàn)在:某進(jìn)程一旦調(diào)用了exec類函數(shù),正在執(zhí)行的程序就被干掉了,系統(tǒng)把代碼段替換成新的程序(由exec類函數(shù)執(zhí)行)的代碼,并且原有的數(shù)據(jù)段和堆棧段也被廢棄,新的數(shù)據(jù)段與堆棧段被分配,但是進(jìn)程號(hào)卻被保留。也就是說,exec執(zhí)行的結(jié)果為:系統(tǒng)認(rèn)為正在執(zhí)行的還是原先的進(jìn)程,但是進(jìn)程對(duì)應(yīng)的程序被替換了。

      fork函數(shù)可以創(chuàng)建一個(gè)子進(jìn)程而當(dāng)前進(jìn)程不死,如果我們?cè)趂ork的子進(jìn)程中調(diào)用exec函數(shù)族就可以實(shí)現(xiàn)既讓父進(jìn)程的代碼執(zhí)行又啟動(dòng)一個(gè)新的

      指定進(jìn)程,這實(shí)在是很妙的。fork和exec的搭配巧妙地解決了程序啟動(dòng)另一程序的執(zhí)行但自己仍繼續(xù)運(yùn)行的問題,請(qǐng)看下面的例子:

      char command[MAX_CMD_LEN];

      void main()
      {

        int rtn; /* 子進(jìn)程的返回?cái)?shù)值 */
        while (1)
        {
          /* 從終端讀取要執(zhí)行的命令 */
          printf(">");
          fgets(command, MAX_CMD_LEN, stdin);
          command[strlen(command) - 1] = 0;
          if (fork() == 0)
          {
            /* 子進(jìn)程執(zhí)行此命令 */
            execlp(command, command);
            /* 如果exec函數(shù)返回,表明沒有正常執(zhí)行命令,打印錯(cuò)誤信息*/
            perror(command);
            exit(errorno);
          }
          else
          {
            /* 父進(jìn)程,等待子進(jìn)程結(jié)束,并打印子進(jìn)程的返回值 */
            wait(&rtn);
            printf(" child process return%d"n", rtn);
          }
        }
      }


      這個(gè)函數(shù)基本上實(shí)現(xiàn)了一個(gè)shell的功能,它讀取用戶輸入的進(jìn)程名和參數(shù),并啟動(dòng)對(duì)應(yīng)的進(jìn)程。

       

      clone

      clone是Linux2.0以后才具備的新功能,它較fork更強(qiáng)(可認(rèn)為fork是clone要實(shí)現(xiàn)的一部分),可以使得創(chuàng)建的子進(jìn)程共享父進(jìn)程的資源,并且要使用此函數(shù)必須在編譯內(nèi)核時(shí)設(shè)置clone_actually_works_ok選項(xiàng)。

       

      clone函數(shù)的原型為:

      int clone(int (*fn)(void *), void *child_stack, intflags, void *arg);

      此函數(shù)返回創(chuàng)建進(jìn)程的PID,函數(shù)中的flags標(biāo)志用于設(shè)置創(chuàng)建子進(jìn)程時(shí)的相關(guān)選項(xiàng),具體含義如下表:

      標(biāo)志
       含義
       
      CLONE_PARENT
       創(chuàng)建的子進(jìn)程的父進(jìn)程是調(diào)用者的父進(jìn)程,新進(jìn)程與創(chuàng)建它的進(jìn)程成了“兄弟”而不是“父子”
       
      CLONE_FS
       子進(jìn)程與父進(jìn)程共享相同的文件系統(tǒng),包括root、當(dāng)前目錄、umask
       
      CLONE_FILES
       子進(jìn)程與父進(jìn)程共享相同的文件描述符(file descriptor)表
       
      CLONE_NEWNS
       在新的namespace啟動(dòng)子進(jìn)程,namespace描述了進(jìn)程的文件hierarchy
       
      CLONE_SIGHAND
       子進(jìn)程與父進(jìn)程共享相同的信號(hào)處理(signal handler)表
       
      CLONE_PTRACE
       若父進(jìn)程被trace,子進(jìn)程也被trace
       
      CLONE_VFORK
       父進(jìn)程被掛起,直至子進(jìn)程釋放虛擬內(nèi)存資源
       
      CLONE_VM
       子進(jìn)程與父進(jìn)程運(yùn)行于相同的內(nèi)存空間
       
      CLONE_PID
       子進(jìn)程在創(chuàng)建時(shí)PID與父進(jìn)程一致
       
      CLONE_THREAD
       Linux 2.4中增加以支持POSIX線程標(biāo)準(zhǔn),子進(jìn)程與父進(jìn)程共享相同的線程群
       

      來看下面的例子:

      int variable, fd;

      int do_something() {

         variable = 42;

         close(fd);

         _exit(0);

      }

       

      int main(int argc, char *argv[]) {

         void **child_stack;

         char tempch;

         variable = 9;

         fd = open("test.file", O_RDONLY);

         child_stack = (void **) malloc(16384);

         printf("The variable was%d"n", variable);

         clone(do_something, child_stack,CLONE_VM|CLONE_FILES, NULL);

         sleep(1);   /* 延時(shí)以便子進(jìn)程完成關(guān)閉文件操作、修改變量 */

         printf("The variable is now%d"n", variable);

         if (read(fd, &tempch, 1) < 1) {

            perror("File ReadError");

            exit(1);

         }

         printf("We could read from thefile"n");

         return 0;

      }

      運(yùn)行輸出:

      The variable is now 42

      File Read Error

      程序的輸出結(jié)果告訴我們,子進(jìn)程將文件關(guān)閉并將變量修改(調(diào)用clone時(shí)用到的CLONE_VM、CLONE_FILES標(biāo)志將使得變量和文件描述符表被共享),父進(jìn)程隨即就感覺到了,這就是clone的特點(diǎn)。

       

      sleep

      函數(shù)調(diào)用sleep可以用來使進(jìn)程掛起指定的秒數(shù),該函數(shù)的原型為:  

      unsigned int sleep(unsigned int seconds);

      該函數(shù)調(diào)用使得進(jìn)程掛起一個(gè)指定的時(shí)間,如果指定掛起的時(shí)間到了,該調(diào)用返回0;如果該函數(shù)調(diào)用被信號(hào)所打斷,則返回剩余掛起的時(shí)間數(shù)(指定的時(shí)間減去已經(jīng)掛起的時(shí)間)。

       

       

      exit

      系統(tǒng)調(diào)用exit的功能是終止本進(jìn)程,其函數(shù)原型為:

      void _exit(int status);

      _exit會(huì)立即終止發(fā)出調(diào)用的進(jìn)程,所有屬于該進(jìn)程的文件描述符都關(guān)閉。參數(shù)status作為退出的狀態(tài)值返回父進(jìn)程,在父進(jìn)程中通過系統(tǒng)調(diào)用wait可獲得此值。

       

      wait

      wait系統(tǒng)調(diào)用包括:

      pid_t wait(int *status);

      pid_t waitpid(pid_t pid, int *status, int options);

      wait的作用為發(fā)出調(diào)用的進(jìn)程只要有子進(jìn)程,就睡眠到它們中的一個(gè)終止為止; waitpid等待由參數(shù)pid指定的子進(jìn)程退出。

       

      3.進(jìn)程間通信

      Linux的進(jìn)程間通信(IPC,InterProcess Communication)通信方法有:

      管道、消息隊(duì)列、共享內(nèi)存、信號(hào)量、套接口等。

      管道分為有名管道和無名管道,無名管道只能用于親屬進(jìn)程之間的通信,而有名管道則可用于無親屬關(guān)系的進(jìn)程之間。

      #define INPUT 0

      #define OUTPUT 1

      void main()

      {

        int file_descriptors[2];

        /*定義子進(jìn)程號(hào) */

        pid_t pid;

        char buf[BUFFER_LEN];

        int returned_count;

        /*創(chuàng)建無名管道*/

        pipe(file_descriptors);

        /*創(chuàng)建子進(jìn)程*/

        if ((pid = fork()) ==  - 1)

        {

          printf("Error infork"n");

          exit(1);

        }

        /*執(zhí)行子進(jìn)程*/

        if (pid == 0)

        {

          printf("in the spawned (child)process..."n");

          /*子進(jìn)程向父進(jìn)程寫數(shù)據(jù),關(guān)閉管道的讀端*/

          close(file_descriptors[INPUT]);

          write(file_descriptors[OUTPUT],"test data", strlen("test data"));

          exit(0);

        }

        else

        {

          /*執(zhí)行父進(jìn)程*/

          printf("in the spawning (parent)process..."n");

          /*父進(jìn)程從管道讀取子進(jìn)程寫的數(shù)據(jù),關(guān)閉管道的寫端*/

          close(file_descriptors[OUTPUT]);

          returned_count = read(file_descriptors[INPUT],buf, sizeof(buf));

          printf("%d bytes of data receivedfrom spawned process: %s"n",

            returned_count, buf);

        }

      }

      上述程序中,無名管道以

      int pipe(int filedis[2]);

      方式定義,參數(shù)filedis返回兩個(gè)文件描述符filedes[0]為讀而打開,filedes[1]為寫而打開,filedes[1]的輸出是filedes[0]的輸入;

       

      在Linux系統(tǒng)下,有名管道可由兩種方式創(chuàng)建(假設(shè)創(chuàng)建一個(gè)名為“fifoexample”的有名管道):

      (1)mkfifo("fifoexample","rw");

      (2)mknod fifoexample p

      mkfifo是一個(gè)函數(shù),mknod是一個(gè)系統(tǒng)調(diào)用,即我們可以在shell下輸出上述命令。

      有名管道創(chuàng)建后,我們可以像讀寫文件一樣讀寫之:

      /* 進(jìn)程一:讀有名管道*/

      void main()

      {

        FILE *in_file;

        int count = 1;

        char buf[BUFFER_LEN];

        in_file = fopen("pipeexample","r");

        if (in_file == NULL)

        {

          printf("Error infdopen."n");

          exit(1);

        }

        while ((count = fread(buf, 1, BUFFER_LEN,in_file)) > 0)

          printf("received from pipe:%s"n", buf);

        fclose(in_file);

      }

       

      /* 進(jìn)程二:寫有名管道*/

      void main()

      {

        FILE *out_file;

        int count = 1;

        char buf[BUFFER_LEN];

        out_file = fopen("pipeexample","w");

        if (out_file == NULL)

        {

          printf("Error openingpipe.");

          exit(1);

        }

        sprintf(buf, "this is test data for the namedpipe example"n");

        fwrite(buf, 1, BUFFER_LEN, out_file);

        fclose(out_file);

      }

       

      消息隊(duì)列用于運(yùn)行于同一臺(tái)機(jī)器上的進(jìn)程間通信,與管道相似;

       

      共享內(nèi)存通常由一個(gè)進(jìn)程創(chuàng)建,其余進(jìn)程對(duì)這塊內(nèi)存區(qū)進(jìn)行讀寫。得到共享內(nèi)存有兩種方式:映射/dev/mem設(shè)備和內(nèi)存映像文件。前一種方式不給系統(tǒng)帶來額外的開銷,但在現(xiàn)實(shí)中并不常用,因?yàn)樗刂拼嫒〉氖菍?shí)際的物理內(nèi)存;常用的方式是通過shmXXX函數(shù)族來實(shí)現(xiàn)共享內(nèi)存:

      int shmget(key_t key, int size, int flag); /* 獲得一個(gè)共享存儲(chǔ)標(biāo)識(shí)符*/

      該函數(shù)使得系統(tǒng)分配size大小的內(nèi)存用作共享內(nèi)存;

      void *shmat(int shmid, void *addr, int flag); /* 將共享內(nèi)存連接到自身地址空間中*/

      shmid為shmget函數(shù)返回的共享存儲(chǔ)標(biāo)識(shí)符,addr和flag參數(shù)決定了以什么方式來確定連接的地址,函數(shù)的返回值即是該進(jìn)程數(shù)據(jù)段所連接的實(shí)際地址。此后,進(jìn)程可以對(duì)此地址進(jìn)行讀寫操作訪問共享內(nèi)存。

       

      本質(zhì)上,信號(hào)量是一個(gè)計(jì)數(shù)器,它用來記錄對(duì)某個(gè)資源(如共享內(nèi)存)的存取狀況。一般說來,為了獲得共享資源,進(jìn)程需要執(zhí)行下列操作:

      (1)測(cè)試控制該資源的信號(hào)量;

      (2)若此信號(hào)量的值為正,則允許進(jìn)行使用該資源,進(jìn)程將進(jìn)號(hào)量減1;

      (3)若此信號(hào)量為0,則該資源目前不可用,進(jìn)程進(jìn)入睡眠狀態(tài),直至信號(hào)量值大于0,進(jìn)程被喚醒,轉(zhuǎn)入步驟(1);

      (4)當(dāng)進(jìn)程不再使用一個(gè)信號(hào)量控制的資源時(shí),信號(hào)量值加1,如果此時(shí)有進(jìn)程正在睡眠等待此信號(hào)量,則喚醒此進(jìn)程。

       

      下面是一個(gè)使用信號(hào)量的例子,該程序創(chuàng)建一個(gè)特定的IPC結(jié)構(gòu)的關(guān)鍵字和一個(gè)信號(hào)量,建立此信號(hào)量的索引,修改索引指向的信號(hào)量的值,最后清除信號(hào)量:

      #include <stdio.h>

      #include <sys/types.h>

      #include <sys/sem.h>

      #include <sys/ipc.h>

      void main()

      {

        key_t unique_key; /* 定義一個(gè)IPC關(guān)鍵字*/

        int id;

        struct sembuf lock_it;

        union semun options;

        int i;

       

        unique_key = ftok(".", 'a'); /* 生成關(guān)鍵字,字符'a'是一個(gè)隨機(jī)種子*/

        /* 創(chuàng)建一個(gè)新的信號(hào)量集合*/

        id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL |0666);

        printf("semaphore id=%d"n", id);

        options.val = 1; /*設(shè)置變量值*/

        semctl(id, 0, SETVAL, options); /*設(shè)置索引0的信號(hào)量*/

       

        /*打印出信號(hào)量的值*/

        i = semctl(id, 0, GETVAL, 0);

        printf("value of semaphore at index 0 is%d"n", i);

       

        /*下面重新設(shè)置信號(hào)量*/

        lock_it.sem_num = 0; /*設(shè)置哪個(gè)信號(hào)量*/

        lock_it.sem_op =  - 1; /*定義操作*/

        lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/

        if (semop(id, &lock_it, 1) ==  - 1)

        {

          printf("can not locksemaphore."n");

          exit(1);

        }

       

        i = semctl(id, 0, GETVAL, 0);

        printf("value of semaphore at index 0 is%d"n", i);

       

        /*清除信號(hào)量*/

        semctl(id, 0, IPC_RMID, 0);

      }

       

      套接字通信并不為Linux所專有,在所有提供了TCP/IP協(xié)議棧的操作系統(tǒng)中幾乎都提供了socket,而所有這樣操作系統(tǒng),對(duì)套接字的編程方法幾乎是完全一樣的。

       

      4.小節(jié)

      本章講述了Linux進(jìn)程的概念,并以多個(gè)實(shí)例講解了進(jìn)程控制及進(jìn)程間通信方法,理解這一章的內(nèi)容可以說是理解Linux這個(gè)操作系統(tǒng)的關(guān)鍵。

       

       

       

      Linux下的C編程實(shí)戰(zhàn)(四)

      ――“線程”控制與“線程”通信編程

       

      1.Linux“線程”

          筆者曾經(jīng)在《基于嵌入式操作系統(tǒng)VxWorks的多任務(wù)并發(fā)程序設(shè)計(jì)》(《軟件報(bào)》2006年第5~12期)中詳細(xì)敘述了進(jìn)程和線程的區(qū)別,并曾經(jīng)說明Linux是一種“多進(jìn)程單線程”的操作系統(tǒng)。Linux本身只有進(jìn)程的概念,而其所謂的“線程”本質(zhì)上在內(nèi)核里仍然是進(jìn)程。大家知道,進(jìn)程是資源分配的單位,同一進(jìn)程中的多個(gè)線程共享該進(jìn)程的資源(如作為共享內(nèi)存的全局變量)。Linux中所謂的“線程”只是在被創(chuàng)建的時(shí)候“克隆”(clone)了父進(jìn)程的資源,因此,clone出來的進(jìn)程表現(xiàn)為“線程”,這一點(diǎn)一定要弄清楚。因此,Linux“線程”這個(gè)概念只有在打冒號(hào)的情況下才是最準(zhǔn)確的,可惜的是幾乎沒有書籍留心去強(qiáng)調(diào)這一點(diǎn)。

          Linux內(nèi)核只提供了輕量進(jìn)程的支持,未實(shí)現(xiàn)線程模型,但Linux盡最大努力優(yōu)化了進(jìn)程的調(diào)度開銷,這在一定程度上彌補(bǔ)無線程的缺陷。Linux用一個(gè)核心進(jìn)程(輕量進(jìn)程)對(duì)應(yīng)一個(gè)線程,將線程調(diào)度等同于進(jìn)程調(diào)度,交給核心完成。

          目前Linux中最流行的線程機(jī)制為LinuxThreads,所采用的就是線程-進(jìn)程“一對(duì)一”模型,調(diào)度交給核心,而在用戶級(jí)實(shí)現(xiàn)一個(gè)包括信號(hào)處理在內(nèi)的線程管理機(jī)制。LinuxThreads由Xavier Leroy (Xavier.Leroy@inria.fr)負(fù)責(zé)開發(fā)完成,并已綁定在GLIBC中發(fā)行,它實(shí)現(xiàn)了一種BiCapitalized面向Linux的Posix 1003.1c “pthread”標(biāo)準(zhǔn)接口。Linuxthread可以支持Intel、Alpha、MIPS等平臺(tái)上的多處理器系統(tǒng)。

      按照POSIX 1003.1c標(biāo)準(zhǔn)編寫的程序與Linuxthread 庫相鏈接即可支持Linux平臺(tái)上的多線程,在程序中需包含頭文件pthread. h,在編譯鏈接時(shí)使用命令:

      gcc -D -REENTRANT -lpthread xxx.c

      其中-REENTRANT宏使得相關(guān)庫函數(shù)(如stdio.h、errno.h中函數(shù)) 是可重入的、線程安全的(thread-safe),-lpthread則意味著鏈接庫目錄下的libpthread.a或libpthread.so文件。使用Linuxthread庫需要2.0以上版本的Linux內(nèi)核及相應(yīng)版本的C庫(libc5.2.18、libc5.4.12、libc 6)。

       

      2.“線程”控制

      線程創(chuàng)建

      進(jìn)程被創(chuàng)建時(shí),系統(tǒng)會(huì)為其創(chuàng)建一個(gè)主線程,而要在進(jìn)程中創(chuàng)建新的線程,則可以調(diào)用pthread_create:

      pthread_create(pthread_t *thread, const pthread_attr_t*attr, void *

        (start_routine)(void*), void *arg);

      start_routine為新線程的入口函數(shù),arg為傳遞給start_routine的參數(shù)。

      每個(gè)線程都有自己的線程ID,以便在進(jìn)程內(nèi)區(qū)分。線程ID在pthread_create調(diào)用時(shí)回返給創(chuàng)建線程的調(diào)用者;一個(gè)線程也可以在創(chuàng)建后使用pthread_self()調(diào)用獲取自己的線程ID.

       

      pthread_self (void) ;

       

      線程退出

      線程的退出方式有三:

      (1)執(zhí)行完成后隱式退出;

      (2)由線程本身顯示調(diào)用pthread_exit 函數(shù)退出;

           pthread_exit(void * retval) ;

      (3)被其他線程用pthread_cancel函數(shù)終止:

      pthread_cancel (pthread_t thread) ;

      在某線程中調(diào)用此函數(shù),可以終止由參數(shù)thread 指定的線程。

      如果一個(gè)線程要等待另一個(gè)線程的終止,可以使用pthread_join函數(shù),該函數(shù)的作用是調(diào)用pthread_join的線程將被掛起直到線程ID為參數(shù)

       

      thread的線程終止:

      pthread_join (pthread_t thread, void** threadreturn);

       

      3.線程通信

      線程互斥

      互斥意味著“排它”,即兩個(gè)線程不能同時(shí)進(jìn)入被互斥保護(hù)的代碼。Linux下可以通過pthread_mutex_t 定義互斥體機(jī)制完成多線程的互斥操作,該機(jī)制的作用是對(duì)某個(gè)需要互斥的部分,在進(jìn)入時(shí)先得到互斥體,如果沒有得到互斥體,表明互斥部分被其它線程擁有,此時(shí)欲獲取互斥體的線程阻塞,直到擁有該互斥體的線程完成互斥部分的操作為止。

       

      下面的代碼實(shí)現(xiàn)了對(duì)共享全局變量x 用互斥體mutex 進(jìn)行保護(hù)的目的:

      int x; // 進(jìn)程中的全局變量

      pthread_mutex_t mutex;

      pthread_mutex_init(&mutex, NULL); //按缺省的屬性初始化互斥體變量mutex

      pthread_mutex_lock(&mutex); // 給互斥體變量加鎖

      … //對(duì)變量x 的操作

      phtread_mutex_unlock(&mutex); // 給互斥體變量解除鎖

       

      線程同步

      同步就是線程等待某個(gè)事件的發(fā)生。只有當(dāng)?shù)却氖录l(fā)生線程才繼續(xù)執(zhí)行,否則線程掛起并放棄處理器。當(dāng)多個(gè)線程協(xié)作時(shí),相互作用的任務(wù)必須在一定的條件下同步。

      Linux下的C語言編程有多種線程同步機(jī)制,最典型的是條件變量(condition variable)。pthread_cond_init用來創(chuàng)建一個(gè)條件變量,其函數(shù)原型為:

      pthread_cond_init (pthread_cond_t *cond, constpthread_condattr_t *attr);

      pthread_cond_wait和pthread_cond_timedwait用來等待條件變量被設(shè)置,值得注意的是這兩個(gè)等待調(diào)用需要一個(gè)已經(jīng)上鎖的互斥體mutex,這是為了防止在真正進(jìn)入等待狀態(tài)之前別的線程有可能設(shè)置該條件變量而產(chǎn)生競爭。

      pthread_cond_wait的函數(shù)原型為:

      pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t*mutex);

       

      pthread_cond_broadcast用于設(shè)置條件變量,即使得事件發(fā)生,這樣等待該事件的線程將不再阻塞:

      pthread_cond_broadcast (pthread_cond_t *cond) ;

      pthread_cond_signal則用于解除某一個(gè)等待線程的阻塞狀態(tài):

      pthread_cond_signal (pthread_cond_t *cond) ;

      pthread_cond_destroy 則用于釋放一個(gè)條件變量的資源。

      在頭文件semaphore.h 中定義的信號(hào)量則完成了互斥體和條件變量的封裝,按照多線程程序設(shè)計(jì)中訪問控制機(jī)制,控制對(duì)資源的同步訪問,提供程序設(shè)計(jì)人員更方便的調(diào)用接口。

      sem_init(sem_t *sem, int pshared, unsigned int val);

      這個(gè)函數(shù)初始化一個(gè)信號(hào)量sem 的值為val,參數(shù)pshared是共享屬性控制,表明是否在進(jìn)程間共享。

       

      sem_wait(sem_t *sem);

      調(diào)用該函數(shù)時(shí),若sem為無狀態(tài),調(diào)用線程阻塞,等待信號(hào)量sem值增加(post )成為有信號(hào)狀態(tài);若sem為有狀態(tài),調(diào)用線程順序執(zhí)行,但信號(hào)量的值減一。

       

      sem_post(sem_t *sem);

      調(diào)用該函數(shù),信號(hào)量sem的值增加,可以從無信號(hào)狀態(tài)變?yōu)橛行盘?hào)狀態(tài)。

       

      4.實(shí)例

      下面我們還是以著名的生產(chǎn)者/消費(fèi)者問題為例來闡述Linux線程的控制和通信。一組生產(chǎn)者線程與一組消費(fèi)者線程通過緩沖區(qū)發(fā)生聯(lián)系。生產(chǎn)者線程將生產(chǎn)的產(chǎn)品送入緩沖區(qū),消費(fèi)者線程則從中取出產(chǎn)品。緩沖區(qū)有N 個(gè),是一個(gè)環(huán)形的緩沖池。

      #include <stdio.h>

      #include <pthread.h>

      #define BUFFER_SIZE 16 // 緩沖區(qū)數(shù)量

      struct prodcons

      {

        // 緩沖區(qū)相關(guān)數(shù)據(jù)結(jié)構(gòu)

        int buffer[BUFFER_SIZE]; /* 實(shí)際數(shù)據(jù)存放的數(shù)組*/

        pthread_mutex_t lock; /* 互斥體lock 用于對(duì)緩沖區(qū)的互斥操作 */

        int readpos, writepos; /* 讀寫指針*/

        pthread_cond_t notempty; /* 緩沖區(qū)非空的條件變量 */

        pthread_cond_t notfull; /* 緩沖區(qū)未滿的條件變量 */

      };

       

      /* 初始化緩沖區(qū)結(jié)構(gòu) */

      void init(struct prodcons *b)

      {

        pthread_mutex_init(&b->lock, NULL);

        pthread_cond_init(&b->notempty, NULL);

        pthread_cond_init(&b->notfull, NULL);

        b->readpos = 0;

        b->writepos = 0;

      }

       

      /* 將產(chǎn)品放入緩沖區(qū),這里是存入一個(gè)整數(shù)*/

      void put(struct prodcons *b, int data)

      {

        pthread_mutex_lock(&b->lock);

        /* 等待緩沖區(qū)未滿*/

        if ((b->writepos + 1) % BUFFER_SIZE ==b->readpos)

        {

          pthread_cond_wait(&b->notfull,&b->lock);

        }

        /* 寫數(shù)據(jù),并移動(dòng)指針 */

        b->buffer[b->writepos] = data;

        b->writepos++;

        if (b->writepos >  = BUFFER_SIZE)

          b->writepos = 0;

        /* 設(shè)置緩沖區(qū)非空的條件變量*/

        pthread_cond_signal(&b->notempty);

        pthread_mutex_unlock(&b->lock);

      }

       

      /* 從緩沖區(qū)中取出整數(shù)*/

      int get(struct prodcons *b)

      {

        int data;

        pthread_mutex_lock(&b->lock);

        /* 等待緩沖區(qū)非空*/

        if (b->writepos == b->readpos)

        {

          pthread_cond_wait(&b->notempty,&b->lock);

        }

        /* 讀數(shù)據(jù),移動(dòng)讀指針*/

        data = b->buffer[b->readpos];

        b->readpos++;

        if (b->readpos >  = BUFFER_SIZE)

          b->readpos = 0;

        /* 設(shè)置緩沖區(qū)未滿的條件變量*/

        pthread_cond_signal(&b->notfull);

        pthread_mutex_unlock(&b->lock);

        return data;

      }

       

      /* 測(cè)試:生產(chǎn)者線程將1 到10000 的整數(shù)送入緩沖區(qū),消費(fèi)者線

      程從緩沖區(qū)中獲取整數(shù),兩者都打印信息*/

      #define OVER ( - 1)

      struct prodcons buffer;

      void *producer(void *data)

      {

        int n;

        for (n = 0; n < 10000;n++)

        {

          printf("%d --->"n",n);

          put(&buffer, n);

        } put(&buffer, OVER);

        return NULL;

      }

       

      void *consumer(void *data)

      {

        int d;

        while (1)

        {

          d = get(&buffer);

          if (d == OVER)

            break;

          printf("--->%d "n",d);

        }

        return NULL;

      }

       

      int main(void)

      {

        pthread_t th_a, th_b;

        void *retval;

        init(&buffer);

        /* 創(chuàng)建生產(chǎn)者和消費(fèi)者線程*/

        pthread_create(&th_a, NULL, producer, 0);

        pthread_create(&th_b, NULL, consumer, 0);

        /* 等待兩個(gè)線程結(jié)束*/

        pthread_join(th_a, &retval);

        pthread_join(th_b, &retval);

        return 0;

      }

       

      5.WIN32、VxWorks、Linux線程類比

      目前為止,筆者已經(jīng)創(chuàng)作了《基于嵌入式操作系統(tǒng)VxWorks的多任務(wù)并發(fā)程序設(shè)計(jì)》(《軟件報(bào)》2006年5~12期連載)、《深入淺出Win32多線程程序設(shè)計(jì)》(天極網(wǎng)技術(shù)專題)系列,我們來找出這兩個(gè)系列文章與本文的共通點(diǎn)。

      看待技術(shù)問題要瞄準(zhǔn)其本質(zhì),不管是Linux、VxWorks還是WIN32,其涉及到多線程的部分都是那些內(nèi)容,無非就是線程控制和線程通信,它們的許多函數(shù)只是名稱不同,其實(shí)質(zhì)含義是等價(jià)的,下面我們來列個(gè)三大操作系統(tǒng)共同點(diǎn)詳細(xì)表單:

      事項(xiàng)
       WIN32
       VxWorks
       Linux
       
      線程創(chuàng)建
       CreateThread
       taskSpawn
       pthread_create
       
      線程終止
       執(zhí)行完成后退出;線程自身調(diào)用ExitThread 函數(shù)即終止自己;被其他線程調(diào)用函數(shù)  TerminateThread函數(shù)
       執(zhí)行完成后退出;由線程本身調(diào)用exit退出;被其他線程調(diào)用函數(shù)taskDelete終止
      執(zhí)行完成后退出;由線程本身調(diào)用pthread_exit 退出;被其他線程調(diào)用函數(shù)pthread_cancel終止
       
      獲取線程ID
       GetCurrentThreadId
       taskIdSelf
       pthread_self
       
      創(chuàng)建互斥
       CreateMutex
       semMCreate
       pthread_mutex_init
       
      獲取互斥
       WaitForSingleObject、

      WaitForMultipleObjects
       semTake
       pthread_mutex_lock
       
      釋放互斥
       ReleaseMutex
       semGive
       phtread_mutex_unlock
       
      創(chuàng)建信號(hào)量
       CreateSemaphore
       semBCreate、semCCreate
       sem_init
       
      等待信號(hào)量
       WaitForSingleObject
       semTake
       sem_wait
       
      釋放信號(hào)量
       ReleaseSemaphore
       semGive
       sem_post
       

      6.小結(jié)

          本章講述了Linux下多線程的控制及線程間通信編程方法,給出了一個(gè)生產(chǎn)者/消費(fèi)者的實(shí)例,并將Linux的多線程與WIN32、VxWorks多線程進(jìn)行了類比,總結(jié)了一般規(guī)律。鑒于多線程編程已成為開發(fā)并發(fā)應(yīng)用程序的主流方法,學(xué)好本章的意義也便不言自明。

       

       

      Linux下的C編程實(shí)戰(zhàn)(五)

      ――驅(qū)動(dòng)程序設(shè)計(jì)


      1.引言

      設(shè)備驅(qū)動(dòng)程序是操作系統(tǒng)內(nèi)核和機(jī)器硬件之間的接口,它為應(yīng)用程序屏蔽硬件的細(xì)節(jié),一般來說,Linux的設(shè)備驅(qū)動(dòng)程序需要完成如下功能:

      (1)初始化設(shè)備;

      (2)提供各類設(shè)備服務(wù);

      (3)負(fù)責(zé)內(nèi)核和設(shè)備之間的數(shù)據(jù)交換;

      (4)檢測(cè)和處理設(shè)備工作過程中出現(xiàn)的錯(cuò)誤。

      妙不可言的是,Linux下的設(shè)備驅(qū)動(dòng)程序被組織為一組完成不同任務(wù)的函數(shù)的集合,通過這些函數(shù)使得Linux的設(shè)備操作猶如文件一般。在應(yīng)用程序看來,硬件設(shè)備只是一個(gè)設(shè)備文件,應(yīng)用程序可以象操作普通文件一樣對(duì)硬件設(shè)備進(jìn)行操作。本系列文章的第2章文件系統(tǒng)編程中已經(jīng)看到了這些函數(shù)的真面目,它們就是open ()、close ()、read ()、write() 等。

      Linux主要將設(shè)備分為二類:字符設(shè)備和塊設(shè)備(當(dāng)然網(wǎng)絡(luò)設(shè)備及USB等其它設(shè)備的驅(qū)動(dòng)編寫方法又稍有不同)。這兩類設(shè)備的不同點(diǎn)在于:在對(duì)字符設(shè)備發(fā)出讀/寫請(qǐng)求時(shí),實(shí)際的硬件I/O一般就緊接著發(fā)生了,而塊設(shè)備則不然,它利用一塊系統(tǒng)內(nèi)存作緩沖區(qū),當(dāng)用戶進(jìn)程對(duì)設(shè)備請(qǐng)求能滿足用戶的要求,就返回請(qǐng)求的數(shù)據(jù),如果不能,就調(diào)用請(qǐng)求函數(shù)來進(jìn)行實(shí)際的I/O操作。塊設(shè)備主要針對(duì)磁盤等慢速設(shè)備。字符設(shè)備的驅(qū)動(dòng)較為簡單,因此本章主要闡述字符設(shè)備的驅(qū)動(dòng)編寫。

       

      2.驅(qū)動(dòng)模塊函數(shù)

      init 函數(shù)用來完成對(duì)所控設(shè)備的初始化工作,并調(diào)用register_chrdev() 函數(shù)注冊(cè)字符設(shè)備。假設(shè)有一字符設(shè)備“exampledev”,則其init

      函數(shù)為:

      void exampledev_init(void)

      {

        if (register_chrdev(MAJOR_NUM, " exampledev", &exampledev_fops))

          TRACE_TXT("Device exampledevdriver registered error");

        else

          TRACE_TXT("Device exampledevdriver registered successfully");

        …//設(shè)備初始化

      }

      其中,register_chrdev函數(shù)中的參數(shù)MAJOR_NUM為主設(shè)備號(hào),“exampledev”為設(shè)備名,exampledev_fops為包含基本函數(shù)入口點(diǎn)的結(jié)構(gòu)體,類型為file_operations。當(dāng)執(zhí)行exampledev_init時(shí),它將調(diào)用內(nèi)核函數(shù)register_chrdev,把驅(qū)動(dòng)程序的基本入口點(diǎn)指針存放在內(nèi)核的字符設(shè)備地址表中,在用戶進(jìn)程對(duì)該設(shè)備執(zhí)行系統(tǒng)調(diào)用時(shí)提供入口地址。

      file_operations結(jié)構(gòu)體定義為:

      struct file_operations

      {

        int (*lseek)();

        int (*read)();

        int (*write)();

        int (*readdir)();

        int (*select)();

        int (*ioctl)();

        int (*mmap)();

        int (*open)();

        void(*release)();

        int (*fsync)();

        int (*fasync)();

        int (*check_media_change)();

        void(*revalidate)();

      };

      大多數(shù)的驅(qū)動(dòng)程序只是利用了其中的一部分,對(duì)于驅(qū)動(dòng)程序中無需提供的功能,只需要把相應(yīng)位置的值設(shè)為NULL。對(duì)于字符設(shè)備來說,要提供的主要入口有:open ()、release ()、read ()、write()、ioctl ()。

      open()函數(shù) 對(duì)設(shè)備特殊文件進(jìn)行open()系統(tǒng)調(diào)用時(shí),將調(diào)用驅(qū)動(dòng)程序的open () 函數(shù):

      int open(struct inode * inode ,struct file * file);

      其中參數(shù)inode為設(shè)備特殊文件的inode (索引結(jié)點(diǎn)) 結(jié)構(gòu)的指針,參數(shù)file是指向這一設(shè)備的文件結(jié)構(gòu)的指針。open()的主要任務(wù)是確定硬件處在就緒狀態(tài)、驗(yàn)證次設(shè)備號(hào)的合法性(次設(shè)備號(hào)可以用MINOR(inode-> i - rdev) 取得)、控制使用設(shè)備的進(jìn)程數(shù)、根據(jù)執(zhí)行情況返回狀態(tài)碼(0表示成功,負(fù)數(shù)表示存在錯(cuò)誤) 等;

      release()函數(shù) 當(dāng)最后一個(gè)打開設(shè)備的用戶進(jìn)程執(zhí)行close ()系統(tǒng)調(diào)用時(shí),內(nèi)核將調(diào)用驅(qū)動(dòng)程序的release () 函數(shù):

      void release (struct inode * inode ,struct file * file) ;

      release 函數(shù)的主要任務(wù)是清理未結(jié)束的輸入/輸出操作、釋放資源、用戶自定義排他標(biāo)志的復(fù)位等。

      read()函數(shù) 當(dāng)對(duì)設(shè)備特殊文件進(jìn)行read() 系統(tǒng)調(diào)用時(shí),將調(diào)用驅(qū)動(dòng)程序read() 函數(shù):

      void read(struct inode * inode ,struct file * file ,char* buf ,int count) ;

      參數(shù)buf是指向用戶空間緩沖區(qū)的指針,由用戶進(jìn)程給出,count 為用戶進(jìn)程要求讀取的字節(jié)數(shù),也由用戶給出。

      read() 函數(shù)的功能就是從硬設(shè)備或內(nèi)核內(nèi)存中讀取或復(fù)制count個(gè)字節(jié)到buf 指定的緩沖區(qū)中。在復(fù)制數(shù)據(jù)時(shí)要注意,驅(qū)動(dòng)程序運(yùn)行在內(nèi)核中,而buf指定的緩沖區(qū)在用戶內(nèi)存區(qū)中,是不能直接在內(nèi)核中訪問使用的,因此,必須使用特殊的復(fù)制函數(shù)來完成復(fù)制工作,這些函數(shù)在<asm/

      segment.h>中定義:

      void put_user_byte (char data_byte ,char * u_addr) ;

      void put_user_word (short data_word ,short * u_addr) ;

      void put_user_long(long data_long ,long * u_addr) ;

      void memcpy_tofs (void * u_addr ,void * k_addr ,unsignedlong cnt) ;

      參數(shù)u_addr為用戶空間地址,k_addr 為內(nèi)核空間地址,cnt為字節(jié)數(shù)。

      write( ) 函數(shù) 當(dāng)設(shè)備特殊文件進(jìn)行write () 系統(tǒng)調(diào)用時(shí),將調(diào)用驅(qū)動(dòng)程序的write () 函數(shù):

      void write (struct inode * inode ,struct file * file,char * buf ,int count) ;

      write ()的功能是將參數(shù)buf 指定的緩沖區(qū)中的count 個(gè)字節(jié)內(nèi)容復(fù)制到硬件或內(nèi)核內(nèi)存中,和read() 一樣,復(fù)制工作也需要由特殊函數(shù)來完

      成:

      unsigned char_get_user_byte (char * u_addr) ;

      unsigned char_get_user_word (short * u_addr) ;

      unsigned char_get_user_long(long * u_addr) ;

      unsigned memcpy_fromfs(void * k_addr ,void * u_addr,unsigned long cnt) ;

      ioctl() 函數(shù) 該函數(shù)是特殊的控制函數(shù),可以通過它向設(shè)備傳遞控制信息或從設(shè)備取得狀態(tài)信息,函數(shù)原型為:

      int ioctl (struct inode * inode ,struct file * file,unsigned int cmd ,unsigned long arg);

      參數(shù)cmd為設(shè)備驅(qū)動(dòng)程序要執(zhí)行的命令的代碼,由用戶自定義,參數(shù)arg 為相應(yīng)的命令提供參數(shù),類型可以是整型、指針等。

      同樣,在驅(qū)動(dòng)程序中,這些函數(shù)的定義也必須符合命名規(guī)則,按照本文約定,設(shè)備“exampledev”的驅(qū)動(dòng)程序的這些函數(shù)應(yīng)分別命名為

      exampledev_open、exampledev_ release、exampledev_read、exampledev_write、exampledev_ioctl,因此設(shè)備“exampledev”的基本入口點(diǎn)

      結(jié)構(gòu)變量exampledev_fops 賦值如下:

      struct file_operations exampledev_fops {

       NULL ,

       exampledev_read ,

       exampledev_write ,

       NULL ,

       NULL ,

       exampledev_ioctl ,

       NULL ,

       exampledev_open ,

       exampledev_release ,

       NULL ,

       NULL ,

       NULL ,

       NULL

      } ;

       

      3.內(nèi)存分配

      由于Linux驅(qū)動(dòng)程序在內(nèi)核中運(yùn)行,因此在設(shè)備驅(qū)動(dòng)程序需要申請(qǐng)/釋放內(nèi)存時(shí),不能使用用戶級(jí)的malloc/free函數(shù),而需由內(nèi)核級(jí)的函數(shù)

      kmalloc/kfree () 來實(shí)現(xiàn),kmalloc()函數(shù)的原型為:

      void kmalloc (size_t size ,int priority);

      參數(shù)size為申請(qǐng)分配內(nèi)存的字節(jié)數(shù);參數(shù)priority說明若kmalloc()不能馬上分配內(nèi)存時(shí)用戶進(jìn)程要采用的動(dòng)作:GFP_KERNEL 表示等待,即等kmalloc()函數(shù)將一些內(nèi)存安排到交換區(qū)來滿足你的內(nèi)存需要,GFP_ATOMIC 表示不等待,如不能立即分配到內(nèi)存則返回0 值;函數(shù)的返回值指向已分配內(nèi)存的起始地址,出錯(cuò)時(shí),返回0。

      kmalloc ()分配的內(nèi)存需用kfree()函數(shù)來釋放,kfree ()被定義為:

      # define kfree (n) kfree_s( (n) ,0)

      其中kfree_s () 函數(shù)原型為:

      void kfree_s (void * ptr ,int size);

      參數(shù)ptr為kmalloc()返回的已分配內(nèi)存的指針,size是要釋放內(nèi)存的字節(jié)數(shù),若為0 時(shí),由內(nèi)核自動(dòng)確定內(nèi)存的大小。

       

      4.中斷

      許多設(shè)備涉及到中斷操作,因此,在這樣的設(shè)備的驅(qū)動(dòng)程序中需要對(duì)硬件產(chǎn)生的中斷請(qǐng)求提供中斷服務(wù)程序。與注冊(cè)基本入口點(diǎn)一樣,驅(qū)動(dòng)程序也要請(qǐng)求內(nèi)核將特定的中斷請(qǐng)求和中斷服務(wù)程序聯(lián)系在一起。在Linux中,用request_irq()函數(shù)來實(shí)現(xiàn)請(qǐng)求:

      int request_irq (unsigned int irq ,void( * handler) int,unsigned long type ,char * name);

      參數(shù)irq為要中斷請(qǐng)求號(hào),參數(shù)handler為指向中斷服務(wù)程序的指針,參數(shù)type 用來確定是正常中斷還是快速中斷(正常中斷指中斷服務(wù)子程序返回后,內(nèi)核可以執(zhí)行調(diào)度程序來確定將運(yùn)行哪一個(gè)進(jìn)程;而快速中斷是指中斷服務(wù)子程序返回后,立即執(zhí)行被中斷程序,正常中斷type 取值為0 ,快速中斷type 取值為SA_INTERRUPT),參數(shù)name是設(shè)備驅(qū)動(dòng)程序的名稱。

       

      5.實(shí)例

      筆者最近設(shè)計(jì)了一塊采用三星S3C2410 ARM處理器的電路板(ARM處理器廣泛應(yīng)用于手機(jī)、PDA等嵌入式系統(tǒng)),板上包含四個(gè)用戶可編程的發(fā)光二極管(LED),這些LED連接在ARM處理器的可編程I/O口(GPIO)上。

      我們?cè)贏RM處理器上移植Linux操作系統(tǒng),現(xiàn)在來編寫這些LED的驅(qū)動(dòng):

      #include <linux/config.h>

      #include <linux/module.h>

      #include <linux/kernel.h>

      #include <linux/init.h>

      #include <linux/miscdevice.h>

      #include <linux/sched.h>

      #include <linux/delay.h>

      #include <linux/poll.h>

      #include <linux/spinlock.h>

      #include <linux/irq.h>

      #include <linux/delay.h>

      #include <asm/hardware.h>

      #define DEVICE_NAME "leds" /*定義led 設(shè)備的名字*/

      #define LED_MAJOR 231 /*定義led 設(shè)備的主設(shè)備號(hào)*/

      static unsigned long led_table[] =

      {

        /*I/O 方式led 設(shè)備對(duì)應(yīng)的硬件資源*/

        GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6,

      };

      /*使用ioctl 控制led*/

      static int leds_ioctl(struct inode *inode, struct file*file, unsigned int cmd,

        unsigned long arg)

      {

        switch (cmd)

        {

          case 0:

          case 1:

            if (arg > 4)

            {

              return -EINVAL;

            }

           write_gpio_bit(led_table[arg], !cmd);

          default:

            return  -EINVAL;

        }

      }

       

      static struct file_operations leds_fops =

      {

        owner: THIS_MODULE, ioctl: leds_ioctl,

      };

      static devfs_handle_t devfs_handle;

      static int __init leds_init(void)

      {

        int ret;

        int i;

        /*在內(nèi)核中注冊(cè)設(shè)備*/

        ret = register_chrdev(LED_MAJOR, DEVICE_NAME,&leds_fops);

        if (ret < 0)

        {

          printk(DEVICE_NAME " can'tregister major number"n");

          return ret;

        }

        devfs_handle = devfs_register(NULL, DEVICE_NAME,DEVFS_FL_DEFAULT, LED_MAJOR,

          0, S_IFCHR | S_IRUSR | S_IWUSR,&leds_fops, NULL);

        /*使用宏進(jìn)行端口初始化,set_gpio_ctrl 和write_gpio_bit 均為宏定義*/

        for (i = 0; i < 8; i++)

        {

          set_gpio_ctrl(led_table[i] |GPIO_PULLUP_EN | GPIO_MODE_OUT);

          write_gpio_bit(led_table[i], 1);

        }

        printk(DEVICE_NAME "initialized"n");

        return 0;

      }

       

      static void __exit leds_exit(void)

      {

        devfs_unregister(devfs_handle);

        unregister_chrdev(LED_MAJOR, DEVICE_NAME);

      }

       

      module_init(leds_init);

      module_exit(leds_exit);

      使用命令方式編譯led 驅(qū)動(dòng)模塊:

      #arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include

      -DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c

      以上命令將生成leds.o 文件,把該文件復(fù)制到板子的/lib目錄下,使用以下命令就可以安裝leds驅(qū)動(dòng)模塊:

      #insmod /lib/ leds.o

      刪除該模塊的命令是:

      #rmmod leds

       

      6.小結(jié)

      本章講述了Linux設(shè)備驅(qū)動(dòng)程序的入口函數(shù)及驅(qū)動(dòng)程序中的內(nèi)存申請(qǐng)、中斷等,并給出了一個(gè)通過ARM處理器的GPIO口控制LED的驅(qū)動(dòng)實(shí)例。


       

      陳工程師一直做Linux的嵌入式開發(fā),作為在開發(fā)一線的工程師,他對(duì)很多問題的看法可能更切合實(shí)際需求,于是,通過電子郵件,就嵌入式開發(fā)方面的問題,請(qǐng)他談了一下自己的看法: 

      問:關(guān)于嵌入式開發(fā),我們準(zhǔn)備給同學(xué)們講解一些入門知識(shí),從你一線開發(fā)經(jīng)驗(yàn)來說,給我們一些建議: 



      陳工回答: 

      對(duì)于嵌入式Linux入門,如果有一定基礎(chǔ),可以從驅(qū)動(dòng)開始;如果沒有基礎(chǔ),我個(gè)人建議還是從應(yīng)用程序開始。因?yàn)閺膽?yīng)用程序開始是最容易的,也是最直觀的。而驅(qū)動(dòng)程序運(yùn)行在內(nèi)核態(tài),驅(qū)動(dòng)本身的結(jié)構(gòu)就比較復(fù)雜,如果要徹底弄明白驅(qū)動(dòng)的運(yùn)行機(jī)制,必定牽涉內(nèi)核,對(duì)于高年級(jí)的學(xué)生恐怕問題會(huì)少一些,而對(duì)于低年級(jí)的學(xué)生,問題估計(jì)較多。我曾經(jīng)遇到過一些初學(xué)者,就是一入門就栽了,失去了信心,當(dāng)然這只是少數(shù)。不過,如果在遇到問題之后,能夠得到即時(shí)、正確的點(diǎn)化,那就是好事了。 

      既然您決定講驅(qū)動(dòng),那就從內(nèi)核模塊開始。在PC上就可以進(jìn)行的虛擬設(shè)備實(shí)驗(yàn),如基于內(nèi)存的內(nèi)核模塊。可以考慮從模塊的結(jié)構(gòu)、編譯、插入、卸載等方面進(jìn)行闡述。 

      驅(qū)動(dòng)模塊無非分字符驅(qū)動(dòng)、塊設(shè)備驅(qū)動(dòng)和網(wǎng)絡(luò)驅(qū)動(dòng)三大類。但是一定要讓學(xué)生知道,任何一個(gè)系統(tǒng),特別是嵌入式系統(tǒng),并且在目前的嵌入式Linux產(chǎn)品開發(fā)中,最簡單、最重要、最多、最復(fù)雜的也是字符設(shè)備驅(qū)動(dòng),從IO驅(qū)動(dòng)到串口驅(qū)動(dòng)、到USB驅(qū)動(dòng)等等,廣義上都是字符驅(qū)動(dòng)。讓學(xué)生最好專注于字符設(shè)備驅(qū)動(dòng),因?yàn)橐粋€(gè)嵌入式設(shè)備,網(wǎng)卡一般一塊,F(xiàn)LASH一般也是一塊(也包括幾塊組成的FLASH組),但是這兩方面,基本都有完善的驅(qū)動(dòng),如網(wǎng)卡驅(qū)動(dòng)有很多,塊設(shè)備驅(qū)動(dòng),硬件層已經(jīng)有通用接口,不管是NOR FLASH還是NAND FLASH,文件系統(tǒng)層更是有了非常多、非常成熟的文件系統(tǒng),如 JFFS2、YAFFS、YAFFS2、EXT2、EXT3、ROMFS、CRAMFS等等,無需我們?cè)偃パ芯?,學(xué)會(huì)應(yīng)用即可。而除此之外的其它設(shè)備, 如AD、DA、CAN、RS485等等,都是需要根據(jù)應(yīng)用來進(jìn)行設(shè)計(jì)的,這才是一個(gè)產(chǎn)品區(qū)別于其它產(chǎn)品的重點(diǎn),更是市場價(jià)值增值點(diǎn)。 

      另外呢,也是前一點(diǎn)引申為而來的,學(xué)習(xí)Linux,準(zhǔn)備做產(chǎn)品的話,不要把Linux當(dāng)成了終極目標(biāo)(當(dāng)然,這是對(duì)應(yīng)用而言的),要有只是把 Linux當(dāng)成一個(gè)平臺(tái)的思想。更重要的還在各種產(chǎn)品所需求的專業(yè)技術(shù),如通信方面像CAN、RS485、GPRS等等,或者工業(yè)控制方面,IO控制、實(shí)時(shí)特性等等。Linxu博大精深,研究起來永無止境,但是在產(chǎn)品中,只要到了一個(gè)產(chǎn)品夠用就可以了(當(dāng)然,多一些更好,要視人而定)。 

      問:嵌入式應(yīng)用程序的開發(fā),應(yīng)用場景較多的是圖形界面還是字符界面,如果是圖形界面,開發(fā)環(huán)境QT和Minigui哪一種更合適,哪種類型的應(yīng)用程序在嵌入式系統(tǒng)中應(yīng)用比較多? 


      陳工回答: 

      對(duì)于嵌入式Linux的應(yīng)用,大多數(shù)的應(yīng)用并不需要圖形界面,比如交換機(jī)、路由器、嵌入式網(wǎng)關(guān)以及服務(wù)器等等。圖形界面呢,主要應(yīng)用在多媒體、手機(jī)等手持設(shè)備和一些需要圖形界面的人機(jī)交互系統(tǒng)。 

      嵌入式Linux可選圖形界面很多,上網(wǎng)找找的話,可以發(fā)現(xiàn)遠(yuǎn)非我們常說的QT、MiniGUI等。包括Tiny-X,matchbox、 OPIE、GPE等等。不同GUI有自己的特色,有自己的特殊應(yīng)用場合,對(duì)于產(chǎn)品開發(fā),根據(jù)需要選擇合適的GUI。對(duì)于學(xué)習(xí),自然是選擇容易得到、容易開發(fā)的GUI。QT是一個(gè)不錯(cuò)的選擇,由于QT有一個(gè)PC上的模擬器,可以在沒有實(shí)際液晶LCD的情況下,甚至在沒有任何硬件的情況下都可以在PC上進(jìn)行模擬開發(fā)。QT是收費(fèi)的,當(dāng)然,有免費(fèi)版可用。MiniGUI呢,純粹國產(chǎn)的,支持國貨,可以考慮選擇MiniGUI。這是一個(gè)輕量級(jí)的嵌入式GUI,可以跨平臺(tái),學(xué)習(xí)版也才100多塊。MiniGUI可以用于工業(yè)控制場合,QT在這方面的應(yīng)用目前還沒有遇到,主要用在手持設(shè)備。 

      我們?cè)陂_發(fā)中采用Tiny-X,這也是一個(gè)可以用于工業(yè)控制的GUI,基本兼容X-Window,體積小,占用資源少,速度快,穩(wěn)定。 


      對(duì)于Linux的應(yīng)用程序開發(fā),除了GUI程序之外,最基本的應(yīng)用程序有: 
      (1)串口編程。無論是在Windows下還是Linux下,串口編程都是極為復(fù)雜的,但是非常鍛煉一個(gè)人的編程水平和能力。 

      (2)網(wǎng)絡(luò)編程以及WEB相關(guān)編程。網(wǎng)絡(luò)編程的tcp、udp、tcp/ip等。至于WEB編程,主要是在系統(tǒng)開啟一個(gè)WEB服務(wù)器,制作一些網(wǎng)頁,通過遠(yuǎn)程登錄能夠?qū)φ麄€(gè)系統(tǒng)進(jìn)行配置甚至升級(jí)等功能。比如我們的路由器配置網(wǎng)頁。這種應(yīng)用在以后會(huì)越來越廣泛。 

      (3)另外一個(gè)就是Shell編程了。Shell的作用我想,UNIX世界的人都很清楚。在很多應(yīng)用里面,通過一些非常富有技巧性的Shell腳本,實(shí)現(xiàn)了非常復(fù)雜的功能,包括遠(yuǎn)程系統(tǒng)升級(jí)等。 

      以上我提到的這3方面,非常易于實(shí)驗(yàn),在沒有硬件,只有PC的情況都可以做。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多