2016年7月28日 星期四

如何撰寫Linux Kernel中的Timer機制

想要在kernel中寫Timer的機制,首先要些對一些專有名詞有一些初步的了解,分別是HZ, Tick, Jiffies.

HZ:Linux kernel 每隔固定週期會發出timer interrupt,HZ就是用來定義每一秒有幾次timer interrupts。例如:500 HZ 就是CPU每一秒會發出500個interrupt的意思。這個值是可以在Kernel的menuconfig當中來做設定.

Tick:Tick是HZ的倒數,意即timer interrupt每發生一次中斷所花的時間。如HZ為500時,tick為2毫秒 (millisecond)。

Jiffies:Jiffies為Linux核心變數(32位元變數,unsigned long),它被用來紀錄系統自開幾以來,已經過多少的tick。每發生一次timer interrupt,Jiffies變數會被加一。


接下來開始介紹Timer API的做法,我們先來看以下定義所代表的意義:

首先先宣告一個Timer的Struct:
struct timer_list timer;

這個Struct當中有幾個比較重要的值,我們要去做設定分別是:
timer.function    <===== timer 啟動後所設定的expires時間到後,會去做此參數所指定的function
timer.data             <===== timer 傳入function的參數值
timer.expires      <===== timer 延遲的時間,也就是延遲多久後去做 timer.function 所指定的函式

而下面這部分則是想要timer delay多久的寫法
Jiffies + HZ;   <=====  1秒之後 

Jiffies  + HZ/2;       <=====   半秒之後

Jiffies  + 20*HZ;     <=====  20秒之後


接下來就是實做一個Timer的Code.
/**************** Kernel_Timer *************/
struct timer_list danny_timer;

static int danny_do(void)
{
    danny_timer.expires = jiffies + HZ;
    add_timer(&danny_timer);
}

static void danny_timer_init(void)
{
    /* Timer 初始化 */
   init_timer(&danny_timer);

   /* define timer 要執行之函式 */
  danny_timer.function = danny_do;

  /* define timer 傳入函式之 Data */
  danny_timer.data = ((unsigned long) 0);

  /* define timer Delay 1秒的時間 */
  danny_timer.expires = jiffies + HZ;

  /* 啟動 Timer*/
  add_timer(&danny_timer);
}
/*********************************************/

結論:

所以當呼叫 danny_timer_init();之後,delay 1秒之後,會去做danny_do();

而danny_do()裡面也加了add_timer()的函式,所以會不斷的迴圈,每隔1秒鐘都會去做danny_do()的函式.



 

2016年5月19日 星期四

Linux_Driver入門-寫一個HelloWorld module

因為是driver的初學者,所以先練習寫一支HelloWorld的module,這個module的行為是當insmod這個module的時候,會印出"Hello World!"的字眼,而在rmmod的時候會印出"Goodbye, hello world"

首先描述一下整個編譯環境,以及要掛這個module的平台:
首先說明要掛這個module的平台
- arm64平台(DB410C),系統是linux

編譯平台
- PC,系統是ubuntu,DB410C的kernel與toolchain都準備好了

這邊先說明一下,當你要作交叉編譯時,一定要有Target Board(DB410C)的toolchain,以這邊的例子為例,我在PC上面編譯,我利用toolchain把程式編譯成Target Board可讀的檔案

另外要注意的是,要編譯driver,必須要拿到與Target Board相同的Kernel Source Code(版本要相同),Source code必須整包放在要進行編譯的PC端,編譯時必須參考到裡面的一些header檔

接著就來看我們的HelloWorld.c

///////////////////////////////////////////  HelloWorld.c  ///////////////////////////////////////////
#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("Dual BSD/GPL");

static int hello_init(void)
{
    printk("Hello world!\n");
    return 0;
}

static void hello_exit(void)
{
    printk("Goodbye, hello world\n");
}

module_init(hello_init);
module_exit(hello_exit);
///////////////////////////////////////////////////////////////////////////////////////////////////////////

這程式很單純,在module被insert的時候就會執行hello_init的function
在module被remove時會執行hello_exit的function



接著是Makefile的部分

obj-m += hello.o

KERNEL=/home/danny/DB410C/kernel/kernel

all:
make -C $(KERNEL) M=$(shell pwd) modules ARCH=arm64

clean:
make -C $(KERNEL) M=$(shell pwd) clean


編譯之前這邊要注意兩個部分,首先是必須給系統CROSS_COMPILE環境變數,必須給他toolchain的完整路徑,請使用export指令:
export CROSS_COMPILE=/home/Danny/DB410C/toolchain/gcc-linaro-4.9-2014.11-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
(請依照你的toolchain給定CROSS_COMPILE的值)

另外一部分是Makefile中KERNEL這個參數,必須要指定到PC上,Target Board(DB410C)的Kernel Source Code,這樣編譯時才不會出錯!


接著只需下make,hello.ko就會被產生出來,接著把hello.ko複製到Target Board上,接著執行
sudo insmod hello.ko

sudo rmmod hello.ko

輸入dmesg就可以看到driver印出的訊息囉!






Linux中設定root的密碼

當要使用su指令切換成root的時候 ,會請求你輸入root的密碼,但是都沒有設定過root密碼要怎麼處裡呢?

請直接輸入下面的指令:
sudo passwd root

他會要求你設定密碼,設定完成後就可以順利使用su切換root了!

2016年5月13日 星期五

Linux中設定環境變數的方法

當你需要作cross-compiler時,須要先安裝Toolchain在你的工作電腦上(Ubuntu, Fedora, Debiab etc.), 與其說安裝其實就是把Toolchain包解壓縮到某個路徑。接著要作交叉編譯時,需要指定編譯工具的路徑,此時為了不用每次都輸入一整串的路徑來指定編譯工具,就會去設定PATH。例如:我的編譯器arm-linux-gnueabihf-gcc路徑是在 "home/danny/workspace/DB410C/Toolchain/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/bin",此時有三個方法來設定環境變數:

1.用export指令
export PATH=$PATH:/home/danny/workspace/DB410C/Toolchain/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/bin
輸入之後可以使用export指令來查看環境變數是否有輸入進去。

*此修改重開機後,就必須再作一次

2.修改profile
profile的路徑是在 "/etc/profile"
直接修改profile這個檔案在裡面加入
export PATH=$PATH:/home/danny/workspace/DB410C/Toolchain/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/bin

*此修改必須在重開機之後,才會有作用


3.修改.bashrc
.bashrc的路徑是在"/home/danny/.bashrc"
在檔案最後面加入
export PATH=$PATH:/home/danny/workspace/DB410C/Toolchain/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/bin

*此修改只需關掉Terminal在開啟後,就都會被設定



***
另外發現也可以直接去修改 /etc/enviroment  這檔案裡面包含原本PATH變數的資料, 要增加請在最後面用:加上你要加入的路徑即可, 例如:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/danny/workspace/DB410C/Toolchain/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/bin"









2016年5月11日 星期三

網路概論,OSI七層,以及各層封包的英文名稱

下圖是OSI七層的架構圖,TCP/IP則將七層簡化為五層,另外圖片最右邊則是各層相對應的應用


















其中每一階層都會對封包有個別的名稱,其對應稱謂如下所示
 - 應用層(Application Layer)        Message
 - 傳送層(Transport Layer)           Segment
 - 網路層(Network Layer)             Datagram
 - 鏈結層(Data Link Layer)           Frame

了解以上稱謂,我們就可以清楚的知道是在講哪一層的封包處理




Router實作了OSI底下三層,如下圖










而Switch實作OSI底下兩層,如下圖










2016年2月5日 星期五

在Linux上使用類似system("PAUSE")的方法

system("PAUSE");
上面的語法使用在windows上的功能,可以等待使用者按了任意Key再繼續動作

然而這段語法放在Linux上是沒有任何作用的,如果在Linux上想使用類似的功能請使用如下的語法,可以讓使用者按了Enter再繼續:

====================================================
#include <stdio.h>

#define PAUSE {printf("Press Enter key to continue..."); fgetc(stdin);}

int main()
{
    PAUSE
    return 0;
}
====================================================

以上紅字的部分,就是使用方法~

2016年2月4日 星期四

Opencv,Mat的研究:depth(), channels(), elemSize(), dims, step

今天稍為花了一點時間 研究了一下使用Mat讀進了一張圖之後以下這些參數與函式所代表的意義   ===> depth(), channels(), elemSize(), dims, step


假設我們現在使用了下面這行程式碼讀了一張  780x1040(寬x高),彩色的圖片
Mat img1 = imread(test.jpg, IMREAD_UNCHANGED);


img1.depth(): 

代表每個pixel的位元(bit)數,在opencv中的Mat.depth()中得到的是一個0~6的數字,分別代表不同的位數,enum{ CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6}; 0和1都代表8位, 2和3都代表16位, 4和5代表32位,6代表64位,test.jpg為CV_8U因此img1.depth()=0。

img1.channels(): 

代表每個pixel的通到個數,以test.jpg為例為彩色的RGB圖,因此                                                   img1.channels()=3。

img1.elemSize(): 

img1.elemSize() = 3 (每一個元素包含3個uchar值), img1.elemSize1() = 1 (elemSize/channels); 矩陣中每一個元素的數據大小,如果Mat中的數據類型是CV_8U,則elemSize=1,若是CV_8UC3則elemSize=3,若是CV_16UC2則eleSize=4。

img1.dims: 

這張圖片的維度,以這張圖為例,寬x高是二為陣列的資料,所以img1.dims=2。

img1.step: 

step這個部分有兩個值可以取,第一個是img1.step[0],另一個是img1.step[1]。 img1.step[0]代表一列所有的數據大小,而img1.step[1]代表一個pixel的數據大小,所以img1.step[0]=img1 .cols * img1 .elemSize() = ˙780*3 = 2340,img1.step[1]=3,另外如果看到(int)img1.step這樣的寫法,也就是取img1.step[0]的意思。



以上~