2008年12月24日 星期三

GNU document



在寫程式時有時會忘了函數的用法,這裡提供了許多GNU工具手冊可以參考,都是chm的實在很方便。

http://code.google.com/p/htmlhelp/downloads/list?q=label:CHM

2008年12月19日 星期五

Reentrant and Thread-Safe Code



前些日子,在撰寫一個專案時,程式一直當在某一段function,後來先使用mutex方式來解決,但一直未認真研究問題在那,近日終於想到應該是多線程中reentrant的影響會導致程式當掉,為了提高系統效能,一定得修改這部份的程式碼,先來研究一下Reentrant。
下面這一篇就講的很詳細了。【引用來自chinaunix
-------------------------------------------------------------------------------------
記得以前討論過一個關於reentrant函數與thread safe函數的帖子,很多人對於這兩種函數不是很了解,尤其是發現malloc等函數是non-reentrant函數時,對多線程編程都產生了"恐懼",這裡是我對這兩種函數的一些理解,希望和大家探討一些.歡迎批評指正.

1. reentrant函數

一個函數是reentrant的,如果它可以被安全地遞歸或並行調用。要想成為reentrant式的函數,該函數不能含有(或使用)靜態(或全局)數據(來存儲函數調用過程中的狀態訊息),也不能返回指向靜態數據的指標,它只能使用由調用者提供的數據,當然也不能調用non-reentrant函數

比較典型的non-reentrant函數有getpwnam, strtok, malloc等

reentrant和non-reentrant函數的例子
CODE:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <sys>
#include <unistd.h>
#include <math.h>

int* getPower(int i)
{
static int result;
result = pow(2, i);
getchar();
return result;
}

void getPower_r(int i, int* result)
{
*result = pow(2, i);
}

void handler (int signal_number) /*處理SIGALRM信號*/

{
getPower(3);
}

int main ()
{
int *result;
struct sigaction sa;
memset( sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigaction(SIGALRM, sa, NULL);
result = getPower(5);
printf("2^5 = %d\n", *result);
return 0;
}


試驗方法:
1. 編譯 gcc test.c -lpthread
在一個終端中營運 ./a.out, 在另一個終端中營運 ps -Agrep a.out可以看到該進程的id
2. 用如下模式營運a.out:
營運./a.out,在按返回前,在另外一個終端中營運kill -14 pid (這裡的pid是營運上面的ps時看到的值)
然後,按返回繼續營運a.out就會看到2^5 = 8 的錯誤結論


對於函數int* getPower(int i)

由於函數getPower會返回一個指向靜態數據的指標,在第一次調用getPower的過程中,再次調用getPower,則兩次返回的指標都指向同一塊內存,第二次的結果將第一次的覆蓋了(很多non-reentrant函數的這種用法會導致不確定的後果).所以是non-reentrant的.


對於函數void getPower_r(int i, int* result)

getPower_r會將所得的訊息存儲到result所指的內存中,它只是使用了由調用者提供的數據,所以是reentrant.在信號處理函數中可以正常的使用它.


2. thread-safe函數

Thread safety是多線程編程中的概念,thread safe函數是指那些能夠被多個線程同時並發地正確執行的函數.

thread safe和non thread safe的例子

CODE:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER;

int count; /*共享數據*/

void* func (void* unused)
{
if (count == 0)
count++;
}

void* func_s (void* unused)
{
pthread_mutex_lock( sharedMutex); /*進入臨界區*/
if (count == 0)
count++;
pthread_mutex_unlock( sharedMutex); /*離開臨界區*/
}


int main ()
{
pthread_t pid1, pid2;
pthread_create( pid1, NULL, func, NULL);
pthread_create( pid2, NULL, func, NULL);
pthread_join(pid1, NULL);
pthread_join(pid2, NULL);
return 0;
}

函數func是non thread safe的,這是因為它不能避免對共享數據count的race condition,設想這種情況:一開始count是0,當線程1進入func函數,判斷過count == 0後,線程2進入func函數線程2判斷count==0,並執行count++,然後線程1開始執行,此時count != 0 了,但是線程1仍然要執行count++,這就產生了錯誤.

func_s透過mutex鎖將對共享數據的訪問鎖定,從而避免了上述情況的發生。func_s是thread safe的,只要透過適當的"鎖"機製,thread safe函數還是比較好實現的.

3. reentrant函數與thread safe函數的區別

reentrant函數與是不是多線程無關,如果是reentrant函數,那麼要求即使是同一個進程(或線程)同時多次進入該函數時,該函數仍能夠正確的運作.該要求還蘊含著,如果是在多線程環境中,不同的兩個線程同時進入該函數時,該函數也能夠正確的運作.

thread safe函數是與多線程有關的,它只是要求不同的兩個線程同時對該函數的調用在邏輯上是正確的.

從上面的說明可以看出,reentrant的要求比thread safe的要求更加嚴格.reentrant的函數必是thread safe的,而thread safe的函數
未必是reentrant的. 舉例說明:

CODE:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER;

int count; /*共享數據*/

void* func_s (void* unused)
{
pthread_mutex_lock( sharedMutex); /*進入臨界區*/
printf("locked by thead %d\n", pthread_self());
if (count == 0)
count++;
getchar();
pthread_mutex_unlock( sharedMutex); /*離開臨界區*/
printf("lock released by thead %d\n", pthread_self());
}

void handler (int signal_number) /*處理SIGALRM信號*/
{
printf("handler running in %d\n", pthread_self());
func_s(NULL);
}


int main ()
{
pthread_t pid1, pid2;
struct sigaction sa;
memset( sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigaction(SIGALRM, sa, NULL);
printf("main thread's pid is: %d\n", pthread_self());
func_s(NULL);
pthread_create( pid1, NULL, func_s, NULL);
pthread_create( pid2, NULL, func_s, NULL);
pthread_join(pid1, NULL);
pthread_join(pid2, NULL);
func_s(NULL);
return 0;
}

試驗方法:
1. 編譯 gcc test.c -lpthread
在一個終端中營運 ./a.out, 在另一個終端中營運 ps -Agrep a.out可以看到該進程的id
2. 進行下面4次營運a.out:
每次營運分別在第1,2,3,4次返回前,在另外一個終端中營運kill -14 pid (這裡的pid是上面ps中看到的值)

試驗結果:
1. 該進程中有3個線程:一個主線程,兩個子線程
2. func_s是thread safe的
3. func_s不是reentrant的
4. 信號處理程式會中斷主線程的執行,不會中斷子線程的執行
5. 在第1,4次返回前,在另外一個終端中營運kill -14 pid會形成死鎖,這是因為
主線程先鎖住了臨界區,主線程被中斷後,執行handler(以主線程執行),handler試圖鎖定臨界區時,
由於同一個線程鎖定兩次,所以形成死鎖
6. 在第2,3次返回前,在另外一個終端中營運kill -14 pid不會形成死鎖,這是因為一個子線程先鎖住
了臨界區,主線程被中斷後,執行handler(以主線程執行),handler試圖鎖定臨界區時,被掛起,這時,子線程
可以被繼續執行.當該子線程釋放掉鎖以後,handler和另外一個子線程可以競爭進入臨界區,然後繼續執行.
所以不會形成死鎖.

結論:
1. reentrant是對函數相當嚴格的要求,絕大部分函數都不是reentrant的(APUE上有一個reentrant函數
的清單).
什麼時候我們需要reentrant函數呢?只有一個函數需要在同一個線程中需要進入兩次以上,我們才需要
reentrant函數.這些情況主要是異步信號處理,遞歸函數等等.(non-reentrant的遞歸函數也不一定會
出錯,出不出錯取決於你怎么定義和使用該函數). 大部分時候,我們並不需要函數是reentrant的.

2. 在多線程環境當中,只要求多個線程可以同時調用一個函數時,該函數只要是thread safe的就可以了.
我們常見的大部分函數都是thread safe的,不確定的話請查閱相關文檔.

3. reentrant和thread safe的本質的區別就在於,reentrant函數要求即使在同一個線程中任意地進入兩次以上,也能正確執行.

大家常用的malloc函數是一個典型的non-reentrant但是是thread safe函數,這就說明,我們可以方便的
在多個線程中同時調用malloc,但是,如果將malloc函數放入信號處理函數中去,這是一件很危險的事情.

4. reentrant函數肯定是thread safe函數,也就是說,non thread safe肯定是non-reentrant函數
不能簡單的透過加鎖,來使得non-reentrant函數變成 reentrant函數,這個鏈接是說明一些non-reentrant ===> reentrant和non thread safe ===>thread safe轉換的


最後有一些結論要記住:

* 識別出由函數庫匯出的所有全局變量。這些全局變量通常是在頭檔案中由export關鍵字定義的。
匯出的全局變量應該被封裝起來。每個變量應該被設為函數庫所私有的(透過static關鍵字實現),然後創建全局變量的訪問函數來執行對全局變量的訪問。
* 識別出所有靜態變量和其他共享資源。靜態變量通常是由static關鍵字定義的。
每個共享資源都應該與一個鎖關聯起來,鎖的粒度(也就是鎖的數量),影響著函數庫的性能。為了初始化所有鎖,可能需要一個僅被調用一次的初始化函數。
* 識別所有非可重入函數,並將其轉化為可重入。
* 識別所有非線程安全函數,並將其轉化為線程安全。

------------------------------------------------------------------------------------


後來發現我的subfunction中有個變數被宣告為static,無怪一直找不出bug在那,困擾許久的問題終於解決。



參考資料
http://publib.boulder.ibm.com/infocenter/systems/index.jsp?topic=/com.ibm.aix.genprogc/doc/genprogc/writing_reentrant_thread_safe_code.htm
http://blog.csdn.net/lovekatherine/archive/2007/03/28/1544585.aspx
http://www.ttisql.com/relatedlink/thread-safe.html
http://bbs.chinaunix.net/thread-971102-1-1.html

Porting mplayer to ARM9



CPU:ARM9
kernel 2.6.17
MPlayer-1.0rc2.tar.tar
arm-linux-gcc version 4.2.1


原本是要port MPlayer-1.0pre6.tar.tar,不過由於gcc版本不支援,並不會產生Makefile,後來強制讓它忽略gcc版本檢查(--disable-gcc-checking),在編譯時又發生glibc版本太新,function會有衝突,就放棄了。
那就採用最新的版本吧,configure參數如下
#./configure --cc=arm-linux-gcc --host-cc=gcc --enable-cross-compile --target=arm-linux --enable-static --disable-mp3lib --disable-dvdnav --disable-dvdread --disable-libdvdcss-internal --disable-dvdread-internal --enable-fbdev --disable-mencoder --disable-x11 --disable-sdl --enable-mad


接下來一切順利。

後來就直接到版子上試試,無聲音無影像
錯誤訊息為:
visual: 0 not yet supported
FATAL: Cannot initialize video driver.

FATAL: Could not initialize video filters (-vf) or video output (-vo).


fb指定的裝置是對的,但不支援console,將kernel 中選項打開
[*] Framebuffer Console support


另外,由於執行如未指定解碼器,會有以下錯誤訊息
[mp2 @ 0x6c5698]Header missing skipping one byte.

所以執行時要指定解碼器
#./mplayer -ac mad file.mpg

終於成功了,但是聲音跟影像嚴重不同步,又得研究一下了。

2008年12月9日 星期二

Usage of #define



a. 最大、最小值比較

#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); \
(void) (&_x == &_y); _x < _y ? _x : _y; })
#define max(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); \
(void) (&_x == &_y); _x > _y ? _x : _y; })


b. 用於除錯:

#ifdef DEBUG
#define PDEBUG(fmt, arg...) printk(fmt, ##arg)
#define ENTER() PDEBUG("[%-20s] : Enter...\n", __FUNCTION__)
#define LEAVE() PDEBUG("[%-20s] : Leave...\n", __FUNCTION__)
#else
#define PDEBUG(fmt, arg...)
#define ENTER()
#define LEAVE()
#endif

更簡單的表示:
#define DBG(msg, arg...) printf("%s:%s(%d): " msg, __FILE__, __FUNCTION__, __LINE__, ##arg)


2008年12月3日 星期三

linux kernel



linux Platform Driver 說明

從2.6版本開始引入了 platform 這個概念,在開發底層驅動程序時,首先要確認的就是設備的資源信息,例如設備的地址,在2.6內核中將每個設備的資源用結構 platform_device 來描述,該結構體定義在linux\ include\linux\platform_device.h 中


struct platform_device {
const char *name;
int id;
struct device dev;
u32 num_resources;
struct resource *resource;
};

該結構一個重要的元素是 resource,該元素存入了最為重要的設備資源信息,定義在linux\include\ linux\ioport.h 中

struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};

下面舉個例子來說明一下:
在 linux/arch/arm/plat-s3c24xx/devs.c 定義了

static struct resource s3c_lcd_resource[ ] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};

這裡定義了兩組 resource,它描述了一個 lcd 設備的資源,第1組描述了這個 lcd 設備所佔用的地址範圍,IORESOURCE_MEM 表示第 1 組描述的是內存類型的資源信息,第2組描述了這個 lcd 設備的中斷號,
IORESOURCE_IRQ 表示第 2 組描述的是中斷資源信息。設備驅動會根據 flags 來獲取相應的資源信息。

有了 resource 信息,就可以定義 platform_device 了:

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};

EXPORT_SYMBOL( s3c_device_lcd );

將 s3c_device_lcd expoert 讓其他檔案使用

把 s3c_device_lcd 加到 platform_device 裡面 linux/arch/arm/mach-s3c2440/mach-smdk2440.c:

static struct platform_device *smdk2440_devices[ ] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
};

有了 platform_device 就可以使用函數 platform_add_devices 向系統中添加該設備了,這裡的作法是

static void __init smdk2440_machine_init(void)
{
..............

platform_add_devices( smdk2440_devices, ARRAY_SIZE( smdk2440_devices ) );

............
}

MACHINE_START(S3C2440, "SMDK2440")
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,

.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END

驅動程序需要實現結構體 struct platform_driver,參考 linux\driver\video\s3c2410fb.c,
static struct platform_driver s3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = s3c2410fb_remove,
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd",
.owner = THIS_MODULE,
},
};

在驅動初始化函數中使用函數 platform_driver_register() 註冊 platform_driver

int __init s3c2410fb_init( void )
{
return platform_driver_register( &s3c2410fb_driver );
}

module_init( s3c2410fb_init );

需要注意的是: s3c_device_lcd 結構中 name 元素和 s3c2410fb_driver 結構中 driver 的 name 必須是相同的。

這樣在 platform_driver_register() 註冊時,會對所有已註冊的所有 platform_device 中的 name 和當前注冊的 platform_driver 的 driver->name 進行比較,只有找到相同的名稱的 platfomr_device 才能註冊成功,當註冊成功時會呼叫 platform_driver 結構元素 probe 函數指針,這裡就是 s3c2410fb_probe。

當進入 probe 函數後,需要獲取設備的資源信息,獲取資源的函數有:

struct resource * platform_get_resource( struct platform_device *dev, unsigned int type, unsigned int num );

根據參數 type 所指定類型,例如 IORESOURCE_MEM,來獲取指定的資源。

struct int platform_get_irq( struct platform_device *dev, unsigned int num );

獲取資源中的中斷號。

struct resource * platform_get_resource_byname( struct platform_device *dev, unsigned int type, char *name );

根據參數 name 所指定的名稱,來獲取指定的資源。

int platform_get_irq_byname( struct platform_device *dev, char *name );

根據參數 name 所指定的名稱,來獲取資源中的中斷號。

資料來源: http://top12345tw.blogspot.com/2008/03/linux-kernel-26243-platform-device.html