一,進程狀態和進程關係
1、進程的5種狀態
進程狀態 | 解釋 |
---|---|
就緒態 | 這個進程當前所有運行條件就緒,只要得到了CPU時間就能直接運行 |
運行態 | 就緒態時得到了CPU就進入運行態開始運行 |
殭屍態 | 進程已經結束但是父進程還沒來得及回收 |
等待態(淺度睡眠&深度睡眠) | 進程在等待某種條件,條件成熟後可進入就緒態。等待態下就算你給他CPU調度進程也無法執行。淺度睡眠等待時進程可以被(信號)喚醒,而深度睡眠等待時不能被喚醒只能等待的條件到了才能結束睡眠狀態 |
暫停態 | 暫停並不是進程的終止,只是被被人(信號)暫停了,還可以恢復的 |
2、進程各種狀態之間的轉換圖
3、system函數簡介
(1)system函數 = fork+exec
(2)原子操作。原子操作意思就是整個操作一旦開始就會不被打斷的執行完。原子操作的好處就是不會被人打斷(不會引來競爭狀態),壞處是自己單獨連續佔用CPU時間太長影響系統整體實時性,因此應該儘量避免不必要的原子操作,就算不得不原子操作也應該儘量原子操作的時間縮短。
4、進程關係
(1)無關係
(2)父子進程關係
(3)進程組:(group)由若干進程構成一個進程組
(4)會話:(session)會話就是進程組的組
二,守護進程
1.守護進程的引入
1、進程有關命令:進程查看命令ps,殺死進程命令kill
(1)ps -ajx 偏向顯示各種有關的ID號
(2)==ps -aux ==偏向顯示進程各種佔用資源
(3)kill -信號編號 進程ID
,向一個進程發送一個信號
(4)kill -9 xxx
,將向xxx這個進程發送9號信號,也就是要結束進程
2、守護進程
(1)daemon,表示守護進程,簡稱爲d(進程名後面帶d的一般就是守護進程)
(2)長期運行(一般是開機運行直到關機時關閉)
(3)與控制檯脫離(普通進程都和運行該進程的控制檯相綁定,表現爲如果終端被強制關閉了則這個終端中運行的所有進程都被會關閉)
(4)服務器(Server),服務器程序就是一個一直在運行的程序,可以給我們提供某種服務(譬如nfs服務器給我們提供nfs通信方式),當我們程序需要這種服務時我們可以調用服務器程序(和服務器程序通信以得到服務器程序的幫助)來進程這種服務操作。服務器程序一般都實現爲守護進程。
3、常見守護進程
(1)syslogd,系統日誌守護進程,提供syslog功能。
(2)cron,cron進程用來實現操作系統的時間管理,linux中實現定時執行程序的功能就要用到cron。
2.編寫簡單守護進程
1、任何一個進程都可以將自己實現成守護進程
2、create_daemon函數要素
(1)子進程等待父進程退出
(2)子進程使用setsid創建新的會話期,脫離控制檯
(3)調用chdir將當前工作目錄設置爲/
(4)umask設置爲0以取消任何文件權限屏蔽
(5)關閉所有文件描述符
(6)將0、1、2定位到/dev/null(類似於回收站)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void create_daemon(void);
int main(void)
{
create_daemon();
while (1)
{
printf("I am running.\n");
sleep(1);
}
return 0;
}
// 函數作用就是把調用該函數的進程變成一個守護進程
void create_daemon(void)
{
pid_t pid = 0;
pid = fork();
if (pid < 0)
{
perror("fork");
exit(-1);
}
if (pid > 0)
{
exit(0); // 父進程直接退出
}
// 執行到這裏就是子進程
// setsid將當前進程設置爲一個新的會話期session,目的就是讓當前進程
// 脫離控制檯。
pid = setsid();
if (pid < 0)
{
perror("setsid");
exit(-1);
}
// 將當前進程工作目錄設置爲根目錄
chdir("/");
// umask設置爲0確保將來進程有最大的文件操作權限
umask(0);
// 關閉所有文件描述符
// 先要獲取當前系統中所允許打開的最大文件描述符數目
int cnt = sysconf(_SC_OPEN_MAX);
int i = 0;
for (i=0; i<cnt; i++)
{
close(i);
}
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
}
實驗現象:
3.使用syslog來記錄調試信息
1、openlog、syslog、closelog
2、編程實戰
(1)一般log信息都在操作系統的/var/log/messages這個文件中存儲着,但是ubuntu中是在/var/log/syslog文件中的。
#include <stdio.h>
#include <syslog.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
printf("my pid = %d.\n", getpid());
openlog("b.out", LOG_PID | LOG_CONS, LOG_USER);
syslog(LOG_INFO, "this is my log info.%d", 9527);
syslog(LOG_INFO, "this is another log info.");
syslog(LOG_INFO, "this is 3th log info.");
closelog();
}
3、用cat /var/log/syslog查看
4、syslog的工作原理
(1)操作系統中有一個守護進程syslogd(開機運行,關機時才結束),這個守護進程syslogd負責進行日誌文件的寫入和維護。
(2)syslogd是獨立於我們任意一個進程而運行的。我們當前進程和syslogd進程本來是沒有任何關係的,但是我們當前進程可以通過調用openlog打開一個和syslogd相連接的通道,然後通過syslog向syslogd發消息,然後由syslogd來將其寫入到日誌文件系統中。
(3)syslogd其實就是一個日誌文件系統的服務器進程,提供日誌服務。任何需要寫日誌的進程都可以通過openlog/syslog/closelog這三個函數來利用syslogd提供的日誌服務。這就是操作系統的服務式的設計。
4.讓程序不能被多次運行
1、問題
(1)因爲守護進程是長時間運行而不退出,因此./a.out執行一次就有一個進程,執行多次就有多個進程。
(2)這樣並不是我們想要的。我們守護進程一般都是服務器,服務器程序只要運行一個就夠了,多次同時運行並沒有意義甚至會帶來錯誤。
(3)因此我們希望我們的程序具有一個單例運行的功能。意思就是說當我們./a.out去運行程序時,如果當前還沒有這個程序的進程運行則運行之,如果之前已經有一個這個程序的進程在運行則本次運行直接退出(提示程序已經在運行)。
2、實現方法:
(1)最常用的一種方法就是:用一個文件的存在與否來做標誌。具體做法是程序在執行之初去判斷一個特定的文件是否存在,若存在則標明進程已經在運行,若不存在則標明進程沒有在運行。然後運行程序時去創建這個文件。當程序結束的時候去刪除這個文件即可。
(2)這個特定文件(自己定義的)要古怪一點,確保不會湊巧真的在電腦中存在的。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#define FILE "/var/aston_test_single"
void delete_file(void);
int main(void)
{
// 程序執行之初,先去判斷文件是否存在
int fd = -1;
fd = open(FILE, O_RDWR | O_TRUNC | O_CREAT | O_EXCL, 0664);
if (fd < 0)
{
if (errno == EEXIST)
{
printf("進程已經存在,並不要重複執行\n");
return -1;
}
}
atexit(delete_file); // 註冊進程清理函數
int i = 0;
for (i=0; i<10; i++)
{
printf("I am running...%d\n", i);
sleep(1);
}
return 0;
}
void delete_file(void)
{
remove(FILE);
}
三,進程間通信
1、爲什麼需要進程間通信
(1)進程間通信(IPC)指的是2個任意進程之間的通信。
(2)同一個進程在一個地址空間中,所以同一個進程的不同模塊(不同函數、不同文件)之間都是很簡單的(很多時候都是全局變量、也可以通過函數形參實參傳遞)
(3)2個不同的進程處於不同的地址空間,因此要互相通信很難。
2、什麼樣的程序設計需要進程間通信
(1)99%的程序是不需要考慮進程間通信的。因爲大部分程序都是單進程的(可以多線程)
(2)複雜、大型的程序,因爲設計的需要就必須被設計成多進程程序(我們整個程序就設計成多個進程同時工作來完成的模式),常見的如GUI、服務器。
(3)結論:IPC技術在一般中小型程序中用不到,在大型程序中才會用到。
linux內核提供多種進程間通信機制
1、管道(無名管道)
(1)管道通信的原理:內核維護的一塊內存,有讀端和寫端(管道是單向通信的)
(2)管道通信的方法:父進程創建管道後再fork子進程,子進程繼承父進程的管道fd
(3)管道通信的限制:只能在父子進程間通信、半雙工
(4)管道通信的函數:pipe、write、read、close
2、有名管道(fifo)
(1)有名管道的原理:實質也是內核維護的一塊內存,表現形式爲一個有名字的文件
(2)有名管道的使用方法:固定一個文件名,2個進程分別使用mkfifo創建fifo文件,然後分別open打開獲取到fd,然後一個讀一個寫
(3)管道通信限制:半雙工(注意不限父子進程,任意2個進程都可)
(4)管道通信的函數:mkfifo、open、write、read、close
3、消息隊列
(1)本質上是一個隊列,隊列可以理解爲(內核維護的一個)FIFO
(2)工作時A和B2個進程進行通信,A向隊列中放入消息,B從隊列中讀出消息。
3、信號量
(1)實質就是個計數器(其實就是一個可以用來計數的變量,可以理解爲int a)
(2)通過計數值來提供互斥和同步
4、共享內存
(1)大片內存直接映射
(2)類似於LCD顯示時的顯存用法
5、剩餘的2類IPC
(1)信號
(2)Unix域套接字 : socket