嵌入式Linux C編程 05

6.1 Linux進程概述linux

  • 交互進程:
  • 批處理進程:
  • 守護進程:

內核把進程存放在任務隊列(task list)的雙向循環鏈表中,其中鏈表的每一項都是類型爲task_struct,稱爲進程描述符的結構,定義在<include/linux/sched.h>中。數組

task_struct結構中最重要的兩個域:state 和 pid異步

1)進程狀態函數

  • 運行(TASK_RUNNING):包括就緒和運行,指進程隨時能夠被調度運行或正在運行
  • 可中斷(TASK_INTERRUPTIBLE):進程中止運行,直到知足繼續運行條件
  • 不可中斷(TASK_UNINTERRUPTIBLE):中止運行,知足條件,也不會立刻被激活。
  • 僵死(TASK_ZOMBIE):進程運行結束,等待父進程銷燬
  • 中止(TASK_STOPPED):進程中止運行,當進程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信號,就會中止

2)任務標識post

內核經過惟一的進程標識值PID來識別每一個進程。PID是一個非負數,實際是一個短整型數據,最大32768。能夠查看/proc/sys/kernel/pid_max來肯定。spa

進程的建立、執行和終止操作系統

fork() 和 exec()線程

fork()經過複製當前進程的內容建立一個子進程,子進程與父進程的區別僅僅在於不一樣的PID、PPID和其餘一些資源。指針

寫時複製技術。code

exec()函數負責讀取可執行文件並將其載入地址空間開始運行。

6.1.二、進程的調度

優先級在0 ~ MAX_PRIO-1之間,數值越低優先級越高。

實時程序的優先級範圍在0 ~ MAX_RT_PRIO-1,通常進程的優先級在MAX_RT_PRIO ~ MAX_PRIO-1之間。

內核中默認配置:優先級0 ~ 139,實時進程佔用0 ~ 99,通常進程100 ~ 139

6.二、Linux進程控制相關API

一、fork函數

執行一次,返回兩個值。建立一個新進程,稱爲子進程。

父進程返回子進程的進程號,子進程返回0.

#include <sys/types.h> // 提供類型pid_t的定義
#include <unistd.h>

pid_t for(void);

// 0:子進程
// 子進程ID(大於0的整數):父進程
// -1:出錯
int result = fork();

if (result == -1)
{
    exit(-1);
} else if (result ==0) {
    /*子進程相關語句*/
} else {
    /*父進程相關語句*/
}

二、exec函數族

取代原調用進程的數據段、代碼段和堆棧段。

#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execv(const char *path, char * const argv[]);

int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);

int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char * const argv[]);

// 出錯:-1

前4個完整路徑,後2個文件名,從PATH查找。

l(list):逐個列舉,char * arg

v(vector):指針數組,*const argv[]

必須以NULL結尾。

三、exit 和 _exit

exit檢查進程中文件的打開狀況,把文件緩衝區中的內容寫回文件。

_exit直接將進程關閉,緩衝區中的數據會丟失。

exit:
#include <stdlib.h>
_exit:
#include <unistd.h>

void exit/_exit(int status);

// 0 標識正常

四、wait 和 waitpid

wait函數可使父進程阻塞。直到任意一個子進程結束或者父進程接到了一個指定的信號爲止。若是該父進程沒有子進程或者他的子進程已經結束,則wait函數會當即返回。

wait是waitpid的一個特例。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
pid_t waitpid(pid_t pid,
              int *status,
              int options);


// 成功:子進程的進程號 或 0(調用成功但子進程還未退出)
// 失敗:-1

status若爲空,則不保存子進程的退出狀態。

五、避免僵死進程實例

當一個進程已經終止,可是其父進程還沒有對其進行回收的進程被稱爲僵死進程。

爲了不,一種辦法是父進程調用wait/waitpid等待子進程結束,可是在子進程結束前父進程會一直阻塞,不能作任何事情。

另外一種更好的方法就是調用兩次fork函數。

6.三、進程間通訊

System V IPC單機,Socket

  • Unix進程間通訊(IPC)方式包括管道、FIFO、信號
  • System V進程間通訊(IPC)包括System V消息隊列、System V信號燈、System V共享內存區
  • Posix 進程間通訊(IPC)包括Posix消息隊列、Posix信號燈、Posix共享內存區

Linux中使用較多的進程間通訊方式:

  1. 管道(Pipe)及有名管道(named pipe)。管道可用於有親緣關係進程間的通訊;有名管道除具備管道的功能外,還容許無親緣關係進程之間進行通訊。
  2. 信號(Signal)是在軟件層次上對中斷機制的一種模擬。
  3. 消息隊列是消息的連接表,包括Posix消息隊列和System V消息隊列。它克服了前兩種通訊方式種信息量有限的缺點,具備寫權限的進程能夠向消息隊列中按照必定的規則添加新消息;對消息隊列有讀權限的進程則能夠從消息隊列中讀取消息。
  4. 共享內存,是一種最高效的進程間通訊方式。多個進程訪問同一塊內存空間。須要依靠某種同步機制,入互斥鎖和信號量等。
  5. 信號量,主要做爲進程間和同一進程中不一樣線程之間的同步手段。
  6. 套接字(Socket)一種更爲通用的進程間通訊機制,可用於不一樣主機之間的進程間通訊。

6.3.一、管道通訊

一、無名管道(PIPE)

半雙工、具備固定的讀端和寫端。

一種特殊的文件,存在於內存中。

二、有名管道(FIFO)

可以使互不相關的兩個進程實現彼此通訊。

能夠經過路徑名指出,而且在文件系統中是可見的。

FIFO嚴格地遵循先進先出規則,對管道及FIFO的讀老是從開始處返回數據,對它們的寫則把數據添加到末尾。

有名管道的建立

mkfifo函數,相似於open操做,能夠指定管道的路徑和讀/寫權限。

「mknod 管道名 p」命令來建立有名管道。

管道建立成功後,就可使用open、read、write函數了。與普通文件不一樣的是阻塞問題。

普通文件不阻塞,管道中有阻塞的可能。O_NONBLOCK

#include <sys/types.h>
#include <sys/state.h>

int mkfifo(const char *filename,
           mode_t mode);

// 成功:0
// 出錯:-1

mkfifo("myfifo", 0666);
mkfifo("myfifo", O_RDWR | O_NONBLOCK);

// mkfifo僅建立了管道,並無打開管道。

6.3.二、信號通訊

信號是在軟件層次上對中斷機制的一種模擬。

信號是異步的。惟一的異步通訊方式。

兩個來源:硬件來源和軟件來源。

響應:

  1. 忽略信息:可是SIGKILL和SIGSTOP不能忽略
  2. 捕捉信息:定義信號處理函數
  3. 執行默認操做:

信號的處理包括信號的發送、捕獲以及信號的處理,

  • 發送信號的函數:kill、raise
  • 設置信號處理的函數:signal
  • 其餘相關的函數:alarm、pause

kill 和 raise

raise函數只容許進程向自身發送信號。

#include <signal.h>
#include <sys/types.h>

int kill(pid_t pid,
         int sig);

int raise(int sig);

// 成功:0
// 出錯:-1

raise(SIGSTOP);
kill(pis, SIGKILL);

alarm 和 pause

設置一個定時器,到時間時發送SIGALARM信號。一個進程只能有一個鬧鐘時間,新值代替以前的值。

pause函數是用於將調用進程睡眠直到捕捉到信號爲止。一般能夠用來讓進程等待信號到達。

#include <unistd.h>

unsigned int alarm(unsigned int seconds)
int pause(void);

// 成功:上一個時鐘的剩餘時間,不然返回0
// 出錯:-1,而且把error值設爲EINTR

ret = alarm(5);
pause();

signal

使用signal函數時,只須要把處理的信號和處理函數列出便可。主要用於前32種非實時信號的處理,不支持信號傳遞信息。

#include <signal.h>

void (*signal(int signum,  /*指定信號*/
              void (*handler))(int)))(int)      /*對信號的處理*/


// 函數原型
typedef void func(int);
func *signal(int, func *);


// 成功:之前的信號處理配置
// 出錯:-1

void my_func(int signo)
{
    ...
}

signal(SIGINT, my_func);
signal(SIGQUIT, my_func);

6.3.三、共享內存

共享內存容許多個進程共享一個給定的內存區域。數據不須要在多個進程之間複製,因此是最高效的一種通訊方式。

使用共享內存的關鍵在於如何在多個進程之間對一給定的存儲區進行同步訪問。

一般信號量被用來實現對共享內存的訪問。

實現步驟:

  1. 建立共享內存,shmget函數。從內存中得到一段共享內存區域。
  2. 映射共享內存,把這段建立的內存映射到具體的進程空間去。shmat函數。
  3. 撤銷映射的操做。函數爲shmdt
  4. 刪除共享內存對象,函數爲shmctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key,    /*IPC_PRIVATE*/
           int size,     /*共享內存區大小*/
           int shmflg);  /*通open函數的權限位,也能夠用8進製表示法*/

// 成功:共享內存段標識符
// 出錯:-1

void *shmat(int shmid,    /*要映射的共享內存區標識符*/
            const void *shmaddr,    /*將共享內存映射到指定地址(0:由操做系統決定映射地址)*/
            int shmflg);    /*SHM_RDONLY:共享內存只讀。默認0,可讀寫*/

// 成功:被映射後的地址
// 出錯:-1

int shmdt(const void *shmaddr);    /*被映射的共享內存的地址*/

// 成功:0
// 出錯:-1

int shmctl(int shmid,    /*共享內存的標識符*/
           int cmd,      /*要執行的操做的命令字*/
           struct shmid_ds *buf);    /*獲取/設置共享內存屬性的結構體的地址*/

// 成功:0
// 出錯:-1
/*建立共享內存*/
int shmid = shmget(IPC_PRIVATE, BUFSZ, 0666);

/*映射共享內存*/
char *shmaddr = (char *)shmat(shmid, 0, 0);

/*查看共享內存對象*/
system("ipcs -m");

/*取消共享內存映射*/
shmdt(shmaddr);

/*刪除共享內存*/
shmctl(shmid, IPC_RMID, NULL);

6.3.四、消息隊列

消息隊列由內核維護。

  1. 建立或打開消息隊列。使用函數msgget,這裏建立的消息隊列的數量會受到系統消息隊列數量的限制。
  2. 添加消息隊列。使用函數msgsnd,它把消息添加到已打開的消息隊列末尾。
  3. 讀取消息隊列。使用函數msgrcv,它把消息從消息隊列中取走,與FIFO不一樣的是,這裏能夠指定取走某一類型的消息。
  4. 控制消息隊列。使用函數msgctl,能夠完成多項功能。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key,    /*返回新的或已有隊列的隊列ID、IPC_PRIVATE*/
           int flag);

// 成功:消息隊列ID
// 出錯:-1

int msgsnd(int msqid,    /*消息隊列的隊列ID*/
           const void *prt,    /*指向消息結構的指針*/
           size_t size,      /*消息的大小,不包括消息類型*/
           int flag);    /*IPC_NOWAIT:若消息並無當即發送而調用進程會當即執行返回。0:阻塞直到消息發送完成爲止*/


// prt消息的結構用戶本身定義,第一個成員的類型必須爲long
struct msgbuf
{
    long mtype;         /*消息類型*/
    char mtext[LEN];    /*消息正文*/
    ...
}

// 成功:0
// 出錯:-1


int msgrcv(int msqid,    /*消息隊列的隊列ID*/
           const void *msgp,    /*接收消息緩衝區*/
           int size,    /*消息的字節數*/
           long msgtype,   /*接收的消息類型*/
           int flag);    /*標誌位*/

// 成功:接收到的消息的字節數
// 失敗:-1

int msgctl(int msqid,
           int cmd,
           struct msqid_ds *buf);    /*消息隊列緩衝區*/

// 成功:0
// 失敗:-1

typedef struct
{
    long mtype;
    ...
} MSG;
MSG msg;

/*生成消息隊列須要的key*/
key_t key = ftok(".", 'q');

/*建立消息隊列*/
int msqid = msgget(key, IPC_CREATE|0666);

/*添加消息到消息隊列*/
msgsnd(msqid, &msg, sizeof(msg)-sizeof(long), 0);

/*讀取消息隊列中第一個消息*/
msgrcv(msqid, &msg, sizeof(msg)-sizeof(long), 0, 0);

/*從系統內核中移走消息隊列*/
msgctl(msqid, IPC_RMID, NULL);

6.4 線程相關API

pthread線程庫,遵循POSIX規範,具備很好的可移植性。

一、線程建立和退出

  1. 建立線程。指定線程所要執行的函數,使用的函數是pthread_create。
  2. 調用相關線程函數。在線程建立以後,就開始運行相關的線程函數。
  3. 線程退出。在線程調用函數運行完以後,該線程也就自動結束了。另外一種退出線程的方法是使用函數pthread_exit,這是線程的主動行爲。
  4. 線程資源回收。因爲一個進程中的多個線程是共享數據段的,所以一般在線程退出以後,退出線程所佔用的資源並不會隨着線程的終止而獲得釋放。pthread_join函數,pthread_join能夠擁有將當前主線程掛起,等待子線程的結束。調用它的線程將一直阻塞直到指定的線程結束爲止。
#include <pthread.h>

int pthread_create(pthread_t *thread,    /*線程對象*/
                   pthread_attr_t *attr,    /*線程屬性設置,默認爲NULL,可使用其餘函數來設置*/
                   void *(*start_routine)(void *),    /*線程執行的函數*/
                   void *arg);    /*傳遞給start_routine的參數*/

// 成功:0
// 出錯:-1

void pthread_exit(void *retval);    /*線程的返回值,可由其餘函數如pthread_join來檢測獲取*/

int pthread_join(pthread_t thread,
                 void ** thread_return);    /*用戶定義的指針,用來存放被子線程的返回值(不爲NULL時)*/

// 成功:0
// 出錯:-1

二、mutex線程訪問控制

線程共享進程的資源和地址空間。POSIX中線程同步的方法主要有互斥鎖和信號量。

  • 互斥鎖初始化:pthread_mutex_init
  • 互斥鎖上鎖:pthread_mutex_lock
  • 互斥鎖判斷上鎖:pthread_mutex_trylocks
  • 互斥鎖解鎖:pthread_mutex_unlock
  • 刪除互斥鎖:pthread_mutex_destroy
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex,    /*指向互斥鎖對象的指針*/
                       const pthread_mutexattr_t *mutexattr);    /*互斥鎖的屬性*/

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 成功:0
// 出錯:-1

mutexattr 若是爲NULL表示按默認屬性建立互斥鎖。

三、信號量線程控制

信號量也就是操做系統中的PV原語。信號量本質上是一個非負的整數計數器,被用來控制對公共資源的訪問。

PV原語是對整數計數器信號量sem的操做。一次P操做使sem減一,一次V操做使sem加一。進程(或線程)根據信號量的值來判斷是否對公共資源具備訪問權限。

當信號量sem的值大於0時,該進程(或線程)具備公共資源的訪問權限;相反,當信號量sem的值等於0時,該進程(或線程)就將阻塞直到信號量sem的值大於0爲止。

PV原語主要用於進程或線程間的同步和互斥這兩種典型狀況。若用於互斥,幾個進程(或線程)每每只設置一個信號量sem。

當信號量用於同步操做時,每每會設置多個信號量,並安排不一樣的初始值來實現他們之間的順序執行。

Linux實現了POSIX的無名信號量,主要用於線程間的互斥同步。

  • sem_init用於建立一個信號量,並能初始化它的值。
  • sem_wait 和 sem_trywait 至關於P操做,他們都能將信號量的值減一,兩種區別在於若信號量等於0時,sem_wait將會阻塞線程,而sem_trywait則會當即返回。
  • sem_post 至關於V操做,它將信號量的值加一同時發出信號喚醒等待的線程。
  • sem_getvalue用於獲得信號量的值
  • sem_destroy用於刪除信號量。
#include <semaphore.h>

int sem_init(sem_t *sem,    /*信號量*/
             int pshated,    /*決定信號量可否在幾個進程間共享。因爲目前Linux尚未實現進程間共享信號量,因此這個值只可以取0*/
             unsigend int value);    /*信號量初始化值*/
#include <pthread.h>

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

int sem_post(sem_t *sem);

int sem_getvalue(sem_t *sem);

int sem_destroy(sem_t *sem);

// 成功:0
// 出錯:-1
相關文章
相關標籤/搜索