Pages

2009年2月27日 星期五

驅動程式初體驗

因為需求,必須去了解 Linux Driver,所以就東問西問、看非常艱澀的驅動程式第3版、亂試一通,終於把 Dualview 給弄出來,所以趁著空檔時間,來po個文章..:)。##ReadMore##

這裡先簡單把如何寫一個驅動程式的基本程式碼列出來做說明,當然也可以從歐萊禮下載原始碼來做測試,如果官方網站掛掉還有這裡
// 定義模組的初始函式(__init)與清理函式(__exit)
#include <linux/init.h>
// 定義許多 module_ 開頭的函式或 MODULE_ 開頭的巨集
#include <linux/module.h>

#define DRIVER_AUTHOR   "Linly"
#define DRIVER_DESC     "Hello World crap driver"
#ifndef DRIVER_VERSION
#define DRIVER_VERSION  "v0.0.1"
#endif

/*
* KERN_ALERT = <1>,代表訊息優先度,最高為 <0>
* 用法同 printf,只是不支援浮點數
*/
static int __init helloworld_init(void)
{
 printk(KERN_ALERT "Hello, world\n");
 return 0;
}

static void __exit helloworld_cleanup(void)
{
 printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(helloworld_init);    // for insmod calls
module_exit(helloworld_cleanup); // for rmmod calls

// 模組資訊,可從 modinfo helloworld.ko 查詢
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
說明如下:
  1. 前兩行的標頭檔通常都會加入,作用如註解所述。
  2. module_init 是模組(驅動程式)載入會執行的;module_exit 是模組(驅動程式)移除會執行的。
  3. printk 是寫 kernel module 會使用的 API,因為一般開發應用程式的 API 都不能再撰寫驅動程式時使用,只能使用 kernel API。而優先度的部份共分為七種<0>到<7>,數字越小代表優先度越高,如果你希望訊息可以再你期望的地方印出來,就讓它優先度高一點吧,我通常使用<1>,而這些訊息可以透過「dmesg」看到。
  4. MODULE_LICENSE 一定要加上所規定的一些字串,有幾種選擇,擇一即可,如:"GPL"、"GPL v2"、"GPL and additional rights"、"Dual BSD/GPL"、"Dual MPL/GPL"、"Proprietary"。
  5. 載入模組為「sudo insmod xxxx.ko」;移除模組為「sudo rmmod xxxx.ko」,如果說要重新掛載已經存在的模組,需先移除,然後再載入。  
再來就是 Makefile,它和以往所寫的不同,在於驅動程式使用的是核心提供的函式,所以編譯時必須把目錄切換至 kernel source tree 來做編譯,這個東西可以把它想成是 develop toolkit,像標頭檔就會這裡頭。如果是 Ubuntu,該路徑位於「/usr/src」;如果是 fedora,需要去下載安裝,因為作業系統裝好並不會存在。
KERNEL_VERSION    := `uname -r`
KERNEL_DIR    := /lib/modules/$(KERNEL_VERSION)/build

PWD        := $(shell pwd)

obj-m        := helloworld.o
#module-objs   := helloworld.o

all:
    echo "Building Hello World driver..."
    make -C $(KERNEL_DIR) M=$(PWD) modules

clean:
    -rm -f *.o *.ko *.mod.c Module.symvers version.h

你會看到 KERNEL_DIR 是指派成 bra bra build,不過實際到那邊看,會發現 build 其實是一個 symbolic link,指向 /usr/src/$(KERNEL_VERSION),也就是 kernel source tree。編譯後會回到 pwd,並且在程式的目錄下,產生 .o 檔案,最後再根據 obj-m 的名稱,產生 .ko 檔(如根據 helloworld.o 產生 helloworld.ko)。然後「sudo insmod helloworld.ko」,就可以再 dmesg 看到訊息囉。

2009年2月25日 星期三

Daemon

  Daemon 似乎不需要像 windows service 一樣去註冊,只要讓他開機自動執行即可,而他滿足幾項特性:
  1. 開機後即不斷執行。
  2. 父程序為 init。
  3. 不具有 tty (所以printf是幫不了你)。
  4. 執行者為 root (所以和他通訊的程序也必須是 superuser 所執行)。##ReadMore##

  在「Advanced Programming in the UNIX Environment」一書中提到撰寫 daemon 的規則,不過我也沒遵守,因為不了解這些規則的重要性,不過還是簡單列出該書所提到的部份:
  1. 需呼叫 fork,且父程序直接 exit。
  2. 呼叫 setsid。
  3. 改變工作目錄為「/」。
  4. 把 umask 設定為「0」,避免繼承來的遮罩導致檔案權限不如預期。
  5. 關閉無用的 file descriptor。
#include <sys/type.h>
#include <sys/stat.h>
#include <fcntl.h>

int daemon_init(void)
{
pid_t pid;
if((pid = fork()) < 0)
return -1;
else if(pid != 0)
exit(0);

setsid();
chdir("/");
umask(0);
return 0;
}
  
  以上是本書的初使化範例,不論如何,都需要寫一個 script 讓開機自動啟動 daemon,這裡的作法是在 /etc/init.d/rc.local 去執行我寫的一個 script,當然只要在 rc2.d 中會執行到的 script 中加入都是可以的 [註]。以下列出 script 內容;
#!/bin/sh

[ -x /home/linly/DualView/daemon/Dualview-Daemon ] || exit 0
#filename=/home/linly/DualView/daemon/Dualview-Daemon
#test ! -e $filename && echo "The filename '$filename' DO NOT exist" && exit 0

case "$1" in
start)
# Start daemons.
cd /home/linly/DualView/daemon
./Dualview-Daemon &
;;
stop)
# Stop daemons.
chk=`/usr/bin/pgrep -x Dualview-Daemon`
if [ "$chk" ]; then
echo -n "Shutting down Dualview Daemon "
kill -9 $chk
fi
echo ""
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage: Dualview-Daemon {start|stop|status|restart}"
exit 1
esac

cd /home/linly/DualView/uvcvideo
/sbin/rmmod uvcvideo
/sbin/insmod uvcvideo.ko

exit 0

開機後輸入 ps -ajx | grep "Dualview-Daemon" 會看到以下內容:
PPID   PID    PGID   SID    TTY    TPGID  STAT   UID   TIME   COMMAND
1 6038 5067 5067 ? -1 S 0 0:00 ./Dualview-Daemon

  以我的 daemon 為例,由於 shared memory 擁有者是 root,所以權限不足是砍不掉的。而且需要用 signal 來通知 daemon 的應用程式,也必須是 superuser 所執行的應用程式。還有一點,如果需要印訊息,必須透過 syslog 這個 daemon 來達成(因為沒有終端機),以下列出相關的函式:
#include <syslog.h>
void openlog(char *ident, int option, int facility);
void syslog(int priority, char *format, ...);
void closelog(void);

  執行時發生一個問題,就是 gtk_init 無法順利初始化,導致程式被中斷,嘗試很久後,採用 gtk_init_check,該函式即使出始化失敗也不會中斷程式,且能夠順利初使 gdk 的元件,有達到我的需求。(初使做了很多動作,也不知道掛在哪裡,反正我要用的東西有初始好就好拉:P)


注意事項
  • rc2.d 是一般開機進入圖形介面的過程中,會執行的腳本,像 rc0.d 就是關機會執行的;而 rc6.d 是重新開機會執行的。

2009年2月19日 星期四

Camera Preview with V4l2

很久之前做的範例,一直沒放上來,所以就放上來分享一下,這是用 C 和 GTK+ 寫的,一個很單純的 preview,沒有其他額外的功能,且需安裝 gtk+-2.0...

範例:Camera Preview
投影片介紹:V4L2 Introduction

2009年2月18日 星期三

GTK+ 也支援 atomic operation

  本來期望C語言的API有支援,不過搜尋一下/usr/include的標頭檔卻找不到,倒是找到C++和GTK+有支援,所以就去翻一下/usr/include/glib-2.0/glib/gatomic.h,以下是簡單的使用範例:
##ReadMore##
#include <gtk/gtk.h>
#include <stdio.h>

gint atomic;

gboolean test(gpointer data)
{
g_atomic_int_add(&atomic, 1);
gint get_atomic = g_atomic_int_get(&atomic);

if(get_atomic <= 10)
{
g_print("atomic val = %d\n", get_atomic);
return TRUE;
}
else
gtk_main_quit();
}


int main(int argc, char *argv[])
{
gtk_init(&argc, &argv);
g_timeout_add(1000,test,NULL);

g_atomic_int_add(&atomic, 1);
g_print("atomic val = %d\n", g_atomic_int_get(&atomic));

gtk_main();
return 0;
}

  提醒一下,編譯時記得加上`pkg-config gtk+-2.0 --libs --cflags`,否則會找不到相關的函式庫與標頭檔。

2009年2月9日 星期一

Shared memory and Semaphore 的使用

形成通訊是蠻常會遇到的問題。當兩個 process 在執行過程中需要溝通,可以使用許多方式,例如:
  • socket(by AF_UNIX)
  • pipe or fifo
  • file
  • shared memory
  • mmap (我覺得可以歸類到file)
  • message queue (不會用XD)
這裡我所要舉得例子是:shared memory + semaphore。畢竟是共享資源,就要考慮同步,否則會發生 race condition。由於程式碼貼上來過於累贅,我就放在 google doc,程式碼的內容是 P1(write)會一直寫進去,而 P2(read)會去讀取並列印再終端機。[註1]
[註1] 若沒有使用 semaphore,印出來的結果會有重複的內容出現。
[註2] semaphore 是另外包裝的一組 API,提供七個函式,如下:
  1. sem_create - 產生 semaphore,並設定初值。
  2. sem_open - 開啟 semaphore,前題以執行過。
  3. sem_rm - 刪除 semaphore,給 sem_close 使用。
  4. sem_close - 關閉 semaphore,並做刪除(sem_rm)。
  5. sem_op - 給 sem_wait、sem_signal 使用。
  6. sem_wait - semaphore--。
  7. sem_signal - semaphore++。
 
Blogger Templates