操作系统与系统编程(2)——进程

 

目录

进程:

进程状态:

环境变量

进程原语:

进程间通信IPC:

进程间关系:

终端:

终端登录:

进程组:

会话(session)

守护进程:

参考:


 

 

进程:

进程状态:

Cpucpu基本由运算器 寄存器 控制器 译码器等构成,寄存器会读入当前进程的机器码,供给cpu执行,当进程a的运行时间资源耗尽,则a的处理器现场会备份在进程a的pcb栈区(内核区),当b调用完毕,寄存器内会恢复a的寄存器现场继续执行进程a。

进程的状态有:就绪 运行 挂起 停止等。进程状态可以切换,当睡眠态的条件到达时就会进入就绪态。当系统进行调度,就绪态就可能进入运行态。运行态可能进入就绪态和停止态。

操作系统进行进程管理:进程管理利用时分复用的思想进行进程调度,最小调度单位为时间片,早期时间片为10ms。时间片划分越来越小,调度越来越灵活。

环境变量

 环境表就是一个指针数组

https://i-blog.csdnimg.cn/blog_migrate/c8dcc1e79fd2785f9dbf891d82618926.png   https://i-blog.csdnimg.cn/blog_migrate/64f1c96106db27457cbeb94f0ee7d8e3.png

环境表和环境字符串通常存放在进程存储空间的顶部,删除一个字符串很简单,只要现在环境表中找到该指针,然后将所有后续指针都向环境首部顺次移动一个位置。但是增加一个字符串或者修改一个现有的字符串就困难的多。环境表和环境字符串通常占用的是进程地址空间的顶部,所以不能再向高地址方面扩展,同时也不能移动在它之下的各栈帧,所以它也不能向地地址扩展。

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: ma6174@163.com  
	> 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: ma6174@163.com 
	> 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: ma6174@163.com 
	> 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: ma6174@163.com 
	> 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: ma6174@163.com 
	> 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://i-blog.csdnimg.cn/blog_migrate/e1688f893feb27a2f4b7827271d9cb33.png

 

ssh

https://i-blog.csdnimg.cn/blog_migrate/cec96f7ae353e7e903144d8c6889ee7c.png

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

共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取,实现了进程间的通信。

 

采用共享内存进行通信的一个主要好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要再内核和用户空间进行四次的数据拷贝而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。

对比:

mmap进行进程间通信

/************************************************************************
	> File Name: dm04_mmap_w.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> 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: ma6174@163.com 
	> 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: ma6174@163.com 
	> 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: ma6174@163.com 
	> 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
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值