《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发
進程是系統資源的最小單元,很重要。
?
7.1 linux進程的基本概念
- 定義:一個程序的一次執行過程,同時也是資源分配的最小單元。程序是靜態的,而進程是動態的。
- 進程控制塊:linux系統用進程控制塊描述進程,task_struct,在 include/linux/sched.h
- PID,進程唯一標識;PPID,父進程的PID
?
進程相關的還有用戶和用戶組標識、進程時間、資源利用的函數,參考APUE.
- 進程運行的狀態
?
- ?進程的結構:主要包含數據段、代碼段、堆棧段
- 進程模型:用戶態和內核態
- linux啟動進程的兩種方式:?
- 手動啟動:前臺啟動,最常見的是在終端里輸入命令,該命令的執行就是一個進程; 后臺啟動,用&,不影響終端,在終端后面默默運行。
- 調度啟動:制定時間運行,有一些命令,at命令可以在指定時刻執行相關進程;cron命令可以自動周期性的執行相關進程。
常用進程相關命令:
?
7.2 linux進程編程
? 7.2.1 fork
- 從已創建的進程中創建一個新的進程,新進程叫子進程,原進程叫父進程。
- 子進程是父進程的復制,集成父進程的絕大部分內容,包括:整個進程的地址空間、進程上下文、代碼段、進程堆棧、內存信息、打開的文件描述符、信號控制設置、進程優先級、進程組號、當前工作目錄、根目錄、資源限制、控制終端等;
- 子進程獨有的部分:進程號、資源使用、計時器等
- fork的大體流程:父進程執行fork——>父進程復制出一個子進程——>父子進程從fork函數返回開始分別在兩個地址空間同時運行,通過返回值區分父子進程。
- fork的開銷比較大,復制這么多東西,想想都覺得累。有些unix系統創建了vfork()函數,vfork創建新進程時,不產生父進程的副本,允許父子進程訪問相同的物理內存而偽裝成拷貝父進程。但是子進程需要改變內存時(寫),才復制父進程,這就是“寫時復制”,linux的fork就是調用vfork函數實現的。
參數:
返回值:
0:子進程
>0:子進程pid,父進程
-1:出錯
注意事項:
fork調用一次,就創建一個子進程,所以if、else if等分支處理時,不能多次調用,應該調用一次,記下返回值,然后if else等使用此返回值。
?
?
/* 7-1,fork */#include <stdio.h> // printf #include <stdlib.h> // exit #include <unistd.h> #include <fcntl.h> // open,fcntl #include <sys/types.h>int main(int args, char *argv[]) {pid_t pid_rtn;pid_rtn = fork(); if( pid_rtn == 0 ){printf("\r\nChild thread, pid %d, ppid %d",getpid(),getppid());}else if( pid_rtn > 0 ){sleep(1); // 如果父進程先結束,則子進程會被init進程收養,用getppid時獲取的就不是創建他的父進程了printf("\r\nParent thread, pid %d, child %d",getpid(),pid_rtn);}else{ printf("\r\nfork err.");}printf("\r\nfinish.\r\n");exit(0); }$ ./example
Child thread, pid 4087, ppid 4086
finish.
Parent thread, pid 4086, child 4087
finish.
如果父進程不睡1s,則運行結果如下:
$ ./example
Parent thread, pid 4121, child 4122
finish.
$
Child thread, pid 4122, ppid 2326
finish.
$ ps -A
*
2326 ???????? 00:00:01 upstart // upstart就是ubuntu的init進程,對于父進程已經結束的子進程,會被這個進程“收養”
*
7.2.2 exec函數族
- 執行另一個程序,除了pid外,其他全被新的進程替換
- 一般先fork,然后exec執行想執行的程序
- exec注意事項:一定要加上錯誤判斷語句,exec很容易出錯,常見錯誤有:
- 找不到文件或路徑,errno=ENOENT
- argv和envp忘記用NULL結束,errno=EFAULT;
- ? ? ? 沒有對應可執行文件的運行權限,errno=EACCES
- 6個函數中,真正的系統調用只有execve,其他都是庫函數,通過調用execve實現
int execl(const char *path, const char *arg, ...) // list
int execv(const char *path, char *const argv[]) // vector
int execle(const char *path, const char *arg, ..., char *const envp[]) // enviroment
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[])
參數:
path和file:查找方式,path完整的文件目錄路徑;file(p結尾的函數)只給出文件名,系統按照環境變量PATH指定的路徑查找;
arg...和argv[]:參數傳遞方式,list和vector,這些參數必須以NULL結尾,以可執行程序命令本身開頭;
envp:環境變量,e結尾,指定要執行的進程所使用的環境變量
返回值:-1 出錯
?
?
/* 7-2,exec */
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <fcntl.h> // open,fcntl
#include <sys/types.h>
int main(int args, char *argv[])
{
pid_t pid_rtn;
pid_rtn = fork();
if( pid_rtn == 0 )
{
execlp("ps","ps","-A","NULL"); // 第一個ps是文件名,后面是參數,輸入參數時,第一個參數是要運行的程序,跟在shell里輸入是一樣的,注意要用NULL結尾
}
printf("\r\nfinish.\r\n");
exit(0);
}
?
相當于執行了“ps -A”命令,運行結果:
PID TTY STAT TIME COMMAND
1 ? Ss 0:12 /sbin/init splash
2 ? S 0:00 [kthreadd]
......
?
?7.2.3 exit和_exit
- 兩個函數會停止所有操作,清除PCB等數據結構;
- 兩個函數有差別:
- _exit:直接停止運行, 清除進程使用的內存空間,清除內核中的數據結構;
- ? ? ?exit = “清理IO緩存”+_exit, 清理IO緩存,指檢查文件的打開情況,把文件緩沖區中的內容寫回文件。linux里有“緩沖IO”操作,例如printf、fgets等,使用緩沖區,類似cache。
- ?只使用exit()就可以了
進程調用exit()和_exit()后不會立即退出,而是進入僵死zombie狀態,變成僵尸進程,僵尸進程只在進程列表里保留一個位置,記錄該進程的退出狀態等供其他進程收集(一般是父進程用wait收集)。
#include <unistd.h> // _exit#include <stdlib.h> // exit
void exit( int status );
void _exit( int status);
參數:
status 可以返回本進程(調用exit的進程)的退出狀態,一般0表示正常,其他數值表示出錯,進程非正常結束;
父進程用wait()系統調用接收子進程的返回值。
?
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
int main(int args, char *argv[])
{
printf("Start.\n");
printf("content in buffer.");
_exit(0);
}
$ ./example // 在緩沖區里就沒有了,因為_exit不刷緩沖區
Start.
?
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
int main(int args, char *argv[])
{
printf("Start.\n");
printf("content in buffer.");
exit(0);
}
$ ./example // 在緩沖區里的也刷出來了,exit干的
Start.
content in buffer.
?
【注意】
printf遇到“\n”換行符時自動從緩沖區中將記錄讀出?
7.2.4 wait和waitpid
- wait阻塞等待1個子進程結束,如果該進程在阻塞時接到了一個指定的信號,則阻塞也可能終止。如果沒有子進程或者子進程已經結束,則wait會立即返回。
- ? ?waitpid比wait功能豐富,可提供非阻塞、作業控制、指定待等待進程等功能
#include <sys/wait.h>
pid_t wait( int * status );
參數:
status:返回子進程的退出狀態和異常終止狀態,若為NULL,則不獲取。可以通過一些linux特定的宏來測試具體狀態信息。
【重要】:進程退出有正常退出(子進程exit或者return),此時的狀態記為“正常退出狀態”;還有異常退出的情況,例如被信號中斷等,這時的狀態記為“異常終止狀態”。status可以反映這兩種狀態?! ?/span>
union wait
{
int w_status;
struct
{
# if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int __w_termsig:7; /* Terminating signal. */
unsigned int __w_coredump:1; /* Set if dumped core. */
unsigned int __w_retcode:8; /* Return code if exited normally. */
unsigned int:16;
# endif
} __wait_terminated; // 正常退出和異常終止,格式
struct
{
# if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int __w_stopval:8; /* W_STOPPED if stopped. */
unsigned int __w_stopsig:8; /* Stopping signal. */
unsigned int:16;
# endif
} __wait_stopped; // 暫停,stop,格式
};
WIFEXITED(status),若為正常退出,則返回真。若為真,可用WEXITSTATUS(status)獲取exit返回的狀態;
WIFSIGNALED(status),若子程序為異常終止,則返回真(被信號終止),可用WTERMSIG(status)獲取子進程終止的信號編號;可用WCOREDUMP(status)檢查是否產生core文件,產生時為真;
WIFSTOPPED(status),如果子程序暫停,則為真,可通過WSTOPSIG(status),獲取使子程序暫停的信號編號
WIFCONTINUED(status),若暫停后又繼續的子進程返回狀態,則為真,僅用于waitpid。
#define WIFEXITED(status) (((status) & 0x7f)== 0)
#define?WIFSIGNALED(status) (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)
#define?WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
#define?WEXITSTATUS(status)(((status) & 0xff00) >> 8)
#define?WTERMSIG(status) ((status) & 0x7f)
#define?WSTOPSIG(status) (((status) & 0xff00) >> 8)
返回值:
成功:已結束運行(被等待的)的子進程的進程號
失敗:-1
pid_t waitpid( pid_t pid, int *status, int options );
參數:
pid: >0,等待進程ID=pid的子進程,不管別的;
=-1,等待任何一個子進程,與wait()作用一樣;
= 0,等待“組ID==調用進程組ID”的任一子進程;
<-1,等待“組ID==pid絕對值”的任一子進程
status:同wait()函數
options:sya
WNOHANG:不阻塞
WUNTRACED:若實現某支持作業控制,則由 pid 指定的任一子進程狀態已暫停,且其狀態自暫停以來還未報告過,則返回其狀態
0:同wait(),阻塞
返回值:
正常:已結束運行的子進程的進程號
使用WNOHANG且沒有子進程:0
調用出錯:-1
【注意】
1. 關于幾個測試退出狀態的特殊的宏
?
/* 7-4,waitpid */
#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <sys/wait.h>
int main(int args, char *argv[])
{
pid_t pid_fork,pid_wait;
int status;
pid_fork=fork();
if( pid_fork == 0 ) // child
{
sleep(5);
exit(0);
}
else if( pid_fork > 0 )
{
do
{
pid_wait = waitpid(pid_fork,&status,WNOHANG);
if( pid_wait != pid_fork )
printf("child thread %d is not over\r\n",pid_fork);
else
printf("child thread %d is over,status 0x%x\r\n",pid_fork,status);
sleep(1);
}
while(pid_wait!=pid_fork);
}
else
{
printf("fork err code %d.\r\n",pid_fork);
}
exit(0);
}
?
$ ./example
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is not over
child thread 7575 is over,status 0x0
?
$ ./example // 子進程exit(-1)時,waitpid的獲取的值是0xff00,高位是exit的返回值,需要用到宏了
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is not over
child thread 7605 is over,status 0xff00
?
?
? 7.3 守護進程
7.3.1 守護進程概念
守護進程,也叫deamon進程,是后臺服務進程;系統引導載入時啟動,系統關閉時終止,獨立于控制終端;常用于周期性的執行某種任務或等待處理某些事件。守護進程已d結尾,例如crond、lpd等。
控制終端:系統與用戶進行交流的界面稱為終端,每個從此終端開始運行的進程都會依賴這個終端,這個終端就是這些進程的控制終端。控制終端關閉時,相應的進程都會關閉。但是守護進程不受影響。
守護進程不受終端、用戶和其他變化的影響,直到系統關閉時才退出。
? ?7.3.2 編寫守護進程
?步驟:
??7.3.2.1?創建子進程,父進程退出
父進程退出后,子進程編程了孤兒進程,被init進程收養。 形式上做到了與控制終端的脫離。
!!!7.3.2.2 在子進程中創建新會話
先了解基本概念:進程組、會話組、會話期
進程組:一個或多個進程的集合,每個進程組都有一個組長進程,進程組ID=組長PID
會話組:一個或多個進程組的集合
會話期:通常一個會話開始于用戶登錄,終止與用戶退出,在此期間該用戶運行的所有進程都屬于這個會話期。
? ? ? ??
setsid():創建新的會話,并擔任該會話組的組長。調用后起到3個作用:
- 讓進程擺脫原會話的控制;
- 讓進程擺脫原進程組的控制
- 讓進程擺脫原控制終端的控制
#include <sys/types.h>
#include <unistd.h>
pid_t setsid( void );
返回值:
成功:該進程組ID
出錯:-1
?
7.3.2.3 改變當前目錄為根目錄
通常的做法是將守護進程的當前目錄設置為根目錄,用chdir()系統調用。
7.3.2.4 重設文件權限掩碼
umask(0),基本思路是給最大權限。
7.3.2.5 關閉文件描述符
父進程那繼承來的文件描述符,一般不用,浪費,關閉。 連基本的輸入輸出都沒用了,setsid時已經失去聯系了,可以關了。
/* 7-5,deamon */#include <stdio.h> // printf #include <stdlib.h> // exit #include <unistd.h> #include <sys/types.h> // pid_t #include <fcntl.h>int main(int args, char *argv[]) {pid_t pid_fork;int i;int fd;char buf[32]="The deamon info.\n";pid_fork = fork();if( pid_fork < 0 ){printf("fork err.\r\n");}else if( pid_fork > 0 ){exit(0);}// only child enter setsid(); chdir("/");umask(0);for( i=0;i<getdtablesize();i++ ) // 終端也關了,printf沒有效果了,需要用別的調試方法 close(i);if( fd=open("/tmp/log", O_RDWR|O_CREAT,0644) < 0 )printf("open file err\r\n"); while(1){write(fd,buf,sizeof(buf));sleep(1); }exit(0); }?
7.3.3 守護進程的出錯處理
printf不好使,咋辦?用linux提供的syslog服務,系統中有syslogd守護進程。不通版本linux的syslog日志文件的位置可能不通。
#include <syslog.h>
void openlog( char * ident, int options, int facility );
參數:
ident:向每個消息加入的字符串,通常為程序的名稱;
option:LOG_CONS,如果消息無法送到系統日志服務,則直接輸出到系統控制終端
LOG_NDELAY:立即打開系統日志服務的連接。在正常情況下,直接發送到第一條消息時才打開連接
LOG_PERROR:將消息也同時送到 stderr 上 LOG_PID:在每條消息中包含進程的 PID
facility: 指定程序發送的消息類型
LOG_AUTHPRIV:安全/授權信息
LOG_CRON:時間守護進程(cron 及 at)
LOG_DAEMON:其他系統守護進程
LOG_KERN:內核信息
LOG_LOCAL[0~7]:保留
LOG_LPR:行打印機子系統
LOG_MAIL:郵件子系統
LOG_NEWS:新聞子系統
LOG_SYSLOG:syslogd 內部所產生的信息函數傳入值
LOG_USER:一般使用者等級信息
LOG_UUCP:UUCP 子系統
void syslog(int priority, char *format, ...)
參數: priority ,指定消息的重要性, LOG_EMERG:系統無法使用
LOG_ALERT:需要立即采取措施
LOG_CRIT:有重要情況發生
LOG_ERR:有錯誤發生
LOG_WARNING:有警告發生
LOG_NOTICE:正常情況,但也是重要情況
LOG_INFO:信息消息
LOG_DEBUG:調試信息
format,同printf
void closelog( void )
?
?
轉載于:https://www.cnblogs.com/liuwanpeng/p/6605985.html
總結
以上是生活随笔為你收集整理的《嵌入式linux应用程序开发标准教程》笔记——7.进程控制开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: web开发中的长度单位(px,em,ex
- 下一篇: Linux使用imagemagick的c