操作系統與系統編程(2)——進程

 

目錄

進程:

進程狀態:

環境變量

進程原語:

進程間通信IPC:

進程間關係:

終端:

終端登錄:

進程組:

會話(session)

守護進程:

參考:


 

 

進程:

進程狀態:

Cpucpu基本由運算器 寄存器 控制器 譯碼器等構成,寄存器會讀入當前進程的機器碼,供給cpu執行,當進程a的運行時間資源耗盡,則a的處理器現場會備份在進程a的pcb棧區(內核區),當b調用完畢,寄存器內會恢復a的寄存器現場繼續執行進程a。

進程的狀態有:就緒 運行 掛起 停止等。進程狀態可以切換,當睡眠態的條件到達時就會進入就緒態。當系統進行調度,就緒態就可能進入運行態。運行態可能進入就緒態和停止態。

操作系統進行進程管理:進程管理利用時分複用的思想進行進程調度,最小調度單位爲時間片,早期時間片爲10ms。時間片劃分越來越小,調度越來越靈活。

環境變量

 環境表就是一個指針數組

https://images0.cnblogs.com/blog/382408/201410/032125056917830.png   https://img-blog.csdn.net/20131101130534390?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvVE9ERDkxMQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

環境表和環境字符串通常存放在進程存儲空間的頂部,刪除一個字符串很簡單,只要現在環境表中找到該指針,然後將所有後續指針都向環境首部順次移動一個位置。但是增加一個字符串或者修改一個現有的字符串就困難的多。環境表和環境字符串通常佔用的是進程地址空間的頂部,所以不能再向高地址方面擴展,同時也不能移動在它之下的各棧幀,所以它也不能向地地址擴展。

1.如果修改一個現有的name

新的value長度<=原有的,原串中寫,否則malloc新空間,複製,原環境表中name指向新空間。

2.如果加一個新name,調用malloc爲name=value分配空間,然後將該字符串複製到此空間。

a.第一次調用,malloc分配空間->原來環境表複製過來->指向name=value的新串指針加到最後->再加空指針->environ指向新指針表。

b.不是第一次,已經malloc過->realloc分配多一個空間->存入指向新的name=value的指針->再存nullptr。

每個進程都有一個獨立的環境表,初始的環境表繼承自父進程

主函數可以帶三個參數,第三個參數則是環境表,可以通過第三個參數獲取環境參數,也可以通過外部全局變量來定義環境表,extern char **environ。

設置的環境變量隻影響當前進程的,不爲全局的

環境變量操作函數

1.獲取環境變量內容--- getenv

 char * getenv(const char *name);

函數功能:取得環境變量內容

函數說明:getenv()用來取得參數name環境變量的內容。參數name爲環境變量的名稱,如果該變量存在則會返回指向該內容的指針。

環境變量的格式爲namevalue

返回值:成功則返回指向與 name 關聯的 value 的指針,失敗則返回NULL

2,改變或增加環境變量 ---putenv

int putenv(const char * string);

函數說明:putenv()用來改變或增加環境變量的內容

參數string的格式爲namevalue,如果該環境變量原先存在,則變量內容會依參數string改變,否則此參數內容會成爲新的環境變量

返回值:執行成功則返回0,有錯誤發生則返回-1。

3.改變或增加環境變量---setenv

int setenv(const char *name,const char * value,int overwrite);

函數功能:改變或增加環境變量

函數說明:setenv()用來改變或增加環境變量的內容。參數name爲環境變量名稱字符串。

value 變量內容,overwrite 用來決定是否要改變已存在的環境變量。

如果overwrite不爲0,該環境變量原已有內容,則原內容會被改爲value指的變量內容。

如果overwrite爲0,且該環境變量已有內容,則參數value會被忽略。

返回值:執行成功則返回0,有錯誤發生時返回-1。

4刪除環境變量---unsetenv

int unsetenv(const char *name);

函數功能:刪除 name 的定義,即使不存在這種定義也不算出錯  

返回值:成功返回0,出錯返回 -1

環境變量相關測試

 

/*************************************************************************
	> File Name: dm001_environ.c
	> Author: ma6174
	> Mail: [email protected]  
	> Created Time: 2018年09月07日 星期五 09時44分49秒
 ************************************************************************/

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	extern char**environ;//environ is a global var,must use extern
	char *name;
	int i = 0;
	for(;environ[i]!=NULL;i++)//get all the environ var
	{
		printf("%s\n",environ[i]);
	}
    
	if((name = getenv("PATH")) != NULL)//get var value based on var name
	{
		printf("PATH = %s\n",name);
	}

	//setenv 
	setenv("H_TEST","hello world",1);
	printf("%s\n",getenv("H_TEST"));
	unsetenv("H_TEST");//unsetenv
	return 0;
}

進程原語:

子進程複製父進程0-3G的用戶進程和內核的PCB,id號不同。

 

Exec族函數

 

殭屍進程:1,子進程結束而父進程還沒有回收子進程。2,父進程先於子進程結束,子進程運行。init進程領養子進程。

殭屍進程佔據內核資源而不幹活,尸位素餐,1可以在父進程中wait waitpid回收子進程狀態,或設置子進程的分離屬性。2,父進程先於子進程結束則1號進程進行領養。

vfork的基礎知識:

在實現寫時複製之前,Unix的設計者們就一直很關注在fork後立刻執行exec所造成的地址空間的浪費。BSD的開發者們在3.0的BSD系統中引入了vfork( )系統調用。

pid_t vfork(void);

除了子進程必須要立刻執行一次對exec的系統調用,或者調用_exit( )退出,對vfork( )的成功調用所產生的結果和fork( )是一樣的。vfork( )會掛起父進程直到子進程終止或者運行了一個新的可執行文件的映像。通過這樣的方式,vfork( )避免了地址空間的按頁複製。在這個過程中,父進程和子進程共享相同的地址空間和頁表項。實際上vfork( )只完成了一件事:複製內部的內核數據結構。因此,子進程也就不能修改地址空間中的任何內存。

vfork( )是一個歷史遺留產物,Linux本不應該實現它。需要注意的是,即使增加了寫時複製,vfork( )也要比fork( )快,因爲它沒有進行頁表項的複製。然而,寫時複製的出現減少了對於替換fork( )爭論。實際上,直到2.2.0內核,vfork( )只是一個封裝過的fork( )。因爲對vfork( )的需求要小於fork( ),所以vfork( )的這種實現方式是可行的。

forkvfork的區別:

1. fork( )的子進程拷貝父進程的數據段和代碼段vfork( )的子進程與父進程共享數據段

2. fork( )的父子進程的執行次序不確定;vfork( )保證子進程先運行,在調用execexit之前與父進程數據是共享的在它調用execexit之後父進程纔可能被調度運行。如果在調用這兩個函數之前子進程依賴於父進程的進一步動作,則會導致死鎖

3.當需要改變共享數據段中變量的值,則拷貝父進程。

Wait函數:

一個進程終止會關閉所有的文件描述符釋放用戶空間內存,但PCB保留,若正常退出,PCB保存着退出狀態,異常退出PCB保存異常退出的信號。Wait可以獲得這些退出信息,釋放PCB,徹底清除進程。

/*************************************************************************
	> File Name: dm07_waitpid.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年08月28日 星期二 09時27分35秒
 ************************************************************************/

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

int main()
{
	pid_t pid;
	pid_t pid_c;
	int n = 3;
	while(n--)
	{
		pid = fork();
		if(pid == 0)//child pid jump out
			break;
	}

	if(pid == 0)
	{
		printf("I am child %d\n",getpid());
		sleep(3);
	}
	else if(pid > 0)
	{
		n = 0;
	    while(1)
		{
			printf("I am parent \n");
			pid_c = waitpid(0,NULL,WNOHANG);
			if(pid_c > 0)
			{
				n++;
			}
	
	     	if(pid_c == 0)
			{
				sleep(1);
	     		continue;
			}
			if(pid_c == -1)
			{
				printf("waitpid err");
				break;
			}

	    	else
		    	printf("wait for child%d\n",pid_c);
		    if(n == 3)
		    	break;
	    	sleep(1);
         }
	}
	return 0;
}

 

進程間通信IPC:

PIPE(管道):

血緣關係、內核中開緩存,文件描述符fd[0]指向讀,fd[1]指向寫端。

管道的原理: 管道實爲內核使用環形隊列機制,藉助內核緩衝區(4k受限)實現。

pipe限制:單工,兩個pipe實現雙工,必須有血緣(從公共祖先繼承管道)

父子進程pipe通信

/*************************************************************************
	> File Name: dm01_pipe.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年08月28日 星期二 17時20分11秒
 ************************************************************************/

#include<stdio.h>
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>


int main()
{
	pid_t pid;
	int fd[2];
	char str[1024] = {"Hello World\n"};
	char buf[1024];
	char mark[]={"try again\n"};
	int len;
	if(pipe(fd) < 0)
	{
		perror("pipe:");
		exit(1);
	}
	//fpathconf pathconf can get file's max name and path.get pipe size
    printf("the size of pipe buf %ld\n",fpathconf(fd[0],  _PC_PIPE_BUF
));
	//father write and child read
	pid = fork();
	if(pid > 0)
	{
		close(fd[0]);
		sleep(5);// let child wait 5 seconds
    	write(fd[1],str,strlen(str));
		close(fd[1]);//after write the pipe,close write end;
        wait(NULL);
	}
	else if(pid == 0)
	{
		int flags;
		close(fd[1]);

		flags = fcntl(fd[0],F_GETFL);
        flags |=O_NONBLOCK;
		fcntl(fd[0],F_SETFL,flags);//after open file,change to nonblock

tryagain:
		len = read(fd[0],buf,sizeof(buf));
		if(len == -1 )
		{
			if(errno == EAGAIN)//EAGAIN when read,but the context isn't come
			{
				write(STDOUT_FILENO,mark,strlen(mark));
		    	sleep(1);
				goto tryagain;
			}
			else
			{
				perror("read:");
				exit(3);
			}
		}
		write(STDOUT_FILENO,buf,len);
        close(fd[0]);
	}
	else
	{
		perror("fork:");
		exit(2);
	}
	return 0;
}

Fifo(有名管道)

父進程首先在磁盤上創建一個命名管道文件,然後創建兩個子進程後退出。每個子進程都對管道文件進行一次讀和一次寫的動作,然後子進程退出,整個過程就結束了

管道是半雙工的,兩個進程一個只能對它讀,另一個只能對它寫,否則會出現髒數據,也就是無法區分出讀出來的數據是來自於自己的還是來自於另一個進程的。

fifo兩個進程通信

/*************************************************************************
	> File Name: dm02_fifo_w.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年08月28日 星期二 18時50分02秒
 ************************************************************************/
//進程通信 fifo 寫端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h>


void err_str(char*str,int err_n)
{
	perror("str");
	exit(err_n);
}

int main(int argc,char *argv[]) 
{
	int fd,len;
	char buf[] = {"Hello World"};
	if(argc < 2)
	{
		printf("./fifo_w.app pipename");//mkfifo fifoname can make a fifo,
		exit(1);
	}
	fd = open(argv[1],O_WRONLY);
	if(fd == -1)
	{
		err_str("open",2);
	}
	len = write(fd,buf,strlen(buf));
	if(len == -1)
	{
		err_str("write",2);
	}
	
	return(0);
}

 

/*************************************************************************
	> File Name: dm02_fifo_w.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年08月28日 星期二 18時50分02秒
 ************************************************************************/
//讀端進程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h>


void err_str(char*str,int err_n)
{
	perror("str");
	exit(err_n);
}

int main(int argc,char *argv[]) 
{
	int fd,len;
	char buf[1024];
	if(argc < 2)
	{
		printf("./fifo_w.app pipename");//mkfifo fifoname can make a fifo,
		exit(1);
	}
	fd = open(argv[1],O_RDONLY);
	if(fd == -1)
	{
		err_str("open",2);
	}
	len = read(fd,buf,sizeof(buf));
	if(len == -1)
	{
		err_str("reae",2);
	}
	else
	{
	   len = write(STDIN_FILENO,buf,len);
	   if(len == -1)
	   {
		   err_str("write",2);
	   }
	}
	
	return(0);
}

消息隊列:

 

消息隊列是消息的連接表,存放在內核中。一個消息隊列由一個標識符來標識面向記錄的,其中的消息具有特定的格式以及特定的優先級。消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

 

消息隊列就是一個數據結構,是一個隊列,是一系列保存在內核中消息的列表,主要是用來實現消息傳遞。

消息緩存區:如上圖,那些綠塊塊分別是一個一個的消息緩存區。用來存通道號和數據。

通道號:通道號相當於一個分類,不真實存在。channel 值相同的屬於同一通道,系統可以根據通道號來選擇對應的消息隊列。

1int msgget(key_t key, int flag); 創建或打開已存在的消息隊列

-1(失敗)創建/打開消息隊列標識符id(成功)

key(關鍵字),每個消息隊列都有唯一的標識符id,可以是消息隊列的關鍵字,根據key,找到對應的消息隊列生成key, ftok()

flag(權限)IPC_CREAT——創建一個新的消息隊列

 

2int msgsnd(int misqid,const void*msgp,size_t msgsz,int msgflg)給消息隊列裏寫數據

 

misgid——msgget函數的返回值,想寫入內容的消息隊列的標識符id

msgp——臨時創建一個消息隊列結構體對象的指針

mgsz——寫入消息的大小,char metex[100]的大小,不包括通道號

msgflg——權限(0—阻塞方式,PC_NOWAIT——非阻塞方式)

 

  先將內容存放在消息隊列結構體的對象中(中轉站);再將中轉站裏的內容放入到 miqid對應的消息隊列中;從而間接的實現往消息隊列中寫數據。

 

3int msgrcv(int msggid,void*msgp,size_t msgsz,int chnnel,int msgflg) 從消息隊列中讀數據

 

misgid—消息隊列的標識符id

msgp—臨時創建一個消息隊列結構體對象的指針

mgsz——寫入消息的大小,char metex[100]的大小,不包括通道號

msgflg——權限

先將msgqid對應的消息隊列的內容放在這個中轉站中;再對中轉站裏的內容進行讀取;從而間接的實現 從消息隊列中讀數據。

4int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid 消息隊列標識符id

cmd:包括以下三種

  IPC_STAT:讀取消息隊列的數據結構msqid_ds,並將其存儲在buf指定的地址中

  IPC_SET:設置消息隊列的數據結構msqid_ds中ipc_perm元素值。這個值取自buf。

  IPC_RMID:從系統內核中移走消息隊列,即刪除消息隊列

消息隊列克服信號傳遞信息少、管道只能承載無格式字節流及緩衝區大小受限等缺點

 

共享內存mmap shm:

mmap, 它把文件內容映射到一段內存上(準確說是虛擬內存上), 通過對這段內存的讀取和修改, 實現對文件的讀取和修改,mmap()系統調用使得進程之間可以通過映射一個普通的文件實現共享內存(磁盤)。普通文件映射到進程地址空間後,進程可以向訪問內存的方式對文件進行訪問,不需要其他系統調用(read,write 先讀如內核,在寫出內核)去操作。

若果addr=NULL,則內核在進程空間自動選擇內存建立映射,length爲映射文件總長度,offset爲從文件什麼位置開始映射,必須是內存頁(4k)的整數倍

https://img-blog.csdn.net/20170613110846967?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGo2MDU2MzU1Mjk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

 

ssh

https://img-blog.csdn.net/20170613113553833?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGo2MDU2MzU1Mjk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

(效率最高,直接在內存上操作不用拷貝)

共享內存允許兩個或多個進程共享一個給定的存儲區,這一段存儲區可以被兩個或兩個以上的進程映射至自身的地址空間中,一個進程寫入共享內存的信息,可以被其他使用這個共享內存的進程,通過一個簡單的內存讀取,實現了進程間的通信。

 

採用共享內存進行通信的一個主要好處是效率高,因爲進程可以直接讀寫內存,而不需要任何數據的拷貝,對於像管道和消息隊裏等通信方式,則需要再內核和用戶空間進行四次的數據拷貝而共享內存則只拷貝兩次:一次從輸入文件到共享內存區,另一次從共享內存到輸出文件。

對比:

mmap進行進程間通信

/************************************************************************
	> File Name: dm04_mmap_w.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年08月29日 星期三 09時52分46秒
 ************************************************************************/
//寫端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<errno.h>
#define MMAPLEN 0x1000
void sys_err(char*func,int err_num)
{
	perror("func");
	exit(err_num);
}

struct STU{
	char name[1024];
	int id;
	char sex;
};


int main(int argc,char*argv[])
{
	struct STU * mm;
	int fd,i = 0;
	if(argc < 2)
	{
        printf("./a.out filename\n");
		exit(1);
	}

	fd = open(argv[1],O_RDWR|O_CREAT);
	if(fd == -1)
       sys_err("open",2);

	if(lseek(fd,MMAPLEN - 1,SEEK_SET)<0)//set a file that size is MMAPLEN
		sys_err("lseek",3);
	if(write(fd,"\0",1) < 0)//at least must write one,or file size is 0
		sys_err("write",5);

	mm = mmap(NULL,MMAPLEN,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(mm == MAP_FAILED)
		sys_err("mmap",4);

    close(fd);

	while(1)
	{
     	sprintf(mm->name,"student%d",i++);
		mm->id = i;
		if(i%2 == 0)
     		mm->sex = 'w';
		else
			mm->sex = 'm';
    	sleep(1);
	}

	munmap(mm,MMAPLEN);//munmap or wait the process terminal,system do it
	return 0;
}

 

/*************************************************************************
	> File Name: dm04_mmap_w.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年08月29日 星期三 09時52分46秒
 ************************************************************************/
//讀端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<errno.h>
#define MMAPLEN 0x1000
void sys_err(char*func,int err_num)
{ 	perror("func");
	exit(err_num);
}

struct STU{
	char name[1024];
	int id;
	char sex;
};

int main(int argc,char*argv[])
{

	struct STU * mm;
	int fd,i = 0;
	if(argc < 2)
	{
        printf("./a.out filename\n");
		exit(1);
	}

	fd = open(argv[1],O_RDWR);
	if(fd == -1)
       sys_err("open",2);


	mm = mmap(NULL,MMAPLEN,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(mm == MAP_FAILED)
		sys_err("mmap",4);

    close(fd);
    unlink(argv[1]);//unlink the file link decrease 1,so the file is delete
	while(1)
	{
     	printf("name:%s\n",mm->name);
		printf("id:%d\n",mm->id);
		printf("sex:%c\n\n\n",mm->sex);
		sleep(1);
	}

	munmap(mm,MMAPLEN);
	return 0;
}

信號量:

信號量本質上是一個計數器,用於多進程對共享數據對象的讀取,它和管道有所不同,它不以傳送數據爲主要目的,它主要是用來保護共享資源(信號量也屬於臨界資源),使得資源在一個時刻只有一個進程獨享。一般和共享內存一塊使用。

由於信號量只能進行兩種操作等待和發送信號,即P(sv)和V(sv), P操作就是上鎖,計數器-1,V操作就是解鎖,計數器+1:

(1)P(sv):如果sv的值大於零,就給它減1;如果它的值爲零,就掛起該進程的執行

(2)V(sv):如果有其他進程因等待sv而被掛起,就讓它恢復運行,如果沒有進程因等待sv而掛起,就給它加1.

在信號量進行PV操作時都爲原子操作(普通的變量模擬信號量可以不?不可以,信號量是原子操作,普通變量首先進行判斷是否爲0,在進行+-1等,非原子操作)

二元信號量:

二元信號量(Binary Semaphore)是最簡單的一種鎖(互斥鎖),它只用兩種狀態:佔用與非佔用。所以它的用計數爲1。

獲取共享內存

(1)測試控制該資源的信號量

(2)信號量的值爲正,進程獲得該資源的使用權,進程將信號量減1,表示它使用了一個資源單位。

(3)若此時信號量的值爲0,則進程進入掛起狀態(進程狀態改變),直到信號量的值大於0,若進程被喚醒則返回至第一步。

信號量和互斥鎖的區別:

互斥量用於線程的互斥,信號量用於線程/進程間同步。  
這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別。  

互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。  
同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。  

 

 

進程間關係:

終端:

終端登錄:

進程組:

每個進程都有父進程,而所有的進程以init進程爲根,形成一個樹狀結構。每個進程都會屬於一個進程組,每個進程組中可以包含多個進程。進程組會有一個進程組領導進程,領導進程的PID成爲進程組的ID,以識別進程組。

 

會話(session):

Shell 分前後臺來控制的不是進程而是作業(Job)或者進程組(Process Group)。一個前臺作業可以由多個進程組成,一個後臺作業也可以由多個進程組成,Shell可以同時運行一個前臺作業和任意多個後臺作業,這稱爲作業控制(Job Control)。例如用以下命令啓動5個進程。

 

現在我們從Session和進程組的角度重新來看登錄和執行命令的過程

 

  1. getty在打開終端設備之前調用setsid函數創建一個新的Session,該進程稱爲Session Leader,該進程的id也可以看作Sessionid,然後該進程打開終端設備作爲這個Session所有進程的控制終端。在創建新 Session的同時也創建了一個新的進程組,該進程是這個進程組的組長進程,該進程的id也是進程組的id

 

2. 在登錄過程中,getty進程變成login,然後變成Shell但仍然是同一個進程仍然是Session Leader

3. Shell進程fork出的子進程本來具有和Shell相同的Session、進程組和控制終端,但是Shell調用setpgid函數將作業中的某個子進程指定爲一個新進程組的Leader,然後調用setpgid將該作業中的其它子進程也轉移到這個進程組中。如果這個進程組需要在前臺運行,就調用 tcsetpgrp函數將它設置爲前臺進程組,由於一個Session只能有一個前臺進程組,所以Shell所在的進程組就自動變成後臺進程組。

 

 

在上面的例子中,proc3proc4proc5Shell放到同一個前臺進程組,其中有一個進程是該進程組的LeaderShell調用 wait等待它們運行結束。一旦它們全部運行結束,Shell就調用tcsetpgrp函數將自己提到前臺繼續接受命令。但是注意,如果proc3 proc4proc5中的某個進程又fork出子進程,子進程也屬於同一進程組,但是Shell並不知道子進程的存在,也不會調用wait等待它結束。 換句話說,proc3 | proc4 | proc5Shell的作業,而這個子進程不是,這是作業和進程組在概念上的區別一旦作業運行結束,Shell就把自己提到前臺,如果原來的前臺進程 組還存在(如果這個子進程還沒終止),則它自動變成後臺進程組

 

/*************************************************************************
	> File Name: dm04pgid_setsid.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年09月10日 星期一 17時34分57秒
 ************************************************************************/

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>

int main()
{
	pid_t pid;

	pid = fork();
	if(pid == 0)
	{
		printf("child pid is %d\n",getpid());
		printf("child gid is %d\n",getpgid(0));
		printf("child sid is %d\n",getsid(0));
		sleep(4);
		setsid();//change child process session id,new sid is child pid
		printf("child pid is %d\n",getpid());
		printf("child gid is %d\n",getpgid(0));
		printf("child new sid is %d\n",getsid(0));
		sleep(20);
		exit(0);
	}

	return 0;

}

守護進程:

  Fork父進程退出->setsid()子進程創建會話,脫離控制終端->更改到根目錄(防止被卸載)->重設文件掩碼(防止繼承的umask對進程權限影響)->關閉文件描述符(繼承不用,浪費)->執行守護進程核心工作->守護進程退出處理。

精靈進程實例:

/*************************************************************************
	> File Name: dm05_daemon.c
	> Author: ma6174
	> Mail: [email protected] 
	> Created Time: 2018年09月10日 星期一 22時19分18秒
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include<sys/types.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

 
void gettime(char buf[])
{
	time_t t;
	time(&t); //from 1970.1.1.00:00:00 seconds
	ctime_r(&t,buf);
	return;
}

void daemonze(void)
{
	pid_t pid;
	pid = fork();
	int i = 0;
	if(pid == -1)
	{
		perror("fork");
		exit(1);
	}
	if(pid >0)
        exit(0);
	else if(pid == 0)
	{
		pid = setsid();
		if(pid == -1)
		{
			perror("setsid");
			exit(2);
		}
		i = chdir("/");
		if(i < 0)
		{
			perror("chdir");
			exit(2);
		}

		umask(0);
		// redirect 0 1 2 to /dev/null,because lose tty,0 1 2 is meaningless
		close(0);
		open("/dev/bull",O_RDWR);
		dup2(0,1);
		dup2(0,2);
	}
}

int main()
{
	int fd;
	char buf_time[1024] = {0};
	char *str = buf_time;
	daemonze();
	fd = open("/tmp/dameon.log",O_RDWR|O_CREAT|O_APPEND,0x777);

    while(1)
	{
        gettime(buf_time);
	   if(write(fd,str,strlen(str)) == -1)
	   {
		   perror("write");
		   exit(3);
	   }
       sleep(20);
	}
	return 0;
}

參考:


APUE
linux_sys
https://blog.csdn.net/zhourong0511/article/details/80143465
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章