qileilove

          blog已經(jīng)轉(zhuǎn)移至github,大家請訪問 http://qaseven.github.io/

          Java Zip文件解壓縮

          為了解壓縮zip都折騰兩天了,查看了許多谷歌百度來的code,
            真實無語了,絕大多數(shù)是不能用的。這可能跟我的開發(fā)環(huán)境有關(guān)吧。
            我用的是Ubuntu14.04,eclipse 用的是STS3.5,jdk81.8.0_20
            經(jīng)過兩天的努力檢驗了無數(shù)的code終于讓我找到一個還能用的可以解決中文亂碼問題。
            這個項目用maven構(gòu)建的依賴jar坐標如下
            <!-- 用于zip文件解壓縮 -->
            <dependency>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.7.0</version>
            </dependency>
            代碼如下:
          package com.uujava.mbfy.test;
          import java.io.File;
          import java.io.FileInputStream;
          import java.io.FileOutputStream;
          import java.io.IOException;
          import java.io.InputStream;
          import org.apache.tools.zip.ZipOutputStream;
          /**
          * @author k
          * @date 2014年10月7日 上午8:04:51
          * @Description: TODO(用于壓縮和解壓縮zip文件)
          * 尚須解決bug:
          * 這里采用硬編碼這定文件編碼,是否有辦法讀取壓縮文件時判斷起內(nèi)部文件名編碼。
          */
          public final class ZipUtils {
          public static void main(String[] args) throws Exception {
          // ZipFile("/home/k/Documents/testzip/寬屏透明html5產(chǎn)品展示.zip", "/home/k/Documents/testzip/index.html");
          unZipFile("/home/k/Documents/testzip/寬屏透明html5產(chǎn)品展示.zip",
          "/home/k/Documents/testzip/zip");
          }
          public static void zip(ZipOutputStream out, File f, String base,
          boolean first) throws Exception {
          if (first) {
          if (f.isDirectory()) {
          out.putNextEntry(new org.apache.tools.zip.ZipEntry("/"));
          base = base + f.getName();
          first = false;
          } else
          base = f.getName();
          }
          if (f.isDirectory()) {
          File[] fl = f.listFiles();
          base = base + "/";
          for (int i = 0; i < fl.length; i++) {
          zip(out, fl[i], base + fl[i].getName(), first);
          }
          } else {
          out.putNextEntry(new org.apache.tools.zip.ZipEntry(base));
          FileInputStream in = new FileInputStream(f);
          int b;
          System.out.println(base);
          while ((b = in.read()) != -1) {
          out.write(b);
          }
          in.close();
          }
          }
          @SuppressWarnings("unchecked")
          public static void unZipFileByOpache(org.apache.tools.zip.ZipFile zipFile,
          String unZipRoot) throws Exception, IOException {
          java.util.Enumeration e = zipFile.getEntries();
          System.out.println(zipFile.getEncoding());
          org.apache.tools.zip.ZipEntry zipEntry;
          while (e.hasMoreElements()) {
          zipEntry = (org.apache.tools.zip.ZipEntry) e.nextElement();
          InputStream fis = zipFile.getInputStream(zipEntry);
          if (zipEntry.isDirectory()) {
          } else {
          File file = new File(unZipRoot + File.separator
          + zipEntry.getName());
          File parentFile = file.getParentFile();
          parentFile.mkdirs();
          FileOutputStream fos = new FileOutputStream(file);
          byte[] b = new byte[1024];
          int len;
          while ((len = fis.read(b, 0, b.length)) != -1) {
          fos.write(b, 0, len);
          }
          fos.close();
          fis.close();
          }
          }
          }
          public static void ZipFile(String zipFileName, String inputFileName)
          throws Exception {
          org.apache.tools.zip.ZipOutputStream out = new org.apache.tools.zip.ZipOutputStream(
          new FileOutputStream(zipFileName));
          out.setEncoding("gbk");// 設(shè)置的和文件名字格式一樣或開發(fā)環(huán)境編碼設(shè)置一樣的話就能正常顯示了
          File inputFile = new File(inputFileName);
          zip(out, inputFile, "", true);
          System.out.println("zip done");
          out.close();
          }
          public static void unZipFile(String unZipFileName, String unZipPath)
          throws Exception {
          org.apache.tools.zip.ZipFile zipFile = new org.apache.tools.zip.ZipFile(
          unZipFileName, "gbk");
          unZipFileByOpache(zipFile, unZipPath);
          System.out.println("unZip Ok");
          }
          }

          posted @ 2014-10-09 10:28 順其自然EVO 閱讀(183) | 評論 (0)編輯 收藏

          關(guān)于測試人員的職業(yè)發(fā)展

           近期由于項目組人手不夠,需要招聘一些測試人員。本周及上周陸陸續(xù)續(xù)面試了十多個應征者,工作年限在2年~9年之間,但無一滿意。期間,種種感嘆,回想起去年面試六十余人僅有3人滿足要求,如有鯁在喉,還是吐槽一下。如有不對請大家也狂噴我。
            我的要求高么?
            我的要求其實是:有還算不錯的溝通能力,熟悉常見軟件開發(fā)流程,有一定的需求分析、用例設(shè)計能力,會基本的linux和sql操作能力。有一些代碼能力會加分。這是長期與現(xiàn)實妥協(xié)的結(jié)果。如果人還算機靈,其實我很愿意花時間來培養(yǎng)他們。
            面試結(jié)果
            令人惋惜的是,一個合適的人真的很難找。更令人惋惜的是,我看到好多入行很多年的同行,能力并沒有跟隨工作年限一同增長,有些做了五六年的人有時候給人感覺竟然還不如一個入行一兩年的年輕人。最令人遺憾的是,大部分同學竟然沒有一個明確的職業(yè)發(fā)展思路,即使有,也沒有經(jīng)過深入一些的思考,而是人云亦云。
            面試的一些細節(jié):
            因為從事的工作是業(yè)務密集型的,有的業(yè)務邏輯非常復雜,我們特意準備了一份不錯的需求(考慮到應試者沒有行業(yè)背景,給出了詳盡的專業(yè)說明和例子),并根據(jù)這份需求出了幾道用例設(shè)計的題。只有不到四分之一的應試者給出了讓人相對滿意的答案。我們內(nèi)部評估這份需求的時候,認為只要有過一兩年的用例設(shè)計經(jīng)驗,應該能答的不錯。
            我一般會根據(jù)簡歷問一些問題,看看簡歷的真實性。也會問一些基礎(chǔ)的測試知識,查看應試者的專業(yè)素質(zhì)。
            常見的問題:
            說說你常用的測試方法? 百分之九十的人只能答出等價類和邊界值。只有少數(shù)人可以講出其它測試用例設(shè)計方法,但深入問,從沒有一個人能有令人滿意的回答.
            給一個非常簡單的小例子,例如登陸操作,讓應試者回答如何使用等價類方法設(shè)計用例。但讓人吃驚的是仍然只有不到五分之一能夠給出比較滿意的答案。
            陳述一個缺陷的生命周期(你們是怎么管理bug的?)有一多半人能夠說出常見流程,但深入問一些問題:如缺陷如何同版本、測試輪次等結(jié)合起來,一些特殊情況如何處理等,很多人就懵了,而這些基本上都是工作中常用的。
            你做的最長的一個項目是什么?在這期間你遇到了什么問題讓你最頭疼?你如何解決它?十個人里大約只有一人能給出還算不錯的答案(能夠識別出問題,提出它帶來 的不利影響是什么,并能夠給出一定的解決方案就算是不錯的答案了)。
            你感興趣的測試工作是什么,你想在哪方面有所發(fā)展?十個人里有4個會說是自動化測試,3個會說性能測試,2個會說是管理,一個會說是白盒測試。并希望提供相應培訓。只有極少數(shù)人能夠說出具體的思路和技術(shù)項。
            如果繼續(xù)追問:你說的是性能測試吧?你有過這方面的學習么?一半會說看過一些網(wǎng)站上的技術(shù)文章,一半會說看過loadrunner的書。如果繼續(xù)追問,是哪本書?是哪類文章?有哪些具體的知識點能講一下么?90%答不上來。
            問:你有看過哪一本測試書籍?哪些技術(shù)博客?哪些網(wǎng)站?50%的人會說看過QTP的書(QTP的真正使用率已經(jīng)快趕上諾基亞的使用率了,國內(nèi)主流自動化的書竟然還是這個!),并且沒有真正在工作中使用過,然后就沒有別的了。有少一半人最近幾年一本技術(shù)書籍也沒有看過。
            如果有管理經(jīng)驗的應試者,我會問一些測試過程管理相關(guān)的問題,如給一個最簡單的題:如果測試時間不夠如何?十個人中只會有兩三個提到排定優(yōu)先級和測試裁剪,大部分人的回答竟然是加班也一定要搞完。我想說的:
            1.為了你的前途,請多明確一些個人能力思路吧。你五年后,十年后是個什么樣子?有沒有一個明確的想法?有沒有你五年后想達到的某個人的程度?如果這些思路不清楚,請多看看外面的世界,看看一些測試做得非常好的人是如何工作的,他們掌握了什么能力?學習他們,追趕他們并嘗試超越他們。最好認識他們,可以侃侃大山,志同道合抱團前進很好。另外目標別定太抽象,一定要是可以分解,可以檢查的。
            2.多讀一些測試書籍,測試的書并不是只有QTP!看看微軟測試專家史亮推薦的書單,這些都是不錯的好書:http://www.cnblogs.com/liangshi/archive/2011/03/07/1973525.html  有些書能夠幫助你把測試知識框架搭建起來,比照一下你還缺點啥?
            3.多讀一些其它書籍,不限于技術(shù)書籍。如果想讀的書有利于工作,推薦一些如何做思辨思維的書。《思考的藝術(shù)》《六頂思考帽》《你的燈亮著么》 《學會提問》是我喜歡的4本書。它們會教你怎么獨立思考,養(yǎng)成提問的習慣,而提問的習慣是我們現(xiàn)在的測試人員最缺乏的一件事情。人們往往拿了被測物就開始忙著寫用例,忙著測試。而不是先探索它、研究它。當然IT技術(shù)也要掌握,如果你的IT技能能夠趕上開發(fā),你發(fā)現(xiàn)你做測試的思路會非常的寬廣:)
            4.把書籍中的東西跟你的工作對比,把好的東西引入工作(這點是檢驗書本質(zhì)量的好方法,也是促進你思考,促進你能力提高的好方法。
            5.關(guān)注大牛們的技術(shù)博客。國內(nèi)寫好測試博客的人不是很多(很多人其實很有水平,但是不喜歡寫blog),但是國外有很多,有人整理了一個list也推薦給大家:http://ssnlove2008.blog.163.com/blog/static/3788942020093284842381/。
            6.搞定你所在行業(yè)的領(lǐng)域知識:如常見IT技術(shù),常見業(yè)務知識,這些知識掌握的越深,你的價值越高。測試技術(shù)是內(nèi)功,但是你能直接為企業(yè)帶來價值的最大之處是你對被測物熟悉程度,也就是你的領(lǐng)域知識!!!
            7.沒有方向?從你的工作入手,比如,你遇到的最大的難題是什么?我怎么解決它?我需要掌握什么樣的技術(shù)解決他?我要推動什么樣的組織改變來解決它?別人怎么解決它?有沒有更好的方法?使用后我改進了那些?google一下別人有沒有同樣的問題?嘗試作對比,如果覺得他做得好,嘗試聯(lián)系那個人討論一下。看看對方的進展。嘗試把活兒干得特別漂亮。你能解決10個中等問題以后,你的能力會有大幅度提高。
            8.嘗試做筆記。最好是在線的,推薦印象筆記和有道云筆記。
            9.堅持。
            10.保證身體健康,歲月會給你帶來別人的信任感(當然能力要隨著歲數(shù)增長)。
            能做到這里面的一半,兩年后你就能在專業(yè)上有高分通過我的面試:)當然肯定你也不見得會看得上我們的offer了。
            11.對于沒想好就跳槽,換行業(yè)的同學說:你再想想!你的很大價值是與你企業(yè)、行業(yè)綁定的。如:做了5年保險業(yè)務,你的領(lǐng)域知識至少值5w每年,換領(lǐng)域就沒了。你在一家公司證明了你自己,到新公司要重新證明你一遍,有的時候外部環(huán)境、機遇等會讓證明過程很痛苦,成本很高。
            另外的吐槽:
            野蠻生長沒有經(jīng)過系統(tǒng)訓練的同學非常多。這其實有很多因素,分析起來覺得有以下幾點:
            1.大學或者職業(yè)教育沒有非常好的課程體系(有些培訓機構(gòu)還行,但是也需要提高),其實測試技能需要系統(tǒng)訓練和長時間磨練才能有根本的增長,我們的職業(yè)教育或者再教育體系其實還是有很大空白的。
            2.說句實話,大家的讀書氛圍不夠濃厚。大家不喜歡看書。而讀書是再教育成本最低,又非常有效的途徑。相比于程序員,測試同學喜歡讀技術(shù)書籍的比率明顯的低,這是一個讓人悲傷的事實。真希望這種現(xiàn)象能夠改變。
            3.很多人是不喜歡coding才轉(zhuǎn)測試,或者是因為IT產(chǎn)業(yè)普遍薪水高才來做測試。不是真正熱愛這份工作,不熱愛其實做不好,因為興趣是最好的老師。
            4.很多人認為測試門檻低,young talent 不愿意干,測試吸引人才有點兒困難(我初入行的時候也有這種想法,也是當時被強拉來做測試的,當時想做的是coding和數(shù)據(jù)DBA相關(guān)工作并已經(jīng)有了一些積累,(我沒說我是啥人才啊))。說實話測試的入門門檻的確有一點點低,但是做好測試的門檻確是相當?shù)母撸S著系統(tǒng)越來越復雜,測試逐漸會比開發(fā)還難做,更有挑戰(zhàn)性,我這么說你信么?
            5.專業(yè)化社區(qū)還沒有形成規(guī)模,測試人員沒有能有效交流的平臺。這是跟美國和歐洲的一個挺大的差距。他們的社區(qū)做得挺好的,我們也有了一些很好的起步。如一些熱衷測試公益的同學,一些不錯的會議,一些不錯的線下活動,但還需要大大的發(fā)揚光大。
            真心希望測試行業(yè)的整體水平能夠逐漸提高起來。
            最后看一下測試大牛James Whittaker(Google測試之道 和 探索式軟件測試 的作者)對職業(yè)路程發(fā)展的一篇文章吧,你會受益很多

          posted @ 2014-10-09 10:27 順其自然EVO 閱讀(207) | 評論 (0)編輯 收藏

          一鍵獲取軟硬件配置及管理員組

          作為公司的IT運維,經(jīng)常要面對集團各種名頭的稽查,對我們工作量造成相當大的提高。公司的IT政策不允許使用非法軟件、USB口要關(guān)閉、電腦使用者不能有管理員權(quán)限等等。于是每一個最底層的工作人員一天到晚圍著用戶的電腦跑,查找硬件配置,軟件信息等,為了提高工作效率,于是就寫了以下批處理,減輕自己的工作負擔。
            功能說明:
            1.掃描機器硬件配置
            2.獲取電腦的網(wǎng)絡(luò)配置
            3.掃描機器軟件安裝列表
            4.查看Administrators組和Power Users組內(nèi)的用戶
            5.電腦的USB存儲端口開關(guān)情況
            6.電腦的共享信息
            7.掃描結(jié)果自動上傳
            掃描的結(jié)果以程序畫面顯示(重要內(nèi)容)及轉(zhuǎn)出以電腦名稱命名的文本文件(詳細內(nèi)容)。并將此文本文件自動上傳到共享文件夾中。
            以下是批處理的代碼:
          @echo off
          color 57
          title HardSoft Viewer
          mode con cols=67 lines=42
          setlocal  ENABLEDELAYEDEXPANSION
          echo Prepare For View ...
          del /f "%TEMP%\temp.txt" 2>nul
          dxdiag /t %TEMP%\temp.txt
          del /f "%COMPUTERNAME%.txt" 2>nul
          echo Start Hardware Viewer ...
          echo System Information: >>%COMPUTERNAME%.txt
          :system
          rem This must 30s
          if EXIST "%TEMP%\temp.txt" (
          for /f "tokens=1,2,* delims=:" %%a in ('findstr /c:" Machine name:" /c:" Operating System:" /c:" System Model:" /c:" Processor:" /c:"  Memory:" /c:" Card name:" /c:"Display Memory:" "%TEMP%\temp.txt"') do (
          set /a tee+=1
          if !tee! == 1 echo       Computer Name = %%b>>%COMPUTERNAME%.txt
          if !tee! == 2 echo       OS       Type = %%b>>%COMPUTERNAME%.txt
          if !tee! == 3 echo       System  Model = %%b>>%COMPUTERNAME%.txt
          if !tee! == 4 echo       CPU     Model = %%b>>%COMPUTERNAME%.txt
          if !tee! == 5 echo       RAM      Size = %%b>>%COMPUTERNAME%.txt
          if !tee! == 6 echo.>>%COMPUTERNAME%.txt
          if !tee! == 6 echo DisplayCard : >>%COMPUTERNAME%.txt
          if !tee! == 6 echo       Display  Card = %%b>>%COMPUTERNAME%.txt
          if !tee! == 7 echo       DisplayMemory = %%b>>%COMPUTERNAME%.txt
          )   ) else (
          ping /n 2 127.1>nul
          goto system
          )
          set tee=0
          echo.>>%COMPUTERNAME%.txt
          echo Mother Board:>>%COMPUTERNAME%.txt
          for /f "tokens=1,* delims==" %%a in ('wmic BASEBOARD get Manufacturer^,Product^,Version^,SerialNumber /value') do (
          set /a tee+=1
          if "!tee!" == "3" echo       Manufacturer     = %%b>>%COMPUTERNAME%.txt
          if "!tee!" == "4" echo       MotherBoard Model= %%b>>%COMPUTERNAME%.txt
          )
          set tee=0
          )
          echo.>>%COMPUTERNAME%.txt
          echo Hard Disk: >>%COMPUTERNAME%.txt
          for /f "skip=2 tokens=*" %%a in ('wmic DISKDRIVE get model ^,size /value') do (
          echo.      %%a>>%COMPUTERNAME%.txt
          )
          set tee=0
          echo.>>%COMPUTERNAME%.txt
          echo Network Card:>>%COMPUTERNAME%.txt
          for /f "tokens=2* delims==:" %%a in ('ipconfig/all^|find /i "Description" ^| findstr /v "Microsoft" ^| findstr /v "Tunneling"') do (
          set  name=%%a
          echo      NetCard Model = %%a>>%COMPUTERNAME%.txt
          )
          for /f "tokens=2* delims==:" %%a in ('ipconfig/all^|find /i "Physical Address" ^| findstr /v "00-00-00-00"') do (
          set  name=%%a
          echo      MAC Address = %%a>>%COMPUTERNAME%.txt
          )
          for /f "tokens=2* delims==:" %%a in ('ipconfig/all^|find /i "描述" ^| findstr /v "Microsoft" ^| findstr /v "Tunneling"') do (
          set  name=%%a
          echo      NetCard Model = %%a>>%COMPUTERNAME%.txt
          )
          for /f "tokens=2* delims==:" %%a in ('ipconfig/all^|find /i "物理地址" ^| findstr /v "00-00-00-00"') do (
          set  name=%%a
          echo      MAC Address = %%a>>%COMPUTERNAME%.txt
          )
          ver|find /i "windows xp">nul 2>nul&&goto xp||goto win7
          :xp
          for /f "tokens=2* delims==:" %%a in ('ipconfig/all^|find /i "IP Address"') do (
          set  name=%%a
          echo      IP Address = %%a>>%COMPUTERNAME%.txt
          )
          echo Start Software Viewer For XP...
          echo.>>%COMPUTERNAME%.txt
          echo Software Information:>>%COMPUTERNAME%.txt
          for /f "tokens=7 delims=\" %%i in ('reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ^| findstr /v "KB" 2^>nul') do (
          for /f "skip=4 tokens=2*" %%a in ('reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%%i" /v DisplayName 2^>nul' ) do (
          echo %%b>>%COMPUTERNAME%.txt
          )
          )
          for /f "tokens=2 delims=\" %%x in ('reg query HKU') do (
          for /f "tokens=8 delims=\" %%a in ('reg query "HKU\%%x\Software\Microsoft\Windows\CurrentVersion\Uninstall" 2^>nul') do (
          for /f "skip=4 tokens=2*" %%i in ('reg query "HKU\%%x\Software\Microsoft\Windows\CurrentVersion\Uninstall\%%a"  /v "DisplayName" 2^>nul') do (
          echo %%j>>%COMPUTERNAME%.txt
          )
          )
          )
          echo.>>%COMPUTERNAME%.txt
          if exist %windir%\system32\CCM\CcmExec.exe echo "SMS Client has been installed,please uninstall"
          if exist %windir%\system32\CCM\CcmExec.exe echo "SMS Client has been installed,please uninstall">>%COMPUTERNAME%.txt
          echo ==================================================================
          echo USB Information:
          echo.>>%COMPUTERNAME%.txt
          echo USB Information:>>%COMPUTERNAME%.txt
          for /f "skip=4 tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Services\usbstor" /v "start" 2^>nul' ) do (
          if "%%b"=="0x4" echo     USB is Close
          if "%%b"=="0x3" echo     USB is Open,Please Tag It.
          if "%%b"=="0x4" echo     USB is Close>>%COMPUTERNAME%.txt
          if "%%b"=="0x3" echo     USB is Open,Please Tag It.>>%COMPUTERNAME%.txt
          )
          goto last
          :win7
          for /f "tokens=2* delims==:" %%a in ('ipconfig/all^|find /i "IPV4"') do (
          set  name=%%a
          echo      IP Address = %%a>>%COMPUTERNAME%.txt
          )
          echo Start Software Viewer For Win7/8 ...
          rem for 32 win7
          echo.>>%COMPUTERNAME%.txt
          echo Software Information:>>%COMPUTERNAME%.txt
          for /f "tokens=7 delims=\" %%i in ('reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" ^| findstr /v "KB" 2^>nul ') do (
          for /f "skip=2 tokens=3* delims= " %%a in ('reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\%%i" /v DisplayName 2^>nul') do (
          echo %%a %%b>>%COMPUTERNAME%.txt
          )
          )
          for /f "tokens=8 delims=\" %%i in ('reg query "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" 2^>nul ^| findstr /v "KB" 2^>nul ') do (
          for /f "skip=2 tokens=3* delims= " %%a in ('reg query "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\%%i" /v DisplayName 2^>nul') do (
          echo %%a %%b>>%COMPUTERNAME%.txt
          )
          )
          for /f "tokens=2 delims=\" %%x in ('reg query HKU') do (
          for /f "tokens=8 delims=\" %%a in ('reg query "HKU\%%x\Software\Microsoft\Windows\CurrentVersion\Uninstall" 2^>nul') do (
          for /f "skip=2 tokens=2*" %%i in ('reg query "HKU\%%x\Software\Microsoft\Windows\CurrentVersion\Uninstall\%%a"  /v "DisplayName" 2^>nul') do (
          echo %%j>>%COMPUTERNAME%.txt
          )
          )
          )
          for /f "tokens=2 delims=\" %%x in ('reg query HKU') do (
          for /f "tokens=9 delims=\" %%a in ('reg query "HKU\%%x\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" 2^>nul') do (
          for /f "skip=2 tokens=2*" %%i in ('reg query "HKU\%%x\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\%%a"  /v "DisplayName" 2^>nul') do (
          echo %%j>>%COMPUTERNAME%.txt
          )
          )
          )
          echo ==================================================================
          echo USB Information:
          echo.>>%COMPUTERNAME%.txt
          echo USB Information:>>%COMPUTERNAME%.txt
          for /f "skip=2 tokens=2*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Services\usbstor" /v "start" 2^>nul' ) do (
          if "%%b"=="0x4" echo     USB is Close
          if "%%b"=="0x3" echo     USB is Open,Please Tag It.
          if "%%b"=="0x4" echo     USB is Close>>%COMPUTERNAME%.txt
          if "%%b"=="0x3" echo     USB is Open,Please Tag It.>>%COMPUTERNAME%.txt
          )
          :last
          echo ==================================================================
          echo Admin Users:
          echo.>>%COMPUTERNAME%.txt
          echo Admin Users:>>%COMPUTERNAME%.txt
          for /f "skip=6 tokens=*" %%i in ('net localgroup Administrators ^| findstr /v "㏑" ^| findstr /v "命" ^| findstr /v "command"') do (
          echo       %%i
          echo       %%i>>%COMPUTERNAME%.txt
          )
          echo Power Users:
          echo.>>%COMPUTERNAME%.txt
          echo Power Users:>>%COMPUTERNAME%.txt
          for /f "skip=6 tokens=*" %%i in ('net localgroup "Power Users" ^| findstr /v "㏑" ^| findstr /v "命" ^| findstr /v "command"') do (
          echo       %%i
          echo       %%i>>%COMPUTERNAME%.txt
          )
          echo ==================================================================
          echo FileShare Information:
          echo.>>%COMPUTERNAME%.txt
          echo FileShare Information:>>%COMPUTERNAME%.txt
          for /f "skip=4 tokens=*" %%i in ('net share 2^>nul ^| findstr /v "㏑" ^| findstr /v "命" ^| findstr /v "command"' ) do (
          echo  %%i
          echo  %%i>>%COMPUTERNAME%.txt
          )
          echo =========================Viewer Over==============================
          net use \\192.168.1.1 password /user:username 1>nul 2>nul
          copy %COMPUTERNAME%.txt \\192.168.1.1\HardFile$\
          net use \\192.168.1.1\IPC$ /del 1>nul 2>nul
          pause
          start %COMPUTERNAME%.txt
           現(xiàn)在來查看下掃描結(jié)果XP/WIN8對比
            以及產(chǎn)生的掃描結(jié)果
          System Information:
          Computer Name =  C0300022B068
          OS       Type =  Windows 8.1 專業(yè)版 64-bit (6.3, Build 9600) (9600.winblue_gdr.131030-1505)
          System  Model =  System Product Name
          CPU     Model =  Pentium(R) Dual-Core  CPU      E5500  @ 2.80GHz (2 CPUs), ~2.8GHz
          RAM      Size =  4096MB RAM
          DisplayCard =
          Display  Card =  Microsoft 基本顯示適配器
          DisplayMemory =  256 MB
          Mother Board:
          Manufacturer     = ASUSTeK Computer INC.
          MotherBoard Model= P5KPL-AM
          Hard Disk:
          Model=ST3500418AS ATA Device
          Size=500038694400
          Network Card:
          NetCard Model =  Realtek PCIe FE Family Controller
          MAC Address =  00-23-54-0A-31-A9
          IP Address =  172.17.44.103(首選)
          Software Information:
          谷歌拼音輸入法 2.7
          7-Zip 9.30 (x64 edition)
          Windows Live MIME IFilter
          Java 8 Update 20 (64-bit)
          Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.4148
          Java SE Development Kit 8 Update 20 (64-bit)
          Microsoft Application Error Reporting
          PDF-Viewer
          Microsoft Visual C++ 2005 Redistributable (x64)
          MSVCRT110_amd64
          VIA 平臺設(shè)備管理員
          Mozilla Maintenance Service
          Notepad++
          Windows Live 軟件包
          Windows Live UX Platform
          Windows Live Writer
          Windows Live UX Platform Language Pack
          Junk Mail filter update
          Radmin Viewer 3.5
          Windows Live Photo Common
          Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.4148
          Platform
          Windows Live 軟件包
          Windows Live Writer
          Windows Live Writer
          微軟設(shè)備健康助手
          Windows Live Communications Platform
          Java Auto Updater
          Windows Live Mail
          Windows Live Writer Resources
          LibreOffice 4.3.0.4
          Windows Live Installer
          Windows Live Writer
          Windows Live Writer Resources
          Windows Live UX Platform Language Pack
          Windows Live 程式集
          Microsoft Visual C++ 2005 Redistributable
          Photo Common
          MSVCRT
          MSVCRT110
          Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022
          Adobe Reader 8 - Chinese Traditional
          Windows Live PIMT Platform
          Windows Live Mail
          Windows Live Mail
          Windows Live SOXE
          MSVCRT_amd64
          Windows Live SOXE Definitions
          Photo Common
          D3DX10
          Microsoft WSE 3.0 Runtime
          Microsoft Visual C++ 2008 Redistributable - x86 9.0.21022.218
          Microsoft WSE 2.0 SP3 Runtime
          USB Information:
          USB is Open,Please Tag It.
          Admin Users:
          Administrator
          Luke
          Power Users:
          FileShare Information:
          ADMIN$       C:\Windows                      遠程管理
          C$           C:\                             默認共享
          IPC$                                         遠程 IPC
          D$           D:\                             默認共享
          E$           E:\                             默認共享
          F$           F:\                             默認共享
          HardFile$    D:\HardFile
          cd_rom       D:\cd_rom
          HardSoftViewer
          D:\HardSoftViewer
          HardwareViewer 20140923
          D:\HardwareViewer 20140923
          public       D:\public

          posted @ 2014-10-09 10:24 順其自然EVO 閱讀(354) | 評論 (0)編輯 收藏

          一次關(guān)于缺陷狀態(tài)的爭論

          在我們的缺陷庫中,有一種缺陷狀態(tài)為“待提交”,這是一種最終狀態(tài),并且表明這個缺陷實際上是無效的,不會計入到最終的缺陷報告中。
            一個項目開始了,在幾個版本之后,缺陷庫中存在數(shù)個“待提交”狀態(tài)的缺陷,原因無外乎幾種:與其它缺陷重復、測試人員的失誤等。
            再來說由此產(chǎn)生的爭論:一個同事認為,“待提交”狀態(tài)的缺陷不應當存在于缺陷庫中。從字面上來理解,“待提交”就是提交之前的預狀態(tài),將其修改為有效的缺陷后重新置為“新建”狀態(tài)。這樣從缺陷庫來看,不會暴露出測試人員的失誤,可以給上級或其他人一種測試非常高效的印象。
            但我不這么認為:無論是重復的缺陷也好,測試人員的失誤也好,這都是系統(tǒng)測試過程中一種真實的反應和記錄,并且這是不可避免一定會出現(xiàn)的。在測試活動結(jié)束之后,可以對所有“待提交”狀態(tài)的缺陷做統(tǒng)一的分析,指導后續(xù)的測試活動。也就是說“待提交”狀態(tài)的缺陷也是一種資源,可以挖掘出有用的信息來。如果人為的使其消失,盡管表面上看來顯示出測試的高效,但是真正了解軟件測試過程的人必然也能發(fā)現(xiàn)其中的可疑之處。
            關(guān)于爭論的結(jié)果,我只能說對方才是這個項目的負責人。

          posted @ 2014-10-09 10:22 順其自然EVO 閱讀(200) | 評論 (0)編輯 收藏

          CentOS安裝搭建BugFree

          BugFree基于PHP和MySQL開發(fā),是免費且開發(fā)源代碼的缺陷管理系統(tǒng)。服務器端在LinuxWindows平臺上都可以運行;客戶端無需安裝任何軟件,通過IE,F(xiàn)ireFox等瀏覽器就可以自由使用。
            BugFree 2 在BugFree 1.1的基礎(chǔ)上,集成了Test Case和Test Result的管理功能。具體使用流程是:首先創(chuàng)建Test Case(測試用例),運行Test Case產(chǎn)生Test Result(測試結(jié)果),運行結(jié)果為Failed的Case,可以直接創(chuàng)建Bug。Test Case標題、步驟和Test Result運行環(huán)境等信息直接復制到新建的Bug中。
            關(guān)閉selinux:
            # vim /etc/selinux/config
            將配置文件中 SELINUX=permissive
            關(guān)閉iptables
            # chkconfig --level 35 iptables off
            [root@bugfree ~]# chkconfig --list |grep iptables   查看iptables狀態(tài)     0:off 1:off 2:on 3:off 4:on 5:off 6:off
            1. 安裝apache
            yum install httpd
            2. 安裝mysql
            yum install mysql mysql-server
            注:已安裝mysql的跳過此步驟
            3. 安裝PHP
            yum install php php-mysql php-gd php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc
            4. 安裝PHP加密算法插件
            yum install libmcrypt
            yum install php-mcrypt
            centos 6.x 默認yum源沒有l(wèi)ibmcrypt 相關(guān)的包
            從這里下載: http://www.lishiming.net/data/attachment/forum/month_1211/epel-release-6-7.noarch.rpm
            然后再
            yum install -y  libmcrypt-devel   即可解決安裝php加密算法找不到y(tǒng)um源的問題
            注:libmcrypt是加密算法擴展庫,php-mcrypt是Mcrypt對PHP的一個擴展
            5. 安裝bugfree
            bugfree官網(wǎng)已停止對它進行更新,我在百度搜索的一個版本是:bugfree3.0.4
            解壓:unzip bugfree3.zip
            重命名解壓后的文件:mv bugfree3 bugfree
            把bugfree放到apache的DocumentRoot:mv bugfree /var/www/html
            改變bugfree的讀寫權(quán)限:chmod -R 777 bugfree
            6. 配置
            1) 配置apache
            vi /etc/httpd/conf/httpd.conf
            修改默認端口號 Listen 80 --> Listen 7999
            啟動httpd服務:service httpd start
           2) 配置mysql
            啟動mysqld服務:service mysqld start
            注:mysqld服務已啟動的跳過此步驟
            登陸mysql:mysql -uroot -p
            創(chuàng)建新用戶:CREATE USER 'bugfree'@'localhost' IDENTIFIED BY '123456';
            新用戶授權(quán):grant all privileges on *.* to bugfree@localhost identified by '123456';
            注:以上授權(quán)方式需要把mysql和bugfree安裝在同一臺機器上
            3) 配置bugfree
            瀏覽器訪問http://<servername>:port/bugfree/install
            例如:http://192.168.1.20:7999/bugfree/install
            安裝第一步有個提示/var/www/html/BugFile/  文件不可讀不可寫
            創(chuàng)建BugFile文件夾  mkdir BugFile
            chmod -R 777 BugFile 即可解決
            按照提示配置bugfree關(guān)聯(lián)的數(shù)據(jù)庫
            注:要在root權(quán)限下操作,即用root登陸或者sudo來操作
            7. 完成安裝,進入BugFree
            初始用戶名: admin 初始密碼:123456
            查看是否已經(jīng)是開機啟動:chkconfig --list|grep httpd
            [root@localhost ~]# chkconfig --list|grep httpd
            mysql           0:關(guān)閉  1:關(guān)閉  2:關(guān)閉  3:關(guān)閉  4:關(guān)閉  5:關(guān)閉  6:關(guān)閉
            0:關(guān)機。
            1:單用戶字符界面。
            2:不具備網(wǎng)絡(luò)文件系統(tǒng)(NFS)功能的多用戶字符界面。
            3:具有網(wǎng)絡(luò)功能的多用戶字符界面。
            4: 保留不用。
            5:具有網(wǎng)絡(luò)功能的圖形用戶界面。
            6:重新啟動系統(tǒng)。
            用命令 chkconfig --level 2345 mysqld on (更改相應級別即可)更改httpd隨系統(tǒng)啟動狀態(tài)

          posted @ 2014-10-09 10:21 順其自然EVO 閱讀(871) | 評論 (0)編輯 收藏

          VS2010的反匯編功能測試

           F10單步執(zhí)行前
            F10單步執(zhí)行后

          posted @ 2014-10-09 10:20 順其自然EVO 閱讀(187) | 評論 (0)編輯 收藏

          如何寫更好的自動化測試用例

          抱歉, 文章的開頭我需要先給這個[自動化測試用例]設(shè)一個范圍. 自動化用例的形式有很多, 根據(jù)測試對象和測試環(huán)境的不同, 有各種script和自動化框架來支持你開發(fā)出各式各樣的用例.
            而本文是基于Robot Framework, 一種keyword driven(關(guān)鍵字驅(qū)動)的自動化測試框架來講的. 如果你是被題目給騙進來的, 那就說明我的第一個目的已經(jīng)達到了, 哈哈!
            關(guān)于更多Robot Framework的信息請google.
            工作中我經(jīng)常需要去review其他同事的自動化用例(是的,像軟件代碼那樣被review). 我通常從4個方面來審查用例的質(zhì)量:Readability, Maintainability, Reliability, Performance,
            而這4個方面可以具體到下面這些具體工作:
            Coding Style
            良好的規(guī)范可以極大的增強用例的可讀性和可維護性. 當這些用例被轉(zhuǎn)交給他人時, 也會因為相同的coding style增加對方的接受度.
            當一位"新人"參與用例編寫時我會首先把注意力放到Coding Style上, 因為這也是最容易做到的. 為此我們定義了一些規(guī)范:
            a.好的命名規(guī)范
            文件名不能包含特殊符號并且遵照特定的格式. 不同作用域的變量采用不同的命名方式, 變量名描述的更有意義, 全局變量要在Variables表中定義..
            還有case的命名、keyword、case setup、teardown.
            b.documentation
            作為用例的補充信息documentation是必不可少的,如果測試用例本身或者背景太過復雜, 我們還可以給suite、test case、keyword分級加注釋.
            c.tags
            給用例打上正確的標簽.標簽的應用非常的廣泛和靈活既可以拿來做用例篩選、版本管理、統(tǒng)計、調(diào)度策略,還可以為一些測試策略如[基于風險的測試]提供方案.
            d....
            讓case讀起來像文檔
            在考慮Coding Style時我們可以設(shè)置一些固定的規(guī)則,大家只要按照這個規(guī)則來做,實踐幾次之后Coding Style就會趨于統(tǒng)一. 而考慮將case寫的如同文檔一般則需要更多的主觀能動性.
            為什么需要這樣的mindset? 公司開始推行敏捷之后,測試也在不斷的追求更敏捷的測試.敏捷強調(diào) "快速進入, 不斷迭代", 而文檔在整個開發(fā)過程中不可避免的被弱化, 需求設(shè)計不斷的更新,
            文檔往往不能被很及時的更新. 那么怎樣可以讓測試人員如何快速的掌握某個功能或者產(chǎn)品的需求和當前狀態(tài)呢? 答案是用例.
            a.簡潔明了的documatation
            通過增加[用例注釋]來增強用例的可讀性是一個不錯的辦法.一個suite文件往往包含了一組測試用例,suite level的documation通常可以包含該組測試的背景信息、這組測試的目的、特殊的環(huán)境配置說明等.
            要控制documatation的長度,可以通過添加link來擴展更多信息.不要過多的描述測試的詳細內(nèi)容,用例本身應該對其有很好的描述.也不要因為文檔而文檔,將case名或者重復的信息放在上面.
            case level的documatation我們覺得一般情況下是不需要的, 因為case的結(jié)構(gòu)本身應該足夠清楚的描述.
            b.清晰的用例名
            用例文件名應該能簡單描述文件內(nèi)所有case的測試目的.我們建立一個目錄來存儲測試點相近的測試用例, 目錄名可以更抽象, 然后我們給這個用例一致的命名規(guī)則.
            這樣當在你瀏覽一組用例時,僅僅通過用例名就能大致了解里面的測試內(nèi)容,也方便你尋找某個case. 應該更多的從用戶角度來思考文件名,對比下面兩個文件名:
            Suite Name1: creation handling after remove unnecessary fatal error.html
            Suite Name2: Avoid DSP restart by removing unnecessary fatal error when timeout happened during call creation.html
            c.條例清晰的case step
            case name
            case step
            keyword
            case的獨立性
            通常一個test suite包含了一組相近的或者有關(guān)聯(lián)的test case. 而每一個test case應該只測試一種場景,根據(jù)case復雜程度的不同場景同樣可大可小,可以是某個功能的測試也可以是端到端的完整測試.(當然也有特殊的寫法比如工作流測試和數(shù)據(jù)驅(qū)動.) case的獨立性又有哪些需要關(guān)注的點呢?
            首先一個test suite內(nèi)的test case在執(zhí)行時不應該相互影響, 應該將通用的背景部分提取出來放到suite setup中, 允許我隨機的跑某一個case或者亂序的跑這些case. 如果case的步驟有造成環(huán)境被破壞的風險,那應該在case teardown中將環(huán)境恢復,并且在case setup中做環(huán)境監(jiān)察以及時的終止case. suite level和folder level同樣要注意獨立性的問題,在CRT中通常會將數(shù)百數(shù)千的case放在一起跑,robot并不會規(guī)定case執(zhí)行的順序所以從某種程度上來說它是隨機的.獨立性還體現(xiàn)在case fail時信息的抓取上,經(jīng)過一個晚上大批case的執(zhí)行之后,環(huán)境通常已被破壞, 希望通過保留現(xiàn)場來用作case失敗問題定位是不現(xiàn)實的.
            所以每個case都應該準確的收集其開始和結(jié)束之間的信息.
            case的可遷移性
            case的可遷移性主要考慮:case對執(zhí)行環(huán)境的依賴,case對外部設(shè)備的依賴,case對測試對象的依賴.
            a.避免依賴執(zhí)行環(huán)境,你一直在個人PC上編寫的用例并執(zhí)行測試,但是不久之后你的用例就會被遷移到組內(nèi)的測試執(zhí)行服務器上,之后又被部署到持續(xù)集成服務器上,
            中途也有可能被其他同事下載到他的個人PC上來執(zhí)行測試.所以在編寫用例時我們要避免支持不同平臺的不同庫(windows,linux)和或者不同的腳本命令(CentOS,RedHat,MacOS)
            總之要像Java宣揚的那樣"一處編譯處處運行".
            b.在通信領(lǐng)域為了測試需要或者擴展測試覆蓋,我們會引入一些外部設(shè)備如Spirent、Cisco的一些輔助測試設(shè)備,各種網(wǎng)絡(luò)設(shè)備交換機、路由器,還有一些自行開發(fā)的模擬設(shè)備.
            外部設(shè)備會不斷的升級或者更換,在編寫用例時我們就需要考慮如何用一套case更好的兼容這些測試設(shè)備. 我有如下幾點建議:
           i.首先將外部設(shè)備的操作從測試用例步驟中剝離出去,組織成組級別的庫.
            ii.
            c.對測試對象的依賴,這里我考慮到的是如果測試對象是一個軟件平臺,軟件平臺通常需要適配多種的設(shè)備.而設(shè)備的硬件配置可能是多種多樣的,CPU、內(nèi)存、組件的性能和數(shù)量都可能不同.
            對測試對象的依賴不僅要考慮在不同設(shè)備上的可執(zhí)行性,重點要考慮測試覆蓋率,由于設(shè)備組件的增多你的用例可能無法覆蓋到這些組件,或者捕捉不到某個性能瓶頸,這樣測試結(jié)果的可靠性也大打折扣.
            case的可重用性
            自動化用例的開發(fā)通常是一項費時的工作,它需要的時間會是手動執(zhí)行用例的10倍、20倍甚至更多. 我們通過搭建測試框架和封裝資源庫來實現(xiàn)最大范圍的可重用性.
            這里我考慮用例的可重用性包括兩塊:邏輯層的抽象和業(yè)務層的重用.
            對一個產(chǎn)品或者功能進行自動化工作時,我們要考慮這些可用性:首先根據(jù)測試邏輯的不同對測試用例進行分類,根據(jù)邏輯的不同選擇搭建有針對性的case框架,Work Flow, Data Driven等.
            建立公共的庫,將業(yè)務的原子操作抽象出來,并且鼓勵其他同事對庫進行補充和調(diào)用,避免duplicated庫開發(fā).抽象的API通常需要足夠的原子和靈活才會被大眾所接受. 基于底層API編寫的業(yè)務操作也具備可重用性,比方說測試場景(背景資源)的建立、工作流的操作組合、檢查點都可以被復用. 層次分明的抽取時重用性的基礎(chǔ),提高可重用性可以減少開發(fā)時間,也方便日后的維護中的迭代修改.
            case的效率
            不同的case執(zhí)行時間相距甚遠,短則數(shù)秒長則數(shù)小時甚至數(shù)天,數(shù)秒鐘的簡單功能測試用例和穩(wěn)定性測試耗時數(shù)天的用例本身是沒有什么可比性的.但是我當我們放眼某一個或者某一組case時,我們需要重視效率.不論是敏捷還是持續(xù)集成都講究快速的反饋,開發(fā)人員能在提交代碼后快速的獲得測試結(jié)果反饋,測試人員能在最短的時間內(nèi)執(zhí)行更大范圍的測試覆蓋,不僅能提高團隊的工作效率也可增強團隊的信心.
            在編寫用例時我們應該注意哪些方面來提高用例的性能?
            對于單一的case我的注意點多放在一些細節(jié)上,例如:
            1.執(zhí)行條件的檢查,如果檢查失敗,則盡快退出執(zhí)行.
            2.將執(zhí)行環(huán)境搭建或者資源建立和清除 抽取到suite甚至folder level, 抽取時可能需要做一些組合, 但決不允許出現(xiàn)重復的建刪操作.
            3.用例中不允許出現(xiàn)sleep,sleep通常緊接著hard code的時間,不僅效率低還會因為環(huán)境的切換使得執(zhí)行失敗.建議用"wait until ..."來代替.
            4.如有不可避免的sleep,我通常會再三確認其是否清楚它的必要性.
            對于批量的case,我們要如何才能獲得更高的效率呢?
            1.首先我們考慮到可以并行的執(zhí)行一組case來提高效率,并行方案總有著嚴苛的條件:
            2.為了獲得更快的反饋,我們將軟件質(zhì)量分為0~10級,對應的把測試用例分為6~10級,從普通的功能測試開始測試復雜度逐級遞增.
            不同的開發(fā)階段或者是針對不同的測試目的我們就可以有選擇的調(diào)用不同級別的用例.比方說我們調(diào)用6級的cases來測試新功能代碼作為冒煙測試的用例集;軟件人員修改了BUG,我可以根據(jù)BUG的復雜度選擇7和8級的用例來驗證,系統(tǒng)級測試時我們又會主要測試8和9級的用例.
            分級可以靈活調(diào)度用例,并給出更快的反饋,加速迭代過程.
            3.基于風險的測試
            基于風險的測試簡單的說就是根據(jù)優(yōu)先級來選擇需要運行的測試,優(yōu)先級根據(jù)兩個最基本的維度:
            功能點發(fā)生錯誤的概率,以及發(fā)生錯誤后的嚴重性,根據(jù)兩者分值的乘積來排序優(yōu)先級.
            一般從用例失敗率,bug統(tǒng)計,出錯的代碼段,更新的代碼段來考慮調(diào)度.比方說根據(jù)BUG修改的代碼段和功能區(qū)域來選擇對應的測試.開發(fā)人員通常反對這種方式,只有100%的測試覆蓋才能給他們足夠的信心.
            以上是個人的一些積累,由于框架的限制一些建議不一定適用于你的實際工作. 如果你有什么建議歡迎留言. Thx!

          posted @ 2014-10-09 10:16 順其自然EVO 閱讀(758) | 評論 (0)編輯 收藏

          編寫屬于你的第一個Linux內(nèi)核模塊

           內(nèi)核編程常常看起來像是黑魔法,而在亞瑟 C 克拉克的眼中,它八成就是了。Linux內(nèi)核和它的用戶空間是大不相同的:拋開漫不經(jīng)心,你必須小心翼翼,因為你編程中的一個bug就會影響到整個系統(tǒng)。浮點運算做起來可不容易,堆棧固定而狹小,而你寫的代碼總是異步的,因此你需要想想并發(fā)會導致什么。而除了所有這一切之外,Linux內(nèi)核只是一個很大的、很復雜的C程序,它對每個人開放,任何人都去讀它、學習它并改進它,而你也可以是其中之一。
            學習內(nèi)核編程的最簡單的方式也許就是寫個內(nèi)核模塊:一段可以動態(tài)加載進內(nèi)核的代碼。模塊所能做的事是有限的——例如,他們不能在類似進程描述符這樣的公共數(shù)據(jù)結(jié)構(gòu)中增減字段(LCTT譯注:可能會破壞整個內(nèi)核及系統(tǒng)的功能)。但是,在其它方面,他們是成熟的內(nèi)核級的代碼,可以在需要時隨時編譯進內(nèi)核(這樣就可以摒棄所有的限制了)。完全可以在Linux源代碼樹以外來開發(fā)并編譯一個模塊(這并不奇怪,它稱為樹外開發(fā)),如果你只是想稍微玩玩,而并不想提交修改以包含到主線內(nèi)核中去,這樣的方式是很方便的。
            在本教程中,我們將開發(fā)一個簡單的內(nèi)核模塊用以創(chuàng)建一個/dev/reverse設(shè)備。寫入該設(shè)備的字符串將以相反字序的方式讀回(“Hello World”讀成“World Hello”)。這是一個很受歡迎的程序員面試難題,當你利用自己的能力在內(nèi)核級別實現(xiàn)這個功能時,可以使你得到一些加分。在開始前,有一句忠告:你的模塊中的一個bug就會導致系統(tǒng)崩潰(雖然可能性不大,但還是有可能的)和數(shù)據(jù)丟失。在開始前,請確保你已經(jīng)將重要數(shù)據(jù)備份,或者,采用一種更好的方式,在虛擬機中進行試驗。
            盡可能不要用root身份
            默認情況下,/dev/reverse只有root可以使用,因此你只能使用sudo來運行你的測試程序。要解決該限制,可以創(chuàng)建一個包含以下內(nèi)容的/lib/udev/rules.d/99-reverse.rules文件:
            SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"
            別忘了重新插入模塊。讓非root用戶訪問設(shè)備節(jié)點往往不是一個好主意,但是在開發(fā)其間卻是十分有用的。這并不是說以root身份運行二進制測試文件也不是個好主意。
            模塊的構(gòu)造
            由于大多數(shù)的Linux內(nèi)核模塊是用C寫的(除了底層的特定于體系結(jié)構(gòu)的部分),所以推薦你將你的模塊以單一文件形式保存(例如,reverse.c)。我們已經(jīng)把完整的源代碼放在GitHub上——這里我們將看其中的一些片段。開始時,我們先要包含一些常見的文件頭,并用預定義的宏來描述模塊:
            #include <linux/init.h>
            #include <linux/kernel.h>
            #include <linux/module.h>
            MODULE_LICENSE("GPL");
            MODULE_AUTHOR("Valentine Sinitsyn <valentine.sinitsyn@gmail.com>");
            MODULE_DESCRIPTION("In-kernel phrase reverser");
            這里一切都直接明了,除了MODULE_LICENSE():它不僅僅是一個標記。內(nèi)核堅定地支持GPL兼容代碼,因此如果你把許可證設(shè)置為其它非GPL兼容的(如,“Proprietary”[專利]),某些特定的內(nèi)核功能將在你的模塊中不可用。
            什么時候不該寫內(nèi)核模塊
            內(nèi)核編程很有趣,但是在現(xiàn)實項目中寫(尤其是調(diào)試)內(nèi)核代碼要求特定的技巧。通常來講,在沒有其它方式可以解決你的問題時,你才應該在內(nèi)核級別解決它。以下情形中,可能你在用戶空間中解決它更好:
            你要開發(fā)一個USB驅(qū)動 —— 請查看libusb。
            你要開發(fā)一個文件系統(tǒng) —— 試試FUSE。
            你在擴展Netfilter —— 那么libnetfilter_queue對你有所幫助。
            通常,內(nèi)核里面代碼的性能會更好,但是對于許多項目而言,這點性能丟失并不嚴重。
            由于內(nèi)核編程總是異步的,沒有一個main()函數(shù)來讓Linux順序執(zhí)行你的模塊。取而代之的是,你要為各種事件提供回調(diào)函數(shù),像這個:
            static int __init reverse_init(void)
            {
            printk(KERN_INFO "reverse device has been registered\n");
            return 0;
            }
            static void __exit reverse_exit(void)
            {
            printk(KERN_INFO "reverse device has been unregistered\n");
            }
            module_init(reverse_init);
            module_exit(reverse_exit);
           這里,我們定義的函數(shù)被稱為模塊的插入和刪除。只有第一個的插入函數(shù)是必要的。目前,它們只是打印消息到內(nèi)核環(huán)緩沖區(qū)(可以在用戶空間通過dmesg命令訪問);KERN_INFO是日志級別(注意,沒有逗號)。__init和__exit是屬性 —— 聯(lián)結(jié)到函數(shù)(或者變量)的元數(shù)據(jù)片。屬性在用戶空間的C代碼中是很罕見的,但是內(nèi)核中卻很普遍。所有標記為__init的,會在初始化后釋放內(nèi)存以供重用(還記得那條過去內(nèi)核的那條“Freeing unused kernel memory…[釋放未使用的內(nèi)核內(nèi)存……]”信息嗎?)。__exit表明,當代碼被靜態(tài)構(gòu)建進內(nèi)核時,該函數(shù)可以安全地優(yōu)化了,不需要清理收尾。最后,module_init()和module_exit()這兩個宏將reverse_init()和reverse_exit()函數(shù)設(shè)置成為我們模塊的生命周期回調(diào)函數(shù)。實際的函數(shù)名稱并不重要,你可以稱它們?yōu)閕nit()和exit(),或者start()和stop(),你想叫什么就叫什么吧。他們都是靜態(tài)聲明,你在外部模塊是看不到的。事實上,內(nèi)核中的任何函數(shù)都是不可見的,除非明確地被導出。然而,在內(nèi)核程序員中,給你的函數(shù)加上模塊名前綴是約定俗成的。
            這些都是些基本概念 – 讓我們來做更多有趣的事情吧。模塊可以接收參數(shù),就像這樣:
            # modprobe foo bar=1
            modinfo命令顯示了模塊接受的所有參數(shù),而這些也可以在/sys/module//parameters下作為文件使用。我們的模塊需要一個緩沖區(qū)來存儲參數(shù) —— 讓我們把這大小設(shè)置為用戶可配置。在MODULE_DESCRIPTION()下添加如下三行:
            static unsigned long buffer_size = 8192;
            module_param(buffer_size, ulong, (S_IRUSR | S_IRGRP | S_IROTH));
            MODULE_PARM_DESC(buffer_size, "Internal buffer size");
            這兒,我們定義了一個變量來存儲該值,封裝成一個參數(shù),并通過sysfs來讓所有人可讀。這個參數(shù)的描述(最后一行)出現(xiàn)在modinfo的輸出中。
            由于用戶可以直接設(shè)置buffer_size,我們需要在reverse_init()來清除無效取值。你總該檢查來自內(nèi)核之外的數(shù)據(jù) —— 如果你不這么做,你就是將自己置身于內(nèi)核異常或安全漏洞之中。
            static int __init reverse_init()
            {
            if (!buffer_size)
            return -1;
            printk(KERN_INFO
            "reverse device has been registered, buffer size is %lu bytes\n",
            buffer_size);
            return 0;
            }
            來自模塊初始化函數(shù)的非0返回值意味著模塊執(zhí)行失敗。
            導航
            但你開發(fā)模塊時,Linux內(nèi)核就是你所需一切的源頭。然而,它相當大,你可能在查找你所要的內(nèi)容時會有困難。幸運的是,在龐大的代碼庫面前,有許多工具使這個過程變得簡單。首先,是Cscope —— 在終端中運行的一個比較經(jīng)典的工具。你所要做的,就是在內(nèi)核源代碼的頂級目錄中運行make cscope && cscope。Cscope和Vim以及Emacs整合得很好,因此你可以在你最喜愛的編輯器中使用它。
            如果基于終端的工具不是你的最愛,那么就訪問http://lxr.free-electrons.com吧。它是一個基于web的內(nèi)核導航工具,即使它的功能沒有Cscope來得多(例如,你不能方便地找到函數(shù)的用法),但它仍然提供了足夠多的快速查詢功能。
            現(xiàn)在是時候來編譯模塊了。你需要你正在運行的內(nèi)核版本頭文件(linux-headers,或者等同的軟件包)和build-essential(或者類似的包)。接下來,該創(chuàng)建一個標準的Makefile模板:
            obj-m += reverse.o
            all:
            make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
            clean:
            make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
            現(xiàn)在,調(diào)用make來構(gòu)建你的第一個模塊。如果你輸入的都正確,在當前目錄內(nèi)會找到reverse.ko文件。使用sudo insmod reverse.ko插入內(nèi)核模塊,然后運行如下命令:
            $ dmesg | tail -1
            [ 5905.042081] reverse device has been registered, buffer size is 8192 bytes
            恭喜了!然而,目前這一行還只是假象而已 —— 還沒有設(shè)備節(jié)點呢。讓我們來搞定它。
            混雜設(shè)備
            在Linux中,有一種特殊的字符設(shè)備類型,叫做“混雜設(shè)備”(或者簡稱為“misc”)。它是專為單一接入點的小型設(shè)備驅(qū)動而設(shè)計的,而這正是我們所需要的。所有混雜設(shè)備共享同一個主設(shè)備號(10),因此一個驅(qū)動(drivers/char/misc.c)就可以查看它們所有設(shè)備了,而這些設(shè)備用次設(shè)備號來區(qū)分。從其他意義來說,它們只是普通字符設(shè)備。
            要為該設(shè)備注冊一個次設(shè)備號(以及一個接入點),你需要聲明struct misc_device,填上所有字段(注意語法),然后使用指向該結(jié)構(gòu)的指針作為參數(shù)來調(diào)用misc_register()。為此,你也需要包含linux/miscdevice.h頭文件:
            static struct miscdevice reverse_misc_device = {
            .minor = MISC_DYNAMIC_MINOR,
            .name = "reverse",
            .fops = &reverse_fops
            };
            static int __init reverse_init()
            {
            ...
            misc_register(&reverse_misc_device);
            printk(KERN_INFO ...
            }
            這兒,我們?yōu)槊麨?#8220;reverse”的設(shè)備請求一個第一個可用的(動態(tài)的)次設(shè)備號;省略號表明我們之前已經(jīng)見過的省略的代碼。別忘了在模塊卸下后注銷掉該設(shè)備。
            static void __exit reverse_exit(void)
            {
            misc_deregister(&reverse_misc_device);
            ...
            }
            ‘fops’字段存儲了一個指針,指向一個file_operations結(jié)構(gòu)(在Linux/fs.h中聲明),而這正是我們模塊的接入點。reverse_fops定義如下:
            static struct file_operations reverse_fops = {
            .owner = THIS_MODULE,
            .open = reverse_open,
            ...
            .llseek = noop_llseek
            };
            另外,reverse_fops包含了一系列回調(diào)函數(shù)(也稱之為方法),當用戶空間代碼打開一個設(shè)備,讀寫或者關(guān)閉文件描述符時,就會執(zhí)行。如果你要忽略這些回調(diào),可以指定一個明確的回調(diào)函數(shù)來替代。這就是為什么我們將llseek設(shè)置為noop_llseek(),(顧名思義)它什么都不干。這個默認實現(xiàn)改變了一個文件指針,而且我們現(xiàn)在并不需要我們的設(shè)備可以尋址(這是今天留給你們的家庭作業(yè))。
            關(guān)閉和打開
            讓我們來實現(xiàn)該方法。我們將給每個打開的文件描述符分配一個新的緩沖區(qū),并在它關(guān)閉時釋放。這實際上并不安全:如果一個用戶空間應用程序泄漏了描述符(也許是故意的),它就會霸占RAM,并導致系統(tǒng)不可用。在現(xiàn)實世界中,你總得考慮到這些可能性。但在本教程中,這種方法不要緊。
            我們需要一個結(jié)構(gòu)函數(shù)來描述緩沖區(qū)。內(nèi)核提供了許多常規(guī)的數(shù)據(jù)結(jié)構(gòu):鏈接列表(雙聯(lián)的),哈希表,樹等等之類。不過,緩沖區(qū)常常從頭設(shè)計。我們將調(diào)用我們的“struct buffer”:
            struct buffer {
            char *data, *end, *read_ptr;
            unsigned long size;
            };
            data是該緩沖區(qū)存儲的一個指向字符串的指針,而end指向字符串結(jié)尾后的第一個字節(jié)。read_ptr是read()開始讀取數(shù)據(jù)的地方。緩沖區(qū)的size是為了保證完整性而存儲的 —— 目前,我們還沒有使用該區(qū)域。你不能假設(shè)使用你結(jié)構(gòu)體的用戶會正確地初始化所有這些東西,所以最好在函數(shù)中封裝緩沖區(qū)的分配和收回。它們通常命名為buffer_alloc()和buffer_free()。
            static struct buffer buffer_alloc(unsigned long size) { struct buffer *buf; buf = kzalloc(sizeof(buf), GFP_KERNEL); if (unlikely(!buf)) goto out; … out: return buf; }
            內(nèi)核內(nèi)存使用kmalloc()來分配,并使用kfree()來釋放;kzalloc()的風格是將內(nèi)存設(shè)置為全零。不同于標準的malloc(),它的內(nèi)核對應部分收到的標志指定了第二個參數(shù)中請求的內(nèi)存類型。這里,GFP_KERNEL是說我們需要一個普通的內(nèi)核內(nèi)存(不是在DMA或高內(nèi)存區(qū)中)以及如果需要的話函數(shù)可以睡眠(重新調(diào)度進程)。sizeof(*buf)是一種常見的方式,它用來獲取可通過指針訪問的結(jié)構(gòu)體的大小。
            你應該隨時檢查kmalloc()的返回值:訪問NULL指針將導致內(nèi)核異常。同時也需要注意unlikely()宏的使用。它(及其相對宏likely())被廣泛用于內(nèi)核中,用于表明條件幾乎總是真的(或假的)。它不會影響到控制流程,但是能幫助現(xiàn)代處理器通過分支預測技術(shù)來提升性能。
            最后,注意goto語句。它們常常為認為是邪惡的,但是,Linux內(nèi)核(以及一些其它系統(tǒng)軟件)采用它們來實施集中式的函數(shù)退出。這樣的結(jié)果是減少嵌套深度,使代碼更具可讀性,而且非常像更高級語言中的try-catch區(qū)塊。
            有了buffer_alloc()和buffer_free(),open和close方法就變得很簡單了。
            static int reverse_open(struct inode *inode, struct file *file)
            {
            int err = 0;
            file->private_data = buffer_alloc(buffer_size);
            ...
            return err;
            }
            struct file是一個標準的內(nèi)核數(shù)據(jù)結(jié)構(gòu),用以存儲打開的文件的信息,如當前文件位置(file->f_pos)、標志(file->f_flags),或者打開模式(file->f_mode)等。另外一個字段file->privatedata用于關(guān)聯(lián)文件到一些專有數(shù)據(jù),它的類型是void *,而且它在文件擁有者以外,對內(nèi)核不透明。我們將一個緩沖區(qū)存儲在那里。
            如果緩沖區(qū)分配失敗,我們通過返回否定值(-ENOMEM)來為調(diào)用的用戶空間代碼標明。一個C庫中調(diào)用的open(2)系統(tǒng)調(diào)用(如glibc)將會檢測這個并適當?shù)卦O(shè)置errno 。
            學習如何讀和寫
            “read”和“write”方法是真正完成工作的地方。當數(shù)據(jù)寫入到緩沖區(qū)時,我們放棄之前的內(nèi)容和反向地存儲該字段,不需要任何臨時存儲。read方法僅僅是從內(nèi)核緩沖區(qū)復制數(shù)據(jù)到用戶空間。但是如果緩沖區(qū)還沒有數(shù)據(jù),revers_eread()會做什么呢?在用戶空間中,read()調(diào)用會在有可用數(shù)據(jù)前阻塞它。在內(nèi)核中,你就必須等待。幸運的是,有一項機制用于處理這種情況,就是‘wait queues’。
            想法很簡單。如果當前進程需要等待某個事件,它的描述符(struct task_struct存儲‘current’信息)被放進非可運行(睡眠中)狀態(tài),并添加到一個隊列中。然后schedule()就被調(diào)用來選擇另一個進程運行。生成事件的代碼通過使用隊列將等待進程放回TASK_RUNNING狀態(tài)來喚醒它們。調(diào)度程序?qū)⒃谝院笤谀硞€地方選擇它們之一。Linux有多種非可運行狀態(tài),最值得注意的是TASK_INTERRUPTIBLE(一個可以通過信號中斷的睡眠)和TASK_KILLABLE(一個可被殺死的睡眠中的進程)。所有這些都應該正確處理,并等待隊列為你做這些事。
            一個用以存儲讀取等待隊列頭的天然場所就是結(jié)構(gòu)緩沖區(qū),所以從為它添加wait_queue_headt read\queue字段開始。你也應該包含linux/sched.h頭文件。可以使用DECLARE_WAITQUEUE()宏來靜態(tài)聲明一個等待隊列。在我們的情況下,需要動態(tài)初始化,因此添加下面這行到buffer_alloc():
            init_waitqueue_head(&buf->read_queue);
            我們等待可用數(shù)據(jù);或者等待read_ptr != end條件成立。我們也想要讓等待操作可以被中斷(如,通過Ctrl+C)。因此,“read”方法應該像這樣開始:
          static ssize_t reverse_read(struct file *file, char __user * out,
          size_t size, loff_t * off)
          {
          struct buffer *buf = file->private_data;
          ssize_t result;
          while (buf->read_ptr == buf->end) {
          if (file->f_flags & O_NONBLOCK) {
          result = -EAGAIN;
          goto out;
          }
          if (wait_event_interruptible
          (buf->read_queue, buf->read_ptr != buf->end)) {
          result = -ERESTARTSYS;
          goto out;
          }
          }
          ...
            我們讓它循環(huán),直到有可用數(shù)據(jù),如果沒有則使用wait_event_interruptible()(它是一個宏,不是函數(shù),這就是為什么要通過值的方式給隊列傳遞)來等待。好吧,如果wait_event_interruptible()被中斷,它返回一個非0值,這個值代表-ERESTARTSYS。這段代碼意味著系統(tǒng)調(diào)用應該重新啟動。file->f_flags檢查以非阻塞模式打開的文件數(shù):如果沒有數(shù)據(jù),返回-EAGAIN。
            我們不能使用if()來替代while(),因為可能有許多進程正等待數(shù)據(jù)。當write方法喚醒它們時,調(diào)度程序以不可預知的方式選擇一個來運行,因此,在這段代碼有機會執(zhí)行的時候,緩沖區(qū)可能再次空出。現(xiàn)在,我們需要將數(shù)據(jù)從buf->data 復制到用戶空間。copy_to_user()內(nèi)核函數(shù)就干了此事:
            size = min(size, (size_t) (buf->end - buf->read_ptr));
            if (copy_to_user(out, buf->read_ptr, size)) {
            result = -EFAULT;
            goto out;
            }
            如果用戶空間指針錯誤,那么調(diào)用可能會失敗;如果發(fā)生了此事,我們就返回-EFAULT。記住,不要相信任何來自內(nèi)核外的事物!
            buf->read_ptr += size;
            result = size;
            out:
            return result;
            }
            為了使數(shù)據(jù)在任意塊可讀,需要進行簡單運算。該方法返回讀入的字節(jié)數(shù),或者一個錯誤代碼。
            寫方法更簡短。首先,我們檢查緩沖區(qū)是否有足夠的空間,然后我們使用copy_from_userspace()函數(shù)來獲取數(shù)據(jù)。再然后read_ptr和結(jié)束指針會被重置,并且反轉(zhuǎn)存儲緩沖區(qū)內(nèi)容:
            buf->end = buf->data + size;
            buf->read_ptr = buf->data;
            if (buf->end > buf->data)
            reverse_phrase(buf->data, buf->end - 1);
            這里, reverse_phrase()干了所有吃力的工作。它依賴于reverse_word()函數(shù),該函數(shù)相當簡短并且標記為內(nèi)聯(lián)。這是另外一個常見的優(yōu)化;但是,你不能過度使用。因為過多的內(nèi)聯(lián)會導致內(nèi)核映像徒然增大。
            最后,我們需要喚醒read_queue中等待數(shù)據(jù)的進程,就跟先前講過的那樣。wake_up_interruptible()就是用來干此事的:
            wake_up_interruptible(&buf->read_queue);
            耶!你現(xiàn)在已經(jīng)有了一個內(nèi)核模塊,它至少已經(jīng)編譯成功了。現(xiàn)在,是時候來測試了。
            調(diào)試內(nèi)核代碼
            或許,內(nèi)核中最常見的調(diào)試方法就是打印。如果你愿意,你可以使用普通的printk() (假定使用KERN_DEBUG日志等級)。然而,那兒還有更好的辦法。如果你正在寫一個設(shè)備驅(qū)動,這個設(shè)備驅(qū)動有它自己的“struct device”,可以使用pr_debug()或者dev_dbg():它們支持動態(tài)調(diào)試(dyndbg)特性,并可以根據(jù)需要啟用或者禁用(請查閱Documentation/dynamic-debug-howto.txt)。對于單純的開發(fā)消息,使用pr_devel(),除非設(shè)置了DEBUG,否則什么都不會做。要為我們的模塊啟用DEBUG,請?zhí)砑右韵滦械組akefile中:
            CFLAGS_reverse.o := -DDEBUG
            完了之后,使用dmesg來查看pr_debug()或pr_devel()生成的調(diào)試信息。 或者,你可以直接發(fā)送調(diào)試信息到控制臺。要想這么干,你可以設(shè)置console_loglevel內(nèi)核變量為8或者更大的值(echo 8 /proc/sys/kernel/printk),或者在高日志等級,如KERN_ERR,來臨時打印要查詢的調(diào)試信息。很自然,在發(fā)布代碼前,你應該移除這樣的調(diào)試聲明。
            注意內(nèi)核消息出現(xiàn)在控制臺,不要在Xterm這樣的終端模擬器窗口中去查看;這也是在內(nèi)核開發(fā)時,建議你不在X環(huán)境下進行的原因。
            驚喜,驚喜!
            編譯模塊,然后加載進內(nèi)核:
            $ make
            $ sudo insmod reverse.ko buffer_size=2048
            $ lsmod
            reverse 2419 0
            $ ls -l /dev/reverse
            crw-rw-rw- 1 root root 10, 58 Feb 22 15:53 /dev/reverse
            一切似乎就位。現(xiàn)在,要測試模塊是否正常工作,我們將寫一段小程序來翻轉(zhuǎn)它的第一個命令行參數(shù)。main()(再三檢查錯誤)可能看上去像這樣:
            int fd = open("/dev/reverse", O_RDWR);
            write(fd, argv[1], strlen(argv[1]));
            read(fd, argv[1], strlen(argv[1]));
            printf("Read: %s\n", argv[1]);
            像這樣運行:
            $ ./test 'A quick brown fox jumped over the lazy dog'
            Read: dog lazy the over jumped fox brown quick A
            它工作正常!玩得更逗一點:試試傳遞單個單詞或者單個字母的短語,空的字符串或者是非英語字符串(如果你有這樣的鍵盤布局設(shè)置),以及其它任何東西。
            現(xiàn)在,讓我們讓事情變得更好玩一點。我們將創(chuàng)建兩個進程,它們共享一個文件描述符(及其內(nèi)核緩沖區(qū))。其中一個會持續(xù)寫入字符串到設(shè)備,而另一個將讀取這些字符串。在下例中,我們使用了fork(2)系統(tǒng)調(diào)用,而pthreads也很好用。我也省略打開和關(guān)閉設(shè)備的代碼,并在此檢查代碼錯誤(又來了):
            char *phrase = "A quick brown fox jumped over the lazy dog";
            if (fork())
            /* Parent is the writer */
            while (1)
            write(fd, phrase, len);
            else
            /* child is the reader */
            while (1) {
            read(fd, buf, len);
            printf("Read: %s\n", buf);
            }
            你希望這個程序會輸出什么呢?下面就是在我的筆記本上得到的東西:
            Read: dog lazy the over jumped fox brown quick A
            Read: A kcicq brown fox jumped over the lazy dog
            Read: A kciuq nworb xor jumped fox brown quick A
            Read: A kciuq nworb xor jumped fox brown quick A
            ...
            這里發(fā)生了什么呢?就像舉行了一場比賽。我們認為read和write是原子操作,或者從頭到尾一次執(zhí)行一個指令。然而,內(nèi)核確實無序并發(fā)的,隨便就重新調(diào)度了reverse_phrase()函數(shù)內(nèi)部某個地方運行著的寫入操作的內(nèi)核部分。如果在寫入操作結(jié)束前就調(diào)度了read()操作呢?就會產(chǎn)生數(shù)據(jù)不完整的狀態(tài)。這樣的bug非常難以找到。但是,怎樣來處理這個問題呢?
            基本上,我們需要確保在寫方法返回前沒有read方法能被執(zhí)行。如果你曾經(jīng)編寫過一個多線程的應用程序,你可能見過同步原語(鎖),如互斥鎖或者信號。Linux也有這些,但有些細微的差別。內(nèi)核代碼可以運行進程上下文(用戶空間代碼的“代表”工作,就像我們使用的方法)和終端上下文(例如,一個IRQ處理線程)。如果你已經(jīng)在進程上下文中和并且你已經(jīng)得到了所需的鎖,你只需要簡單地睡眠和重試直到成功為止。在中斷上下文時你不能處于休眠狀態(tài),因此代碼會在一個循環(huán)中運行直到鎖可用。關(guān)聯(lián)原語被稱為自旋鎖,但在我們的環(huán)境中,一個簡單的互斥鎖 —— 在特定時間內(nèi)只有唯一一個進程能“占有”的對象 —— 就足夠了。處于性能方面的考慮,現(xiàn)實的代碼可能也會使用讀-寫信號。
            鎖總是保護某些數(shù)據(jù)(在我們的環(huán)境中,是一個“struct buffer”實例),而且也常常會把它們嵌入到它們所保護的結(jié)構(gòu)體中。因此,我們添加一個互斥鎖(‘struct mutex lock’)到“struct buffer”中。我們也必須用mutex_init()來初始化互斥鎖;buffer_alloc是用來處理這件事的好地方。使用互斥鎖的代碼也必須包含linux/mutex.h。
            互斥鎖很像交通信號燈 —— 要是司機不看它和不聽它的,它就沒什么用。因此,在對緩沖區(qū)做操作并在操作完成時釋放它之前,我們需要更新reverse_read()和reverse_write()來獲取互斥鎖。讓我們來看看read方法 —— write的工作原理相同:
            static ssize_t reverse_read(struct file *file, char __user * out,
            size_t size, loff_t * off)
            {
            struct buffer *buf = file->private_data;
            ssize_t result;
            if (mutex_lock_interruptible(&buf->lock)) {
            result = -ERESTARTSYS;
            goto out;
            }
            我們在函數(shù)一開始就獲取鎖。mutex_lock_interruptible()要么得到互斥鎖然后返回,要么讓進程睡眠,直到有可用的互斥鎖。就像前面一樣,_interruptible后綴意味著睡眠可以由信號來中斷。
            while (buf->read_ptr == buf->end) {
            mutex_unlock(&buf->lock);
            /* ... wait_event_interruptible() here ... */
            if (mutex_lock_interruptible(&buf->lock)) {
            result = -ERESTARTSYS;
            goto out;
            }
            }
            下面是我們的“等待數(shù)據(jù)”循環(huán)。當獲取互斥鎖時,或者發(fā)生稱之為“死鎖”的情境時,不應該讓進程睡眠。因此,如果沒有數(shù)據(jù),我們釋放互斥鎖并調(diào)用wait_event_interruptible()。當它返回時,我們重新獲取互斥鎖并像往常一樣繼續(xù):
            if (copy_to_user(out, buf->read_ptr, size)) {
            result = -EFAULT;
            goto out_unlock;
            }
            ...
            out_unlock:
            mutex_unlock(&buf->lock);
            out:
            return result;
            最后,當函數(shù)結(jié)束,或者在互斥鎖被獲取過程中發(fā)生錯誤時,互斥鎖被解鎖。重新編譯模塊(別忘了重新加載),然后再次進行測試。現(xiàn)在你應該沒發(fā)現(xiàn)毀壞的數(shù)據(jù)了。
            接下來是什么?
            現(xiàn)在你已經(jīng)嘗試了一次內(nèi)核黑客。我們剛剛為你揭開了這個話題的外衣,里面還有更多東西供你探索。我們的第一個模塊有意識地寫得簡單一點,在從中學到的概念在更復雜的環(huán)境中也一樣。并發(fā)、方法表、注冊回調(diào)函數(shù)、使進程睡眠以及喚醒進程,這些都是內(nèi)核黑客們耳熟能詳?shù)臇|西,而現(xiàn)在你已經(jīng)看過了它們的運作。或許某天,你的內(nèi)核代碼也將被加入到主線Linux源代碼樹中 —— 如果真這樣,請聯(lián)系我們!

          posted @ 2014-10-08 09:23 順其自然EVO 閱讀(204) | 評論 (0)編輯 收藏

          優(yōu)化臨時表使用,SQL語句性能提升100倍

          【問題現(xiàn)象】
            線上mysql數(shù)據(jù)庫爆出一個慢查詢,DBA觀察發(fā)現(xiàn),查詢時服務器IO飆升,IO占用率達到100%, 執(zhí)行時間長達7s左右。
            SQL語句如下:
            SELECT DISTINCT g.*, cp.name AS cp_name, c.name AS category_name, t.name AS type_name FROMgm_game g LEFT JOIN gm_cp cp ON cp.id = g.cp_id AND cp.deleted = 0 LEFT JOIN gm_category c ON c.id = g.category_id AND c.deleted = 0 LEFT JOIN gm_type t ON t.id = g.type_id AND t.deleted = 0 WHERE g.deleted = 0 ORDER BY g.modify_time DESC LIMIT 20 ;
            【問題分析】
            使用explain查看執(zhí)行計劃,結(jié)果如下:
            這條sql語句的問題其實還是比較明顯的:
            查詢了大量數(shù)據(jù)(包括數(shù)據(jù)條數(shù)、以及g.* ),然后使用臨時表order by,但最終又只返回了20條數(shù)據(jù)。
            DBA觀察到的IO高,是因為sql語句生成了一個巨大的臨時表,內(nèi)存放不下,于是全部拷貝到磁盤,導致IO飆升。
            【優(yōu)化方案】
            優(yōu)化的總體思路是拆分sql,將排序操作和查詢所有信息的操作分開。
            第一條語句:查詢符合條件的數(shù)據(jù),只需要查詢g.id即可
            SELECT DISTINCT g.id FROM gm_game g LEFT JOIN gm_cp cp ON cp.id = g.cp_id AND cp.deleted = 0 LEFT JOIN gm_category c ON c.id = g.category_id AND c.deleted = 0 LEFT JOIN gm_type t ON t.id = g.type_id AND t.deleted = 0 WHERE g.deleted = 0 ORDER BY g.modify_time DESC LIMIT 20 ;
            第二條語句:查詢符合條件的詳細數(shù)據(jù),將第一條sql的結(jié)果使用in操作拼接到第二條的sql
            SELECT DISTINCT g.*, cp.name AS cp_name,c.name AS category_name,t.name AS type_name FROMgm_game g LEFT JOIN gm_cp cp ON cp.id = g.cp_id AND cp.deleted = 0 LEFT JOIN gm_category c ON c.id = g.category_id AND c.deleted = 0 LEFT JOIN gm_type t ON t.id = g.type_id AND t.deleted = 0 WHERE g.deleted = 0 and g.id in(…………………) ORDER BY g.modify_time DESC ;
            【實測效果】
            在SATA機器上測試,優(yōu)化前大約需要50s,優(yōu)化后第一條0.3s,第二條0.1s,優(yōu)化后執(zhí)行速度是原來的100倍以上,IO從100%降到不到1%
            在SSD機器上測試,優(yōu)化前大約需要7s,優(yōu)化后第一條0.3s,第二條0.1s,優(yōu)化后執(zhí)行速度是原來的10倍以上,IO從100%降到不到1%
            可以看出,優(yōu)化前磁盤io是性能瓶頸,SSD的速度要比SATA明顯要快,優(yōu)化后磁盤不再是瓶頸,SSD和SATA性能沒有差別。
            【理論分析】
            MySQL在執(zhí)行SQL查詢時可能會用到臨時表,一般情況下,用到臨時表就意味著性能較低。
            臨時表存儲
            MySQL臨時表分為“內(nèi)存臨時表”和“磁盤臨時表”,其中內(nèi)存臨時表使用MySQL的MEMORY存儲引擎,磁盤臨時表使用MySQL的MyISAM存儲引擎;
            一般情況下,MySQL會先創(chuàng)建內(nèi)存臨時表,但內(nèi)存臨時表超過配置指定的值后,MySQL會將內(nèi)存臨時表導出到磁盤臨時表;
            Linux平臺上缺省是/tmp目錄,/tmp目錄小的系統(tǒng)要注意啦。
            使用臨時表的場景
            1)ORDER BY子句和GROUP BY子句不同, 例如:ORDERY BY price GROUP BY name;
            2)在JOIN查詢中,ORDER BY或者GROUP BY使用了不是第一個表的列 例如:SELECT * from TableA, TableB ORDER BY TableA.price GROUP by TableB.name
            3)ORDER BY中使用了DISTINCT關(guān)鍵字 ORDERY BY DISTINCT(price)
            4)SELECT語句中指定了SQL_SMALL_RESULT關(guān)鍵字 SQL_SMALL_RESULT的意思就是告訴MySQL,結(jié)果會很小,請直接使用內(nèi)存臨時表,不需要使用索引排序 SQL_SMALL_RESULT必須和GROUP BY、DISTINCT或DISTINCTROW一起使用 一般情況下,我們沒有必要使用這個選項,讓MySQL服務器選擇即可。
          直接使用磁盤臨時表的場景
            1)表包含TEXT或者BLOB列;
            2)GROUP BY 或者 DISTINCT 子句中包含長度大于512字節(jié)的列;
            3)使用UNION或者UNION ALL時,SELECT子句中包含大于512字節(jié)的列;
            臨時表相關(guān)配置
            tmp_table_size:指定系統(tǒng)創(chuàng)建的內(nèi)存臨時表最大大小;
            http://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_tmp_table_size
            max_heap_table_size: 指定用戶創(chuàng)建的內(nèi)存表的最大大小;
            http://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_max_heap_table_size
            注意:最終的系統(tǒng)創(chuàng)建的內(nèi)存臨時表大小是取上述兩個配置值的最小值。
            表的設(shè)計原則
            使用臨時表一般都意味著性能比較低,特別是使用磁盤臨時表,性能更慢,因此我們在實際應用中應該盡量避免臨時表的使用。 常見的避免臨時表的方法有:
            1)創(chuàng)建索引:在ORDER BY或者GROUP BY的列上創(chuàng)建索引;
            2)分拆很長的列:一般情況下,TEXT、BLOB,大于512字節(jié)的字符串,基本上都是為了顯示信息,而不會用于查詢條件, 因此表設(shè)計的時候,應該將這些列獨立到另外一張表。
            SQL優(yōu)化
            如果表的設(shè)計已經(jīng)確定,修改比較困難,那么也可以通過優(yōu)化SQL語句來減少臨時表的大小,以提升SQL執(zhí)行效率。
            常見的優(yōu)化SQL語句方法如下:
            1)拆分SQL語句
            臨時表主要是用于排序和分組,很多業(yè)務都是要求排序后再取出詳細的分頁數(shù)據(jù),這種情況下可以將排序和取出詳細數(shù)據(jù)拆分成不同的SQL,以降低排序或分組時臨時表的大小,提升排序和分組的效率,我們的案例就是采用這種方法。
            2)優(yōu)化業(yè)務,去掉排序分組等操作
            有時候業(yè)務其實并不需要排序或分組,僅僅是為了好看或者閱讀方便而進行了排序,例如數(shù)據(jù)導出、數(shù)據(jù)查詢等操作,這種情況下去掉排序和分組對業(yè)務也沒有多大影響。
            如何判斷使用了臨時表?
            使用explain查看執(zhí)行計劃,Extra列看到Using temporary就意味著使用了臨時表。

          posted @ 2014-10-08 09:17 順其自然EVO 閱讀(226) | 評論 (0)編輯 收藏

          淺談關(guān)于java程序員面試的一些事項

          本篇博文針對的是應屆畢業(yè)生以及工作兩三年左右的java程序員。
            為什么要跳槽?
            這是一個很廣義的問題,每個人心中都有一份答案。
            例如:
            公司的待遇不好,
            薪資漲幅不符合預期要求,
            厭倦了出差的荒無天日的繁重工作,
            公司的妹子太少,
            領(lǐng)導太傲嬌,
            同事之間關(guān)系太逼格,
            某某同學跳槽到某某公司之后漲到了多少多少錢,
            某某同學的朋友的同事的三姑媽家的大兒子的好基友在某某高就,
            等等辭職理由。
            咱們就不多說了,還是談談怎么應付面試吧。
            以下內(nèi)容是我在面試中總結(jié)的一些經(jīng)驗,希望這些可以給各位帶來幫助和啟迪。
            簡單的說一下筆試,筆試這個環(huán)節(jié)是很容易通過的,無非就是幾張試卷,一共也就十幾道題。一般由5至10個選擇題+2至5個論述題+1至2個編程題 組成。
            接過筆試題之后,第一步要平靜心態(tài),第二步要瀏覽所有題目,第三步自然就是答題了~
            答題的時候,要先把自己會的快速的答上來,選擇題自然不多說了,論述題根據(jù)自己的理解大致說明一下,多少會給你自己加分的。
            編程題其實也不難,出現(xiàn)幾率最大的是寫一個關(guān)于某某設(shè)計模式的例子,而設(shè)計模式的編碼例子,出現(xiàn)最多的是單例模式、工廠模式和代理模式。
            有時候也會有一些算法的編碼,一般是排序算法的編碼實現(xiàn)。
            還有的筆試題,會有一些程序題,就是看程序,然后自己寫出運行結(jié)果,這樣的問題考察的是對java基礎(chǔ)知識的掌握,所以,有堅固的基礎(chǔ)是很重要滴!
            OK,筆試結(jié)束之后,下一個環(huán)節(jié)就是面試了,java程序員的一些面試問題主要有哪些呢?
            我個人認為主要有三方面:
            1. 關(guān)于java有關(guān)的技術(shù)問題
            2. 關(guān)于項目經(jīng)驗的問題
            3. 關(guān)于個人對團隊的看法以及個人的職業(yè)規(guī)劃
            咱們就一條一條來看,大家看完之后找相關(guān)資料然后一條一條的應對
            一、技術(shù)問題
            Struts1原理和Struts2原理以及區(qū)別和聯(lián)系,在什么項目中用過,有什么體會。
            spring的原理  aop和ioc機制,如何使用,在哪個項目用到過?有什么體會。
            簡要說明一下StrutsMVC和SpringMVC。
            servlet的原理,生命周期。
            socket 原理以及使用方式
            Linux常用命令,shell編程介紹
            java常用算法
            多線程、線程池、線程鎖等等
            二叉樹、java數(shù)據(jù)結(jié)構(gòu)
            數(shù)據(jù)庫mysql、Oracle的優(yōu)缺點以及使用方法和sql語句,問的多的是如果模擬分頁查詢和多表查詢
            Java垃圾回收機制
            敏捷開發(fā)的簡要說明,是否了解
            OOA/OOD/OOP 的含義
            java加密與解密
            java網(wǎng)絡(luò)通信、http協(xié)議要素
            是否熟悉設(shè)計模式?簡要說一下自己所了解或者使用過的開發(fā)模式有哪些,在哪些場景中使用。

          posted @ 2014-10-08 09:16 順其自然EVO 閱讀(197) | 評論 (0)編輯 收藏

          僅列出標題
          共394頁: First 上一頁 36 37 38 39 40 41 42 43 44 下一頁 Last 
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導航

          統(tǒng)計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 双城市| 松阳县| 武穴市| 青铜峡市| 文昌市| 南部县| 宜兴市| 驻马店市| 瑞丽市| 辽宁省| 宜昌市| 定襄县| 临江市| 永修县| 从江县| 荃湾区| 黎城县| 嘉义县| 延边| 西吉县| 温宿县| 岑溪市| 南昌市| 图们市| 高碑店市| 定兴县| 苏尼特左旗| 郧西县| 阿拉善右旗| 大化| 景德镇市| 景东| 崇义县| 察雅县| 嘉善县| 平昌县| 华阴市| 方山县| 永修县| 安义县| 乐清市|