先說ubuntu下的那個程序吧,上次都編的差不多了,主要是熟悉了shmget, shmat的操作就好了,廣泛蒐羅之後比較好的貼子就拿來了,以後可能用得着。但是編這次程序的時候居然發現了上次的bug。。。。
1、我開闢了兩塊共享內存,一個num用來存緩衝區中有多少產品,這樣指針直接後移這麼多就好了。但是producer.c中通過判斷num是否爲零來初始化,但是可能出現消費完的情況,這樣就出現了範老師最討厭的問題:有時候對有時候錯,錯了還自己偷偷知道。。因爲不一定什麼時候消費完,也可能一直沒有出現這樣的情況,所以加了個flag用來判斷是否是第一次,再初始化。
2、生產者生產的時候,我是通過讀取最後一個數字,然後++,在寫進去的,所以如果消費完的話,是沒有內容可以讀取出來的,所以把受範老師影響的while循環,改成了for,直接靠循環次數來記錄編號,不用再讀取了。。
這樣看來,上次程序就有這些問題,只不過當時是兩個進程,在一個程序裏,可能切換的比較頻繁?總之沒有發現消費者消費完的情況,純屬僥倖啊。。還是自己考慮問題不全面。。好丟人。。我還去問TA了,結果問着自己悟出來了,有時候自己在那想不如給別人講一下自己的思路呢,講的時候比較詳細,就能縷一遍,可能自己就明白了。。不過暴露了上次的問題。。。希望不要扣分呀~~~
接下來是糾結了好幾天的0.11下的程序了。。看了十三章之後就清楚了許多,但具體實現還是找了好多網上的資料。首先開闢共享內存有那個現成的get_free_page()函數(這個居然上次就用到了好神奇~),但是獲得的是物理地址,而我們編程時纔不關心這些呢,使用的是虛擬地址,所以需要找到程序中一段空閒的線性地址,減去基地址就是虛擬地址啦~同時也要把找到的線性地址映射到get_free_page()獲得的物理地址上去。。。我是在shmget函數中實現找到空閒的物理內存,在shmat函數中找到空閒的線性地址,映射好,並且返回虛擬地址供用戶操作。。但是思路清晰了問題又來了,還是總結一下吧~~
1、怎麼獲得空閒的線性地址呢?我們的程序的線性地址中有代碼段,數據段,堆棧段,也有空閒的部分,參考書最後有一個get_base的函數,看來是返回一個地址的,但裏面的參數我就不明白了。。於是我查了下task_struct的結構,本想知道ldt這個數組是幹嘛的,結果收穫更多啊~看紅色的部分就好,不解釋。。。
struct task_struct {
/*----------------------- these are hardcoded - don't touch -----------------------*/
long state; // 進程運行狀態(-1不可運行,0可運行,>0以停止)
long counter; // 任務運行時間片,遞減到0是說明時間片用完
long priority; // 任務運行優先數,剛開始是counter=priority
long signal; // 任務的信號位圖,信號值=偏移+1
struct sigaction sigaction[32]; //信號執行屬性結構,對應信號將要執行的操作和標誌信息
long blocked; // 信號屏蔽碼
/*----------------------------------- various fields--------------------------------- */
int exit_code; // 任務退出碼,當任務結束時其父進程會讀取
unsigned long start_code,end_code,end_data,brk,start_stack;
// start_code 代碼段起始的線性地址
// end_code 代碼段長度
// end_data 代碼段長度+數據段長度
// brk 代碼段長度+數據段長度+bss段長度
// start_stack 堆棧段起始線性地址
// end_data 代碼段長度+數據段長度
long pid,father,pgrp,session,leader;
// pid 進程號
// father 父進程號
// pgrp 父進程組號
// session 會話號
// leader 會話首領
unsigned short uid,euid,suid;
// uid 用戶標id
// euid 有效用戶id
// suid 保存的用戶id
unsigned short gid,egid,sgid;
// gid 組id
// egid 有效組id
// sgid 保存組id
long alarm; // 報警定時值 long utime,stime,cutime,cstime,start_time; // utime 用戶態運行時間
// stime 內核態運行時間
// cutime 子進程用戶態運行時間
// cstime 子進程內核態運行時間
// start_time 進程開始運行時刻
unsigned short used_math; // 標誌,是否使用了387協處理器
/* ----------------------------------file system info-------------------------------- */
int tty; // 進程使用tty的子設備號,-1表示沒有使用
unsigned short umask; //文件創建屬性屏蔽碼
struct m_inode * pwd; // 當前工作目錄的i節點
struct m_inode * root; // 根目錄的i節點
struct m_inode * executable; // 可執行文件的i節點
unsigned long close_on_exec; // 執行時關閉文件句柄位圖標誌
struct file * filp[NR_OPEN]; // 進程使用的文件
/*------------------ldt for this task 0 - zero 1 - cs 2 - ds&ss -------------------*/
struct desc_struct ldt[3]; // 本任務的ldt表,0-空,1-代碼段,2-數據和堆棧段
/* ---------------------------------tss for this task ---------------------------------*/
struct tss_struct tss; // 本任務的tss段};
其實不用get_base也可以,直接用current->start_code獲得基地址就行,我試過了,是可以的。但是我有個疑問,就是獲得的基地址能不能直接加上長度呢?獲得的是否就是加上這段長度的地址?答案是肯定的,所以是我考慮複雜了,直接相加就好。。。
2、後來程序運行出現了死循環,又不知道怎麼退出,於是我把consumer也在後臺運行了,然後打印了好多信息在文件中。。原來是sem_open的時候忘記考慮第二次open了。。於是加上了判斷,看名字是否已經存在,但名字是用戶態和內核態的比較,還是要用get_fs_byte來獲取,然後在比較的。。因爲上次在一個程序裏,只open了一次,就沒有這個問題。由此觀之,不要以爲上次運行的對的就萬事大吉了啊!!情況不同了,也不能拋棄人家sem.c啊。。
3、但是,死循環依然存在。。。並且數字特別規律,就是10——0一循環,難道。。。。果然!粗心的我居然兩次都正好獲得了基地址加上current->brk的空閒內存,於是。。。衝突了。。。所以我又往後移了shmid*1024*4的長度,shmid都不一樣,這樣就不會衝突了。。。
4、由於剛開始沒有出現trying to free free page的提示,後來在別人的建議下將生產者生產的數目變成了40,於是就出現了。。經過多次實驗,發現生產者生產數目少的話就還出現panic,大的話就不會。。想了想,在producer.c的最後打印了輸出語句,結果是這樣的:生產數目少就會先把生產者運行完,即文本中輸出的第一行是生產者結束,接下來是消費者的打印內容。。而生產數目多的時候,生產者進程就一直沒有結束了,因爲一直在等待消費才能繼續生產,沒有結束則沒有free_page,也就不會有panic了。。。