http://debug-sai.blogbus.com/logs/51711788.html
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
//傳入文件fd
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
//根據文件fd得到file
file = fget_light(fd, &fput_needed);
if (file) {
//讀出當前偏移
loff_t pos = file_pos_read(file);
//從當前偏移讀,pos返回讀取後的偏移
ret = vfs_read(file, buf, count, &pos);
//設置新偏移
file_pos_write(file, pos);
fput_light(file, fput_needed);//
}
return ret;
}
看看fget_light是怎麼根據fd得到file的
struct file *fget_light(unsigned int fd, int *fput_needed)
{
struct file *file;
struct files_struct *files = current->files;
*fput_needed = 0;
if (likely((atomic_read(&files->count) == 1))) {
//若已經打開了
file = fcheck_files(files, fd);
} else {
//若沒有打開,加鎖
rcu_read_lock();
file = fcheck_files(files, fd);
if (file) {
if (atomic_long_inc_not_zero(&file->f_count))
*fput_needed = 1;
else
/* Didn't get the reference, someone's freed */
file = NULL;
}
rcu_read_unlock();
}
return file;
fput_light的流程如下,具體見源代碼:
fput_light->fput(當所有釋放完時還要調用__fput)
接著看 vfs_read:
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
//下面都是一系列驗證
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;
ret = rw_verify_area(READ, file, pos, count);
if (ret >= 0) {
count = ret;
if (file->f_op->read)
//一般都是到這裡調用vfs的read
ret = file->f_op->read(file, buf, count, pos);
else
ret = do_sync_read(file, buf, count, pos);
if (ret > 0) {
fsnotify_access(file->f_path.dentry);
add_rchar(current, ret);
}
inc_syscr(current);
}
return ret;
}
跟著就是vfs設置的read函數了,對於ext2來說是
const struct file_operations ext2_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.unlocked_ioctl = ext2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext2_compat_ioctl,
#endif
.mmap = generic_file_mmap,
.open = generic_file_open,
.release = ext2_release_file,
.fsync = ext2_sync_file,
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
所以是do_sync_read:
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
struct iovec iov = { .iov_base = buf, .iov_len = len };
struct kiocb kiocb;
ssize_t ret;
//初始化kiocb
init_sync_kiocb(&kiocb, filp);
kiocb.ki_pos = *ppos;
kiocb.ki_left = len;
for (;;) {
//其實就是變了個參數傳到generic_file_aio_read中。。。具體分析就先到這了
ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
if (ret != -EIOCBRETRY)
break;
wait_on_retry_sync_kiocb(&kiocb);
}
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&kiocb);
*ppos = kiocb.ki_pos;
return ret;
}
2013年8月11日 星期日
2013年4月17日 星期三
system call open()
今天review linux character driver的時候,發現往往定義的file_operations的.open成員,參數擁有file以及inode參數,可是這個參數哪邊來的?
其實會對open()有好奇,主要也是之前一直在trace linux kernel source內一些socket的東西,socket本身由sockfs支援,不使用open()而用socket()來開啟inode,所以想說兩者的區別是?
kernel內用 inode 結構體用來表示data。因此,它和 file structure 用來表示一個打開了的fd並不相同。對於一個inde,可能會有多個 file structure 對應著多個已打開的多個fd,但是這都只能指向同一個 inode 結構。
回頭看open(),大多數的system call使用interrupt 0x80,所以很快地追蹤到sys_open()是理所當然的入口
簡化整個call stack為sys_open()=>filp_open()=>dentry_open()=>dentry_open()
在呼叫register_chrdev()的時候將有覆寫的file_operations賦予device
底下是一些重要的工作,完整的source code就不列了
sys_open() : 配置fd
filp_open() : 依賴路徑取得nameidata結構,跟著呼叫dentry_open()取回file instance
dentry_open() : 配置file instance,nameidata結構內包含了inode instance,此時將一些必要資料由inode拷貝到file instance,f->f_op = fops_get(inode->i_fop);,此時f->f_op->open(inode,f)就是當時register_chrdev()所註冊的file_operations。
參考資料:
http://hi.baidu.com/potyzhang/item/ae9993a919e86f17a8cfb793
http://hi.baidu.com/heiyebujianwo/item/fa7fe543d99b73ab61d7b9cb
其實會對open()有好奇,主要也是之前一直在trace linux kernel source內一些socket的東西,socket本身由sockfs支援,不使用open()而用socket()來開啟inode,所以想說兩者的區別是?
kernel內用 inode 結構體用來表示data。因此,它和 file structure 用來表示一個打開了的fd並不相同。對於一個inde,可能會有多個 file structure 對應著多個已打開的多個fd,但是這都只能指向同一個 inode 結構。
回頭看open(),大多數的system call使用interrupt 0x80,所以很快地追蹤到sys_open()是理所當然的入口
簡化整個call stack為sys_open()=>filp_open()=>dentry_open()=>dentry_open()
在呼叫register_chrdev()的時候將有覆寫的file_operations賦予device
底下是一些重要的工作,完整的source code就不列了
sys_open() : 配置fd
filp_open() : 依賴路徑取得nameidata結構,跟著呼叫dentry_open()取回file instance
dentry_open() : 配置file instance,nameidata結構內包含了inode instance,此時將一些必要資料由inode拷貝到file instance,f->f_op = fops_get(inode->i_fop);,此時f->f_op->open(inode,f)就是當時register_chrdev()所註冊的file_operations。
參考資料:
http://hi.baidu.com/potyzhang/item/ae9993a919e86f17a8cfb793
http://hi.baidu.com/heiyebujianwo/item/fa7fe543d99b73ab61d7b9cb
2013年2月14日 星期四
select()的細節
直接先看code
如果可以一眼看出答案,表示對select()有深入了解。容我賣個關子,這裡問題出在於stdio對於buffer以及kernel buffer的認知上面
1: #include <sys/types.h>
2: #include <sys/select.h>
3: #include <stdio.h>
4: #include <stdlib.h>
5: #include <string.h>
6:
7: #define BUFSIZE (256)
8:
9: int main(void){
10: fd_set read_fdset;
11: int maxfd=1;
12: char buff[BUFSIZE];
13: while (1) {
14: FD_ZERO(&read_fdset);
15: FD_SET(0, &read_fdset);
16: int result = select(maxfd + 1, &read_fdset, NULL, NULL, NULL);
17: if (0 > result){
18: printf("select error\n");
19: exit(1);
20: }
21:
22: if (FD_ISSET(0, &read_fdset)){
23: printf("data is ready\n");
24: int c=getc(stdin);
25: printf("%c",c);
26: }
27: }
28: return 0;
29: }
結果是?輸入test,按下兩次enter,得到的結果,如下圖如果可以一眼看出答案,表示對select()有深入了解。容我賣個關子,這裡問題出在於stdio對於buffer以及kernel buffer的認知上面
2013年1月26日 星期六
pipe
在linux底下,有anonymous pipe以及named pipe,兩者差異是anonymous pipe是不具名,所以在不同process之間無法共同使用,他存取主要藉由process的parent/child來存取,也就是在parent下先宣告一個pipe(),就可以同時在child下面共享;或者也可以由一個parent下的兩個child分享也是可以的
pipe的特性是,他是半雙工,也就是只有一個process通常只處理一端read(or write),另外一個process處理write(or read)的一端。現在許多新的系統也可以是全雙工的。
對pipe或許可以想像成是多個processes之間共享的queue,這樣就比較好理解了
richard stevens的書籍給出了許多的應用,比方說: process同步、filter...,可想而知pipe在unix/linux環境下的應用
在process建立parent/child關係,通常是透過fork(),所以動作變得有點麻煩,比方說
popen()跟pclose()提供一個偷懶的方式XD,藉由popen等同fork()出一個process並且同時使用exec執行指令,跟著再把stdin跟stdout做好對應
底下是richard stevens的範例,pipe(fd[2])的參數[0]表示輸出,[1]表示輸入,程式就是將hello world由paretn送給child
pipe的特性是,他是半雙工,也就是只有一個process通常只處理一端read(or write),另外一個process處理write(or read)的一端。現在許多新的系統也可以是全雙工的。
對pipe或許可以想像成是多個processes之間共享的queue,這樣就比較好理解了
richard stevens的書籍給出了許多的應用,比方說: process同步、filter...,可想而知pipe在unix/linux環境下的應用
在process建立parent/child關係,通常是透過fork(),所以動作變得有點麻煩,比方說
- 宣告pipe()
- fork()
- 分別在parent, child中關閉
- 必要的時候呼叫dup()/dup2()對應stdin跟stdout
popen()跟pclose()提供一個偷懶的方式XD,藉由popen等同fork()出一個process並且同時使用exec執行指令,跟著再把stdin跟stdout做好對應
底下是richard stevens的範例,pipe(fd[2])的參數[0]表示輸出,[1]表示輸入,程式就是將hello world由paretn送給child
#include <unistd.h> #include <sys/types.h> #define MAXLINE 80 int main(void){ int n,fd[2]; pid_t pid; char line[MAXLINE]; if(pipe(fd)<0){ printf("pipe failed!\n"); exit(1); } if( (pid=fork())<0 ){ printf("fork error!\n"); exit(1); }else if(pid>0){ close(fd[0]); write(fd[1],"hello world\n",12); }else{ close(fd[1]); n=read(fd[0],line,MAXLINE); write(STDOUT_FILENO,line,n); } exit(0); }這裡必須要提到的是在一般stdin/stdout/stderr是FILE型態,而STDOUT_FILENO則是int型態,差別在於型態
2012年3月8日 星期四
Posix Message Queue (1)
Richard Steven的大作:
header file: mqueue.h
mqd_t mq_open(const char *name, int oflag, ...);
int mq_close(mqd_t mqdes);
int mq_unlink(const char *name);
回傳一個handler,型態為mqd_t,close表示關閉,unlink並不會真正刪除,而是隨著系統對close作count,直到count=0,也就是最後一個process使用close,才會刪除message queue
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, struct mq_attr *attr,mq_attr *oattr);
struct mq_attr{
long mq_flags;
long mq_maxmsg;
long mq_msgsize;
long mq_curmsgs;
};
可以取得各種屬性,其中mq_maxmsg跟mq_msgsize都只能在mq_open的時候設定,免得硬要把已經在msg queue內的message縮小,這樣的操作屬於不合理
message queue收送
int mq_send(mqd_t mqdes,const char *ptr, size_t len, unsigned int prio);
ssize_t mq_receive(mqd_t mqdes,const char *ptr, size_t len, unsigned int prio);
書中有提到一個問題就是message queue資料型態被定義為char*,其實建議應該定義為void*
另外就是在receive的時候必須先使用mq_getattr取得資料的大小,如果太小將會傳回錯誤,書中的程式figure 5.7就是這麼作,其實我認為應該是使用最大的msg size最為配置大小才不會產生錯誤,因為如果多人同時讀取message queue難保沒有race condition,不然就得加上file lock的機制
message queue跟FIFO一個很大的差異就是優先權以及個數,對FIFO來說,他就是一個stream,可是message是很多個package組成,再者FIFO並沒有優先權的概念,message queue有
另外必須注意到系統message queue的限制,書中提到可以使用sysconf來讀取
後面開始探討message非同步的問題,這就牽涉到system call以及非同步訊號安全函數的使用,等後面有空再寫
header file: mqueue.h
mqd_t mq_open(const char *name, int oflag, ...);
int mq_close(mqd_t mqdes);
int mq_unlink(const char *name);
回傳一個handler,型態為mqd_t,close表示關閉,unlink並不會真正刪除,而是隨著系統對close作count,直到count=0,也就是最後一個process使用close,才會刪除message queue
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, struct mq_attr *attr,mq_attr *oattr);
struct mq_attr{
long mq_flags;
long mq_maxmsg;
long mq_msgsize;
long mq_curmsgs;
};
可以取得各種屬性,其中mq_maxmsg跟mq_msgsize都只能在mq_open的時候設定,免得硬要把已經在msg queue內的message縮小,這樣的操作屬於不合理
message queue收送
int mq_send(mqd_t mqdes,const char *ptr, size_t len, unsigned int prio);
ssize_t mq_receive(mqd_t mqdes,const char *ptr, size_t len, unsigned int prio);
書中有提到一個問題就是message queue資料型態被定義為char*,其實建議應該定義為void*
另外就是在receive的時候必須先使用mq_getattr取得資料的大小,如果太小將會傳回錯誤,書中的程式figure 5.7就是這麼作,其實我認為應該是使用最大的msg size最為配置大小才不會產生錯誤,因為如果多人同時讀取message queue難保沒有race condition,不然就得加上file lock的機制
message queue跟FIFO一個很大的差異就是優先權以及個數,對FIFO來說,他就是一個stream,可是message是很多個package組成,再者FIFO並沒有優先權的概念,message queue有
另外必須注意到系統message queue的限制,書中提到可以使用sysconf來讀取
後面開始探討message非同步的問題,這就牽涉到system call以及非同步訊號安全函數的使用,等後面有空再寫
2012年3月6日 星期二
Pipe與FIFO
header file : unistd.h
int pipe(int fd[2]); 正確回傳0錯誤回傳-1
相當單純的一個函數,fd[0] 供 read,fd[1]提供write
使用於fork()的時候,通常provider關閉fd[0],只有使用fd[1],相反的comsumer則是關閉fd[1],使用fd[0],關閉pipe則是使用close()
pipe是半雙工的模式,如果要使用全雙工,richard steven建議使用兩組pipe來模擬
header file: stdio.h
FILE *popen(const char *cmd, const char *type);
int pclose(FILE *stream);
類似使用檔案一樣,只是執行一道command,然後由stdout讀入資料,或者使用寫入模式,經stdin將資料輸入command中
Pipe有兩個問題,第一個就因為file desriptor只有在程式執行間產生,也就是大致上只能適用於parent/child之間的關係,對於不相關的process就無法溝通,第二個問題是沒有權限的管理,因為設計的關係,本身不遭遇/提供這樣的情境
FIFO的設計就是用來解決這問題
header files: sys/types.h, sys/stat.h
int mkfifo(const char *pathname, mode_t mode);
由於是輸入路徑,所以只要知道檔案路徑就可以在process之間的做溝通,取得回傳值之後就可以使用open來開啟,但是FIFO不支援lseek操作,會回傳ESPIPE錯誤
關閉一樣使用close就可以,但是因為mkfifo會產生file,所以必須使用unlink()來刪除
至於FIFO還有其他的特性,如nonblock,晚點有時間再寫
int pipe(int fd[2]); 正確回傳0錯誤回傳-1
相當單純的一個函數,fd[0] 供 read,fd[1]提供write
使用於fork()的時候,通常provider關閉fd[0],只有使用fd[1],相反的comsumer則是關閉fd[1],使用fd[0],關閉pipe則是使用close()
pipe是半雙工的模式,如果要使用全雙工,richard steven建議使用兩組pipe來模擬
header file: stdio.h
FILE *popen(const char *cmd, const char *type);
int pclose(FILE *stream);
類似使用檔案一樣,只是執行一道command,然後由stdout讀入資料,或者使用寫入模式,經stdin將資料輸入command中
Pipe有兩個問題,第一個就因為file desriptor只有在程式執行間產生,也就是大致上只能適用於parent/child之間的關係,對於不相關的process就無法溝通,第二個問題是沒有權限的管理,因為設計的關係,本身不遭遇/提供這樣的情境
FIFO的設計就是用來解決這問題
header files: sys/types.h, sys/stat.h
int mkfifo(const char *pathname, mode_t mode);
由於是輸入路徑,所以只要知道檔案路徑就可以在process之間的做溝通,取得回傳值之後就可以使用open來開啟,但是FIFO不支援lseek操作,會回傳ESPIPE錯誤
關閉一樣使用close就可以,但是因為mkfifo會產生file,所以必須使用unlink()來刪除
至於FIFO還有其他的特性,如nonblock,晚點有時間再寫
IPC object的life time
| IPC Object | lifetime |
| FIFO | process |
| Posix mutex | process |
| Posix conditional var | process |
| Posix read/write lock | process |
| fcntl recorder lock | process |
| Posix message queue | kernel |
| Posix named semaphore | kernel |
| Posix memory semaphore | process |
| Posix shared memory | kernel |
| System V message queue | kernel |
| System V semaphore | kernel |
| System V shared memory | kernel |
| TCP socket | process |
| UDP socket | process |
| Unix domain socket | process |
明顯的kernel將會隨著有無釋放相關的IPC物件而影響lifetime,另外一個影響因素就是為是否為kernel等級,如果是kernel的lifetim,該物件如果沒有釋放,將會被保留到系統結束為止,換句話說,他會相當的佔用系統的資源
底下是兩大主流Posix與System V所支援的IPC的head file
| IPC Object | Header file | functions |
| Posix msg queue | mqueue.h | mq_open、mq_close、mq_close、mq_getattr、mq_setattr、mq_send、mq_receive、mq_notify |
| Posix semaphore | semaphore.h | sem_open、sem_close、sem_unlink、sem_init、sem_destroy、sem_wait、sem_trywait、sem_post、semgetvalue |
| Posix shared memory | sys/mman.h | shm_open、shm_unlink、ftruncate、fstat、mmap、munmap |
| System V msg queue | sys/msg.h | msgget、msgctl、msgsnd、msgrcv |
| System V semaphore | sys/sem.h | semget、semctl、semop |
| System V shared memory | sys/shm.h | shmget、shmctl、shmat、shmdt |
2012年3月4日 星期日
Shared Memory in C (2)
今天翻出了Unix Network Programming Vol. 2,看了shared memory
原來還有分posix跟跟system v阿,之前用的是system v的share memory,主要利用到幾個呼叫函數
#include<sys/shm.h>
shmget 建立一塊share memory
shmat 將share memory attached到某個process的空間內
shmdt 分離share memory與該行程
shmctl share meory的操作,包含刪除的部分
一直誤shared memory是parent/child或者child/child之間的資訊交換使用,事實上他可以超越許多process,也就是兩個processes之間沒有關係也可以使用shared memory做溝通,不過中間有個重點就是key的產生,在使用shmget的時候會需要用到一個唯一的key去產生,只要知道這個key value,就可以取得相對應的shared memory
在書中使用ftok這個function去產生唯一的key,其實有個小小的陷阱,ftok接受一個存在的檔案路徑,並且產生相對應的唯一key,也就是只要預期兩個相同的檔案路徑就會產生相同的key,但是事實上man page有提到,如果說該檔案路徑被刪除後就有可能不一樣,也就是說假設A, B兩個processes,A使用ftok(path1,1)取得key1,當path1重新被建立(如刪除後又重新新增),這時候B使用ftok(path1,1)可能會取得key2且key2!=key1
效能方面,書中也有所解說,shared memory與pipe/FIFO/message que這些空間主要都由kernel在維護,但是後者是由process A讀取檔案(system call 1)後寫入pipe/FIFO/message queue(system call 2),跟著再由process B取得(system call 3),最後再由process B輸出到(system call4)檔案,也就是後者進行了四次的system call。由於shared memory是直接對於kernel將共享的記憶體映射到process空間,也就是不用透過kernel來管理,這樣只要兩次(system call 1, 4),所以效能會比較好
雖然richard steven大師這樣的解釋讓人比較了解差別跟應用的情境,但是實際上實作的差異還是有分別,如shared memory屬於哪個process?還是屬於kernel?如果實際屬於kernel那就嚴重囉,表示使用者如果讓他產生溢位的話可能會危害到kernel,如果屬於process,那他應該屬於哪個process的?這些疑問等以後有時間再來trace code好了
原來還有分posix跟跟system v阿,之前用的是system v的share memory,主要利用到幾個呼叫函數
#include<sys/shm.h>
shmget 建立一塊share memory
shmat 將share memory attached到某個process的空間內
shmdt 分離share memory與該行程
shmctl share meory的操作,包含刪除的部分
一直誤shared memory是parent/child或者child/child之間的資訊交換使用,事實上他可以超越許多process,也就是兩個processes之間沒有關係也可以使用shared memory做溝通,不過中間有個重點就是key的產生,在使用shmget的時候會需要用到一個唯一的key去產生,只要知道這個key value,就可以取得相對應的shared memory
在書中使用ftok這個function去產生唯一的key,其實有個小小的陷阱,ftok接受一個存在的檔案路徑,並且產生相對應的唯一key,也就是只要預期兩個相同的檔案路徑就會產生相同的key,但是事實上man page有提到,如果說該檔案路徑被刪除後就有可能不一樣,也就是說假設A, B兩個processes,A使用ftok(path1,1)取得key1,當path1重新被建立(如刪除後又重新新增),這時候B使用ftok(path1,1)可能會取得key2且key2!=key1
效能方面,書中也有所解說,shared memory與pipe/FIFO/message que這些空間主要都由kernel在維護,但是後者是由process A讀取檔案(system call 1)後寫入pipe/FIFO/message queue(system call 2),跟著再由process B取得(system call 3),最後再由process B輸出到(system call4)檔案,也就是後者進行了四次的system call。由於shared memory是直接對於kernel將共享的記憶體映射到process空間,也就是不用透過kernel來管理,這樣只要兩次(system call 1, 4),所以效能會比較好
雖然richard steven大師這樣的解釋讓人比較了解差別跟應用的情境,但是實際上實作的差異還是有分別,如shared memory屬於哪個process?還是屬於kernel?如果實際屬於kernel那就嚴重囉,表示使用者如果讓他產生溢位的話可能會危害到kernel,如果屬於process,那他應該屬於哪個process的?這些疑問等以後有時間再來trace code好了
2012年3月1日 星期四
signal process、shared memory與IPC
shared memory這手法已經是常見的IPC手法,但是在開發期間往往程式會在無預期的地方出錯,或者是手動ctrl+c給予中斷,這時候必須加入signal process讓程式自行清理,或者類似socket的地方,不然會有port number或者shared memory繼續被占據,下次執行前必須手動清理
常見的signal有
SIGINT : 由ctrl+c產生
SIGTERM : 關機或者由其他方式送入
最後在IPC產生的時候,由child process可以要求系統對於parent process結束的時候送出一個signal給child process
prctl(PR_SET_PDEATHSIG, SIGHUP);
這個只適用於linux,屬於kernel額外提供的功能
但是一旦需要再signal process中回收資源的後,表示資源必須是global variable,因為我暫時想不到其他方式,這某種程度讓程式變得比較不穩定,當然可以想辦法寫入shared memory中,但是這樣一來就面臨shared memory規劃的問題(要小心不要輸入的資料蓋錯地方),還有race condition的議題
愈是深入IPC就愈覺得System Programming因作業系統而異的特色
參考資料:
http://www.win.tue.nl/~aeb/linux/lk/lk-5.html
http://www.cs.cf.ac.uk/Dave/C/node24.html
http://www.c.happycodings.com/Gnu-Linux/code18.html
http://hylcarson.blog.sohu.com/54735006.html
常見的signal有
SIGINT : 由ctrl+c產生
SIGTERM : 關機或者由其他方式送入
最後在IPC產生的時候,由child process可以要求系統對於parent process結束的時候送出一個signal給child process
prctl(PR_SET_PDEATHSIG, SIGHUP);
這個只適用於linux,屬於kernel額外提供的功能
但是一旦需要再signal process中回收資源的後,表示資源必須是global variable,因為我暫時想不到其他方式,這某種程度讓程式變得比較不穩定,當然可以想辦法寫入shared memory中,但是這樣一來就面臨shared memory規劃的問題(要小心不要輸入的資料蓋錯地方),還有race condition的議題
愈是深入IPC就愈覺得System Programming因作業系統而異的特色
參考資料:
http://www.win.tue.nl/~aeb/linux/lk/lk-5.html
http://www.cs.cf.ac.uk/Dave/C/node24.html
http://www.c.happycodings.com/Gnu-Linux/code18.html
http://hylcarson.blog.sohu.com/54735006.html
2012年2月29日 星期三
Shared Memory in C (1)
最近因為需要,所以必須使用到shared memory,一看之下,越是覺得shared memory其實是相當有趣的一種構想,只會存在於特定的平台,強烈跟作業系統相關,雖然說很多系統都支援這樣的實做
很多人都知道process有自己獨立的virtual memory space,這也是MMC存在的價值,但是在沒有multi-thread的時候,兩個process分享資料有其需求,這時候就必須透過shared memory,可是這塊shared memory應該屬於誰的?我想應該是OS"作弊"在某個記憶體區間弄出了一塊空間,這塊空間是屬於作業系統管理的,雖然他還是有權限的控管,但是他的存在不屬於單一process
最後回到為何我需要利用到shared memory,我先前想要建立兩個server,這兩個server位於同一台機器,但是兩者的狀態可能會互相影響,共同的資料分享是必須得,如果只是兩個工作這件事情可以用multi-thread來解決,multi-thread在race condition的控管比multi-process容易許多,也好的多,很不幸,因為是server,server會呼叫accept這個system call,意味著system call將會block住整個process,導致另外一個server/thread無法正常運作,這時候只好轉向multi-process
當然還有其他更複雜的手法,比方說使用interval socket來建立process之間的連線,但是複雜度會比shared memory高,pipe的方式則是效能略遜,如果其他網友有更好的方式請建議我
很多人都知道process有自己獨立的virtual memory space,這也是MMC存在的價值,但是在沒有multi-thread的時候,兩個process分享資料有其需求,這時候就必須透過shared memory,可是這塊shared memory應該屬於誰的?我想應該是OS"作弊"在某個記憶體區間弄出了一塊空間,這塊空間是屬於作業系統管理的,雖然他還是有權限的控管,但是他的存在不屬於單一process
最後回到為何我需要利用到shared memory,我先前想要建立兩個server,這兩個server位於同一台機器,但是兩者的狀態可能會互相影響,共同的資料分享是必須得,如果只是兩個工作這件事情可以用multi-thread來解決,multi-thread在race condition的控管比multi-process容易許多,也好的多,很不幸,因為是server,server會呼叫accept這個system call,意味著system call將會block住整個process,導致另外一個server/thread無法正常運作,這時候只好轉向multi-process
當然還有其他更複雜的手法,比方說使用interval socket來建立process之間的連線,但是複雜度會比shared memory高,pipe的方式則是效能略遜,如果其他網友有更好的方式請建議我
IPC中的shared memory管理
其實寫shared memory很常遇到的一個問題是,空間已經配置,但是因為程式的不正常結束,導致空間尚未歸還,下次再用同樣的key要申請的時候引發File exists的錯誤
使用
ipcs -t
ipcs -m
他會列出所有shared memory的列表
ipcrm -m 393228
最後一個是列表上的share memory id,表示將該區域清空
參考資料:
http://telinit0.blogspot.com/2009/09/clearing-shared-memory-of-crashed.html
使用
ipcs -t
ipcs -m
他會列出所有shared memory的列表
ipcrm -m 393228
最後一個是列表上的share memory id,表示將該區域清空
參考資料:
http://telinit0.blogspot.com/2009/09/clearing-shared-memory-of-crashed.html
2012年2月21日 星期二
LCD之framebuffer(2)
在網路上找到了一些現成的程式拿來觀察,發現了一些有趣的現象
得到的結果是
很簡單的可以得知解析度為1366*768,每個pixel用32bits(4 bytes)來表示一切都還符合預期,但是最上面兩個我就有點疑問了,尤其是memory佔用的大小,4.0xMB,長度(line_length)如我之前猜測,可能因為1366這數字是16:9的解析度,所以實際上在處理的時候會用比較長的長度(1376)來表示,但是怎樣算術都是無法得到memory size的大小
不過我使用其他方式將1366*768*32塞入對應記憶體空間內的時候,卻是又可以填滿畫面,例如讓整個畫面呈現紅色或者藍色,表示其他記憶體區塊可能是沒有使用到或者被才切掉的,這部份我還搞不大懂
參考資料:
http://www.linuxgraphics.cn/graphics/fb_drawpoint.html
http://www.lslnet.com/linux/f/docs1/i48/big5328178.htm
得到的結果是
很簡單的可以得知解析度為1366*768,每個pixel用32bits(4 bytes)來表示一切都還符合預期,但是最上面兩個我就有點疑問了,尤其是memory佔用的大小,4.0xMB,長度(line_length)如我之前猜測,可能因為1366這數字是16:9的解析度,所以實際上在處理的時候會用比較長的長度(1376)來表示,但是怎樣算術都是無法得到memory size的大小
不過我使用其他方式將1366*768*32塞入對應記憶體空間內的時候,卻是又可以填滿畫面,例如讓整個畫面呈現紅色或者藍色,表示其他記憶體區塊可能是沒有使用到或者被才切掉的,這部份我還搞不大懂
參考資料:
http://www.linuxgraphics.cn/graphics/fb_drawpoint.html
http://www.lslnet.com/linux/f/docs1/i48/big5328178.htm
2012年2月20日 星期一
LCD之framebuffer(1)
有人把framebuffer當成driver,可是我稍微看了一下幾個系統,似乎framebuffer的個數並不一致(有的只有fb0,可是有的有fb1~fb3),還有看到有人講解framebuffer本身其實是一種抽象介面,我是蠻同意這樣的看法,不過我並未完全用程式檢驗任何一個平台完畢
其中有人提到下面有趣的實驗,但是我實驗並不如我所想得
對 framebuffer 操作:
dd if=/dev/fb0 of=fbfile
可以將 fb0 中的內容保存下來存到 fbfile 裡
如果顯示模式是 1024*768 的 8 位色
dd if=/dev/zero of=/dev/fb0 bs=1024 count=768
可以清空 framebuffer (螢幕全黑)
dd if=fbfile of=/dev/fb0
將 fbfile 內的資料寫回 framebuffer
其中有人提到下面有趣的實驗,但是我實驗並不如我所想得
對 framebuffer 操作:
dd if=/dev/fb0 of=fbfile
可以將 fb0 中的內容保存下來存到 fbfile 裡
如果顯示模式是 1024*768 的 8 位色
dd if=/dev/zero of=/dev/fb0 bs=1024 count=768
可以清空 framebuffer (螢幕全黑)
dd if=fbfile of=/dev/fb0
將 fbfile 內的資料寫回 framebuffer
在NB上實驗第二條,想要把整個螢幕填入乘黑色,我的解析度為1366x768,結果不論我填入fb=1366 count=768*3或者fb=1366 count=768*4(數值是乘法過後的數值,我這裡用乘法好理解),都無法整個填滿,另外就是其實不用急著把之前存下的資料蓋回去,只要拿著視窗"當抹布抹一抹"亂掉的區域就可以復原(少數位置抹不到,我覺得也不是很要緊,反正在實驗)
改天寫個C語言來把framebuffer內容倒出來看看,我猜測有一部份可能是1366還會被處理一些對齊的方式,可能被擴展到1440,或者其他解析度,其餘的被切除了
參考連結:
http://moto.debian.tw/viewtopic.php?t=11901&start=0&postdays=0&postorder=asc&highlight=
http://top12345tw.blogspot.com/2008/04/lcd-driver.html
參考連結:
http://moto.debian.tw/viewtopic.php?t=11901&start=0&postdays=0&postorder=asc&highlight=
http://top12345tw.blogspot.com/2008/04/lcd-driver.html
2012年2月12日 星期日
用qemu觀察interrupt以及製作system call
網路上的資料教我們怎樣做到這兩件事情,還頗為有趣,首先是觀察interrupt
interrupt事實上在硬體設備上是有限的,所以大多時候是必須要分享的,除此之外,還有目前更複雜的多核心中斷處理(想想,一個中斷發生,但是有多個核心,誰該去處理這件事情?)。因為共享,所以有時候在設備有接上主機的時候,在跟系統要求中斷,不要用的時候必須歸還,或者說暫時切換,這樣才讓系統在有限的資源內可以為多個硬體的中斷做服務
有了以上的概念,跟著就是直接切入code,在上面印出資訊,至kernel source目錄,進入drivers/mmc/mmci.c這個source code,這是一個SD Card Host Controller driver,在
ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);之前加上printk,因為已經在kernel mode就不要再用printf,printf是給user mode用的
printk("\n***************Hello Interrupt*******************\n");
重新編譯kernel
make CROSS_COMPILE=arm-linux- ARCH=arm
接著把產生出來的kernel image放到之前busybox目錄,跟著執行,這些中斷就註冊上去,所以在開機訊息上可以看到,如下圖(我拼錯字了~囧,不管他)
===============
interrupt事實上在硬體設備上是有限的,所以大多時候是必須要分享的,除此之外,還有目前更複雜的多核心中斷處理(想想,一個中斷發生,但是有多個核心,誰該去處理這件事情?)。因為共享,所以有時候在設備有接上主機的時候,在跟系統要求中斷,不要用的時候必須歸還,或者說暫時切換,這樣才讓系統在有限的資源內可以為多個硬體的中斷做服務
- request_irq() :向系統註冊使用某一個 interrupt ,並指定負責處理該 interrupt 的 ISR 。
- free_irq() :告知系統釋放特定的 interrupt 。
有了以上的概念,跟著就是直接切入code,在上面印出資訊,至kernel source目錄,進入drivers/mmc/mmci.c這個source code,這是一個SD Card Host Controller driver,在
ret = request_irq(dev->irq[0], mmci_irq, IRQF_SHARED, DRIVER_NAME " (cmd)", host);之前加上printk,因為已經在kernel mode就不要再用printf,printf是給user mode用的
printk("\n***************Hello Interrupt*******************\n");
重新編譯kernel
make CROSS_COMPILE=arm-linux- ARCH=arm
接著把產生出來的kernel image放到之前busybox目錄,跟著執行,這些中斷就註冊上去,所以在開機訊息上可以看到,如下圖(我拼錯字了~囧,不管他)
===============
接下來自行建立一個system call,system call是系統提供給一般程式開發者作為有限存取kernel功能的一個介面,可以想成開發kernel的人提供給programmer的API,必須要做的事情有五件
- 製作system call的功能,大致上一般程式設計差不多,不過是kernel mode
- 註冊 system call 的名字
- 定義新 system call 的代碼
- 調整 Makefile ,使 systam call 被包含在 kernel 中
- 增加 system call 的 header file ,讓 user program 能夠 include
第二、三件事情對於一般程式設計師比較陌生,但是如果有看過system programming的書籍,大致上會提到過,底下要注意,大多目錄是相對於kernel source code目錄,在定義function name、system call number有一些prefix words要注意
第一步在arch/arm/kernel下建立mytestcall.c,內容是
#include <linux/linkage.h>
#include <linux/kernel.h>
asmlinkage void sys_mytestcall(){
static int count = 0;
printk("mytestcall has been called for %d time(s)\n", ++count);
}
第四步驟,在arch/arm/kernel/Makefile裡面,sys_mytestcall.o加到target後面,讓他可以被編譯,這時候就可以重新編譯kernel,指令跟前面一樣
看到用了asmlinkage的修飾子跟sys_這個prefix
第二步在arch/arm/kernel/calls.S下將創立的system call的名稱做註冊上去在
CALL(sys_set_mempolicy)
後面加上
CALL(sys_mytestcall)
後面加上
CALL(sys_mytestcall)
第三步驟,號碼註冊上去,直接就取前面那個最小的+1即可,前面最小的是,include/asm-arm/unistd.h裡面,最小的是底下
#define __NR_set_mempolicy (__NR_SYSCALL_BASE+321)第四步驟,在arch/arm/kernel/Makefile裡面,sys_mytestcall.o加到target後面,讓他可以被編譯,這時候就可以重新編譯kernel,指令跟前面一樣
make CROSS_COMPILE=arm-linux- ARCH=arm
第五件事情,是為了一般programmer提供header file,就如大家寫lib,總是要提供head file讓人家include,才可以做symbol以及link,命名include/linux/mytestcall.h
#include <linux/unistd.h>
#define __NR_mysyscall (__NR_SYSCALL_BASE+322)
#define mytestcall(void) syscall(__NR_mytestcall);
===============
===============
以上事情完成就可以開始寫測試程式了,我是直接就在linux kernel source code目錄底下建立一個名為testmycall.c,內容很簡單
#include "linux/mysyscall.h"
int main(){
mytestcall();
return 0;
}
跟著編譯他,指令如下
arm-linux-gcc -I ./include -static testmycall.c -o testmycall
完成後把新的kernel放到busybox目錄下,把這個testmycall拷貝到busybox內的_install目錄底下,然後重新用cpio命令打包_install目錄成root image,用qemu開機,跟著直行就可以看到成果了
這裡可以看到有趣的現象,一般的程式在結束的時候,static variable也跟著被釋放掉,再次執行該程式就重新來過,但是這裡system call是屬於kernel mode,他生命週期一般跟第一次載入記憶體後,就待到觀及,所以再次呼叫的時候,裡面的static變數還是繼續加1變成了2
一般而言儘量避免使用這種static用法,因為system call有點類似global變數,如果使用了這樣的用法,很容易造成race condition的情形。
一般而言儘量避免使用這種static用法,因為system call有點類似global變數,如果使用了這樣的用法,很容易造成race condition的情形。
訂閱:
文章 (Atom)




