嵌入式Linux應用程式開發
1.Linux成員命令的使用。
切換超級使用者:su 例:su - root
使用者管理:useradd:新增使用者賬號 passwd:設定賬號密碼 例:useradd liu;passwd liu
顯示程序:ps:顯示當前使用者執行程序列表 例:ps -ef
殺死程序:kill:輸出特定的訊號給特定的PID(程序號) 例:kill -9 7421(kill -9為強制中止)
列出所有可用訊號:kill -l
磁碟命令:fdisk -1(列出檔案系統的分割槽情況,需root)
掛載分割槽:mount -t vfat /dev/hda1 /mnt/win/c(mount -t後面接上檔案型別)
切換路徑:cd 例:cd /home/david(移至主目錄home下的david目錄)
cd -:回到前次目錄 ./:當前目錄 ../:上一級目錄
顯示檔案:ls -l詳細資訊 -a所有檔案
新建目錄:mkdir 例:mkdir -m 777 ./why(777為檔案許可權) mkdir -p ./home/why
連線並顯示多個檔案:cat 例:cat -n hello1.c hello2.c
複製移動刪除:cp -a 、mv -f 、rm -rf
例:cp -a ./why/mhy ./(將why/mhy下的檔案複製到當前目錄下,保留原目錄下的檔案)
mv -i ./why/mhy ./(將why/mhy目錄下的檔案移動到當前目錄下,刪除原目錄下的檔案)
rm -rf .why/mhy(刪除目錄下的檔案,不需確認直接刪除)
更改三兄弟:chown(修改檔案所有者和組別)、chgrp(修改檔案的組所有權)、chmod(修改檔案許可權)
例:chown root hello.c(hello.c檔案所有者改為root)
chgrp root hello.c(檔案使用者組改為root)
chmod 777 hello.c(對檔案擁有者(u)、所屬的使用者組(g)、系統裡其他使用者(o)都有rwx(讀寫執行)許可權)
搜尋檔案中的內容:grep 例:grep “hello” / -r(-r表示搜尋範圍包括子目錄,若為-d skip 則忽略子目錄)
指定目錄搜檔案:find 例:find ./ -name hello*.c(可以指定目錄,-name也可改為-user,後接使用者名稱)
查詢檔案(主要用於資料庫):locate 例:locate issue -U ./(在指定位置新建資料庫)
建立連結檔案(為一個檔案在另一個位置建立符號連結以節省空間):ln
例:ln -s ../hello1.c ./hello(當ls -l 時檔案許可權變為了lrwxrwxrwx,前面的l代表著檔案為符號連結)
壓縮和解壓縮(只能壓縮單個檔案,對多個檔案需要打包後再壓縮,用的少):gzip
例:gzip hello.c(ls後文件名為hello.c.gz)
gzip -l hello.c(加入-l會顯示壓縮前和壓縮後的檔案大小、壓縮百分比及壓縮前的名字)
壓縮和解壓縮:tar -zcvf壓縮 -zxvf解壓
例:tar -zcvf hello.c tar -zxvf hello.c.gz
檔案差異:diff -f(很長很囉嗦)
打補丁:patch -pnum
網路修改:ifconfig 例:ifconfig eth0 192.168.126.131(修改IP地址)
2.Linux系統中檔案的型別和特點。p7
-
普通檔案(-):包括文字檔案,shell 指令碼, 二進位制的可執行程式和各種型別的資料
-
目錄檔案(d):目錄也是檔案,包含檔名和子目錄以及指向那些檔案和子目錄的指標
-
連結檔案(l):類似於快捷方式,但是可以實現對不同的目錄、檔案系統甚至不同機器上的檔案直接訪問,並且不必重新佔用磁碟
-
裝置檔案(c, b):Linux 把裝置當檔案處理,方便了使用者的使用。裝置相關檔案放在 /dev 下
-
字元裝置(c):串列埠的介面裝置
-
塊裝置(b):指資料的讀寫,是以塊為單位裝置
-
3.位操作。
& 與操作
| 或操作
- ^ 異或操作
- 取反操作
.>>右移操作
<< 左移操作
4.Vi,gcc,gdb的使用。
Vim:i 插入
/name: 查詢name字串
N
:wq 儲存退出
:q! 強制退出不儲存
gcc:-E 只進行預編譯,不做其他處理 例:gcc -E hello.c -o hello.i(.i為預處理的C程式)
-S: 只編譯不彙編,生成彙編程式碼 例:gcc -S hello.i -o hello.s
-c: 只編譯不連結,生成目標檔案.o 例:gcc -c hello.s -o hello.o
-o: 自定義可執行檔名稱
-g: 可除錯
-I dir: 標頭檔案搜尋目錄
gdb:除錯程式。gdb [可執行檔案] 例:gdb hello
b N: 在N行設定斷點
l(list): 檢視程式碼
info b: 檢視斷點
r(run): 執行程式
p n/i: 檢視變數n/i
n: 步過(單步執行時不進入函式)
s: 步進(單步執行時進入函式)
c: 繼續(continue)
5.Makefile檔案的編寫。
規則:
target: dependency_files
command /* 該行必須以 Tab 鍵開頭 */
示例:
david: kang.o yul.o
gcc kang.o bar.o myprog
kang.o: kang.c kang.h head.h
gcc -Wall -O -g -c kang.c -o kang.o
yul.o: bar.c head.h
gcc -wall -O -g -c yul.c -o yul.o
改進:
OBJS= kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
david: $(OBJS)
$(CC) $(OBJS) -o david
kang.o: kang.c kang.h
$(CC) $(OBJS) -c kang.c -o kang.o
yul.o:yul.c yul.h
$(CC) $(CFLAGS) -C YUL.C -o yuil.o
OBJS = kang.o yul.o
CC = gcc
CFLAGS= -Wall -O -g
david: $(OBJS)
$(CC) $^ -O $@
kang.o: kang.c kang.h
$(CC) $(CFLAGS) -c $< -o $@
yul.o: yul.c yul.h
$(CC) $(CFLAGS) -C $< -O $@
隱含規則:$(CC) -c $(CPPFLAGS) $(CFLAGS)
OBJS= kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
david: $(OBJS)
$(CC) $^ -o $@
模式規則:
OBJS= kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
david: $(OBJS)
$(CC) $^ -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
6.嵌入式軟體設計,什麼是交叉編譯?什麼是交叉除錯?p110
交叉編譯:在一個平臺上生成另一個平臺上執行的程式碼。
交叉除錯:在宿主機和目標機之間進行,偵錯程式執行圖宿主機的通用作業系統之上,被除錯的程序執行在特定硬體平臺的嵌入式作業系統中,偵錯程式和被除錯程序通過串列埠或者網路進行通訊,偵錯程式可以控制、訪問被除錯程序,讀取被除錯程序的當前狀態,並能夠改變被除錯程序的執行狀態。
7.嵌入式開發常用的除錯手段有哪幾種?簡述它們的優缺點。P111
嵌入式軟體經過編譯和連線後,進入除錯階段。
嵌入式軟體開發,除錯採用的是在宿主機和目標機之間進行交叉除錯
交叉除錯有很多方法,主要分為軟體方式和硬體方式:
軟體方式:
主要是通過插入除錯樁的方式來進行的。是通過目的作業系統和偵錯程式內分別加入某些功能模組,兩者之間互通訊息來進行除錯。該方式的典型偵錯程式有gdb偵錯程式。
-
優點:成本低、簡單、軟體除錯能力強。
-
缺點:必須和宿主機建立通訊連線,需要將軟體燒錄在目標板上(即只能除錯執行在目標作業系統上的應用程式,不宜除錯目標作業系統的核心程式碼及啟動程式碼),工作能力有限。
硬體除錯:
-
ROM監視器
- 優點:ROM監視器功能強大,能夠完成設定斷點、單步執行、檢視暫存器、修改記憶體單元等各項除錯功能。
- 缺點:同軟體除錯一樣,使用ROM監視器目標機必須和宿主機 通訊連線。
-
ROM模擬器
-
優點:避免了每次修改程式後都必須重新燒寫到目標機的ROM中。
-
缺點:ROM模擬器本身比較昂貴,功能相對單一,只適用於某些特定場合。
-
-
線上模擬器
-
優點:功能強大,軟硬體都可做到完全實時線上除錯。
-
缺點:價格昂貴。
-
-
JTAG
- 優點:連線簡單,成本低。
- 缺點:特性受制於晶片廠商。
8.什麼是優先順序?簡述優先順序反轉及其解決辦法。
優先順序:優先順序是計算機分時作業系統在處理多個作業程式時,決定各個作業程式接受系統資源的優先等級的引數。
分為:靜態優先順序,動態優先順序
優先順序反轉:高優先順序程序因為所需資源被低優先順序程序佔用而被阻塞,佔用該資源的低優先順序程序因其優先順序低於其他程序也無法執行而釋放資源,造成最高優先順序程序反而在一段時間內無法執行,系統性能下降的情況。
解決方法:
-
優先順序繼承
-
設定優先順序上限
9.簡述Linux的系統呼叫,使用者程式設計介面(API)、系統命令,以及它們之間的關係。p152
系統呼叫:作業系統給使用者提供的一組特殊介面,使使用者程式可以通過此介面獲得作業系統核心提供的服務
使用者程式設計介面:實際使用中程式設計師呼叫的介面,程式設計師通過呼叫使用者程式設計介面(API)來達到使用系統呼叫的目的,其再通過軟中斷機制向核心提交要求,獲取核心服務的介面。
系統命令:是可執行程式,內部引用了使用者程式設計介面實現相應功能
關係:P153 圖6.1
10.利用Linux的底層檔案I/O操作函式,實現把一原始檔src.txt的最後10KB複製成檔案dst.txt。p157
/* copy_file.c */
/* 22. 利用Linux 的底層檔案 I/O 操作函式,實現把一原始檔 src.txt 的最後 10KB 複製成檔案 dst.txt */
#include <stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/type.c>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024 /* 每次讀寫快取大小,影響執行效率 */
#define SRC_FILE_NAME "src.txt" /* 原始檔名 */
#define DEST_FILE_NAME "dst.txt" /* 目標檔名 */
#define OFFSE 10240 /* 複製的資料大小 */
int main()
{
int src_file, dest_file;
unsigned char buff[BUFFER_SIZE];
int real_ren_len;
/* 以只讀的方式開啟原始檔 */
src_file = open(SRC_FILE_NAME, O_RDONLY);
/* 以只寫的方式開啟目標檔案 */
dest_file = open(DEST_FILE_NAME, o_WRONLY|O_CREATE, S_IRUSR|S_iWUSR|S_IRGRP|S_IPOTH);
if(src_file < 0 || dest_file < 0)
{
printf("Open file error\n");
exit(1);
}
/* 將原始檔的讀寫執行移動到最後 10KB 的起始位置 */
lseek(src_file, -OFFSE, SEEK_END);
/* 讀取原始檔的最後 10KB 資料,並寫到目標檔案中,每次讀寫 1KB */
while((real_ren_len = read(src_file, buffer, sizeof(buff))) > 0)
{
write(dest_file, buff, real_ren_len);
}
close(dest_file);
close(src_file);
}
11.Linux的檔案鎖的型別和特點。p158
- 建議性鎖:要求每一個上鎖檔案的程序都要檢查是否有鎖的存在,並且尊重已有鎖(lockf()、fcntl())。
- 強制性鎖:由核心執行,當一個檔案被上鎖進行寫入操作的時候,核心將阻止其他檔案對其進行讀寫操作(fcntl())
12.Linux的多路複用。p163
Linux多路複用通常指的是I/O多路複用,I/O多路複用通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。I/O 多路複用技術是為了解決程序或執行緒阻塞到某個I/O系統呼叫而出現的技術,使程序不阻塞於某個特定的 I/O 系統呼叫。
目前支援I/O多路複用的系統呼叫函式有select、poll、epoll等。
13.簡述串列埠的三種工作模式,每種工作模式的特點。p172
規範模式:輸入基於行處理
非規範模式:所有輸入即時生效,不可進行行編輯
原始模式:特殊的非規範模式,所有輸入以位元組為單位處理
14.簡述Linux下程序的定義,結構和執行狀態。p203
程序是一個程式的一次執行過程,是資源分配的最小單元
結構:資料段、程式碼段、堆疊段
- 資料段:存放的是全域性變數、常數以及動態資料分配的資料空間。
- 程式碼段:存放的是程式程式碼的資料
- 堆疊段:存放的是子程式的返回地址、子程式的引數以及程式的區域性變數等。
執行狀態:執行、就緒、等待
- 執行態:該程序正在執行,即程序正在佔用CPU
- 就緒態:程序已經具備執行的一切條件,正在等待分配CPU的處理時間片
- 等待態:程序不能使用CPU,若等待事件發生(等待的資源分配到)則可被喚醒。
15.使用fork()建立一個子程序,然後讓子程序暫停5s(使用sleep()函式)。接下來對原有的父程序使用waitpid()函式,並使用引數WNOHANG使父程序不阻塞。若有子程序退出,則waitpid()返回0,並且父程序每隔一秒判斷一次。p216
/* waitpid.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pc, pr;
pc = fork();
if (pc < 0)
{
printf("Error fork\n");
}
else if(pc == 0) /* 子程序 */
{
/* 子程序暫停 5s */
sleep(5);
/* 子程序正常退出 */
exit(0);
}
else /* 父程序 */
{
/* 迴圈測試子程序是否退出 */
do
{
/* 呼叫 waitpid, 且父程序不阻塞 */
pr = waitpid(pc, NULL, WNOHANG);
/* 若父程序還未退出,則父程序暫停 1s */
if(pr == 0)
{
printf("The child process has not exited \n");
sleep(1);
}
} while (pr == 0);
/* 若發現子程序退出,列印相關情況 */
if (pr == pc)
{
printf("Get child exit code: %d\n", pr);
}
else
{
printf("Some error occured.\n");
}
}
}
16.嵌入式Linux系統中,什麼是守護程序?如何編寫守護程序?p217
守護程序:是linux中的後臺服務程序,獨立於控制終端並且週期性執行某種任務或者等待處理某些事件。守護程序常常系統載入時啟動,在系統關閉時中止。
編寫守護程序:
a) 建立子程序,父程序退出
b) 子程序建立新會話
c) 改變當前的目錄為根目錄
d) 重設檔案許可權掩碼
e) 關閉檔案描述符
17.Linux中。兩種管道的各自的特點。p234
無名管道:
a) 只能用於具有親緣關係的程序間通訊。
b) 半雙工的通訊模式,具有固定的讀端和寫端。
c) 看成是特殊的檔案,只存在於核心的記憶體空間中。
有名管道:
a) 互不相干的程序間通訊
b) 可以通過路徑名支出,並且在檔案系統中是可見的
18.建立管道,然後父程序使用fork()函式建立子程序,父程序關閉讀描述符,子程序關閉寫描述符,父子程序通過管道通訊(通訊內容自行定義)。p236
/* pipe.c */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdilb.h>
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
pid_t pid;
int pipe_fd[2];
char buf[MAX_DATA_LEN];
const char data[] = "Pipe Test Program";
int real_read, real_write;
memset((void *) buf, 0, sizeof(buf));
/* 建立管道 */
if(pipe(pipe_fd) < 0)
{
printf("pipe create error\n");
exit(1);
}
/* 建立一個子程序 */
if((pid = fork()) == 0)
{
/* 子程序關閉描述符,並通過子程序暫停 1s 等待父程序已關閉相應的讀描述符 */
close(pipe_fd[1]);
sleep(DELAY_TIME * 3);
/* 子程序讀取管道內容 */
if((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
{
printf("%d bytes read from the pipe is '%'\n", real_read, buf);
}
/* 關閉子程序讀描述符 */
close(pipe_fd[0]);
exit(0);
}
else if (pid > 0)
{
/* 父程序關閉描述符,並通過使父程序暫停 1s 等待子程序已關閉相應的寫描述符 */
close(pipe_fd[0]);
sleep(DELAY_TIME);
if((real_write = write(pipe_fd[1], data, strlen(data))) != -1)
{
printf("Parent wrote %d bytes : '%s'\n", real_write, data);
}
/* 關閉父程序寫描述符 */
close(pipe_fd[1]);
/* 收集子程序退出資訊 */
waitpid(pid, NULL, 0);
exit(0);
}
}
19.Linux系統中訊號的生命週期,對訊號的處理方式。p243
訊號的生命週期可以分為三個階段,三個階段由四個重要事件刻畫:訊號產生、訊號在程序中註冊、訊號在程序中登出、執行訊號處理函式。相鄰兩個事件的時間間隔構成訊號生命週期的一個階段。
生命週期:
-
核心程序(訊號的產生)
-
使用者程序(訊號註冊,訊號登出)
-
訊號處理
處理方式:
a) 忽略訊號:不做任何處理,有兩個訊號不能忽略,即SIGKILL和SIGSTOP。
b) 捕捉訊號:訊號發生時執行相應的自定義處理函式。
c) 執行預設操作:Linux對每種訊號都規定了預設操作。
20.編寫程式,父子程序之間通過訊號量實現同步,子程序睡眠3秒後打印出提示資訊,然後父程序才能輸出資訊。子程序呼叫raise()函式暫停,父程序給子程序傳送SIGKILL訊號後等待子程序終止,子程序終止後,父程序退出。p245
21.Linux系統訊號量的使用步驟。p255
- 建立訊號量或獲取在系統已存在的訊號量。(此時需要呼叫 semget() 函式。不同程序通過使用同一個訊號量鍵值來獲取同一個訊號量)
- 初始化訊號量。(此時使用semctl() 函式的 SETVAL 操作)
- 進行訊號量的 PV 操作。(呼叫 semop() 函式)
- 如果不需要訊號量,則從系統中刪除它。(使用 semclt() 函式的 IPC——RMID 操作)
22.Linux系統中什麼是共享記憶體?簡述實現共享記憶體的兩個步驟。p260
可以說,共享記憶體是一種最為高效的程序間通訊方式。核心留出一片記憶體,由需要訪問的程序對映到自己的私有地址空間,使程序可以直接讀取這一記憶體區,大大提高了效率。
步驟:
- 建立共享記憶體。(shmget())
- 對映共享記憶體。(shmat())
23.Linux系統訊息佇列的使用步驟。p266
- 建立或開啟訊息佇列。(msgget())
- 新增訊息。(msgsnd())
- 讀取訊息。(msgrcv())
- 控制訊息佇列。(msgctl())
24.使用fork()函式建立一個子程序,父程序返回子程序的PID,宣告自己是父程序,子程序返回0值並且宣告自己是子程序。
25.訊號量程式設計。
26.共享記憶體程式設計。p262
/*
1. 首先建立一個共享記憶體區(鍵值為 IPC_PRIVATE), 之後建立子程序,在父子程序
中將共享記憶體分別對映到各自的程序地址空間中
2. 父程序等待使用者輸入,然後將使用者輸入的字串寫入到共享記憶體中,之後往共享內
存內部的頭部寫入 "WROTE" 字串,表示父程序已成功寫入資料
3. 子程序一直等到共享記憶體的頭部字串為 "WROTE", 然後將共享記憶體的有效資料(父
程序中輸入的資料)在螢幕上列印。
4. 父子兩個程序完成以上工作後,分別解除與共享記憶體的對映關係
5. 最後,在子程序中刪除共享記憶體。
/*
/* shmem.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 2048
int main()
{
pid_t pid;
int shmid;
char *shm_addr;
char flag[] = "WORTE";
char *buff;
/* 建立共享記憶體 */
if((shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666)) < 0)
{
perror("shmget");
exit(1);
}
else
{
printf("Create shared-memory:%d\\n", shmid);
}
/* 顯示共享記憶體情況 */
system("ipcs -m");
pid = fork();
if(pid == -1)
{
perror("fork");
exit(1);
}
else if(pid == 0) /* 子程序處理 */
{
/* 對映共享記憶體 */
if((shm_addr = shmat(shmid, 0, 0)) == (void*)-1)
{
perror("Child:shmat");
exit(1);
}
else
{
printf("Child:Attach shared-memory:%p\\n", shm_addr);
}
system("ipcs -m");
/* 通過檢查在共享記憶體的頭部是否標誌字串 “WROTE"來確認父程序已經向記憶體中寫入有效資料 */
while(strncmp(shm_addr, flag, strlen(flag)))
{
printf("Child:Wait for emable data...\\n");
sleep(5);
}
/* 獲取共享記憶體的有效資料並顯示 */
strcpy(buff, shm_addr + strlen(flag));
printf("Child:shared_memory:%s\\n", buff);
/* 解除共享記憶體對映 */
if((shmdt(shm_addr)) < 0)
{
perror("shmdt");
exit(1);
}
else
{
printf("Child:Deattach shared-memory\\n");
}
system("ipcs -m");
/* 刪除共享記憶體 */
if(shmctl(shmid, IPC_RMID, NULL) == -1)
{
perror("Child:shmctl(IPC_RMID)\\n");
exit(1);
}
else
{
printf("Delete shared-memory\\n");
}
system("ipcs -m");
}
else /* 父程序處理 */
{
/* 對映共享記憶體 */
if((shm_addr = shmat(shmid, 0, 0)) == (void*)-1)
{
perror("Parent: shmat");
exit(1);
}
else
{
printf("Parent: Attach shared-memory:%p\\n", shm_addr);
}
sleep(1);
printf("\\nInput some string:\\n");
fgets(buff, BUFFER_SIZE, stdin);
strncpy(shm_addr + strlen(flag), buff, strlen(buff));
strncpy(shm_addr, flag, strlen(flag));
/* 解除共享記憶體的對映 */
if((shmdt(shm_addr)) < 0)
{
perror("Parent: shmdt");
exit(1);
}
else
{
printf("Parent: Deattach shared-memory\\n");
}
system("ipcs -m");
waitpid(pid, NULL, 0);
printf("Finished\\n");
}
exit(0);
}