科林Linux_3 进程

//Linux下C语言编程常用头文件

//标准空间
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

//字符串处理
#include <string.h>

//文件处理
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//线程
#include <pthread.h>

//信号
#include <signal.h>

//套接字网络
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>

一、进程基础

操作系统基础的执行单元,调度单位

静态数据:只占用磁盘空间,不消耗其他资源

动态数据:磁盘 内存 CPU

1. 编译器将源码编译成一个可执行文件.exe/.elf

2. 运行后系统生成一个同名的进程

程序是进程的静态表现,进程是程序的动态表现

3. 进程在创建时,要分配虚拟内存(线程不分配内存)

进程是最小的分配资源单位,也是调度单位

因为每个进程创建时分配内存资源,所以被成为分配资源单位

进程没有实体,由复杂的逻辑关系构成

进程的生存环境,形态:

PCB 进程控制块,是一个300多个成员的结构体,记录了详细的进程信息

虚拟内存地址通过内存映射访问真正的物理内存地址

进程中虚拟地址不同,但是指向相同的物理地址,实现共享内存【内核空间】

不同的进程内存独立【用户空间】(以防互相影响,内核空间只有系统拥有权限,不怕被修改)

程序优化:系统占用以及系统开销(CPU 内存)

内存基本单位:Page(页) 1Page=4KB=4096Bytes

malloc(8192) ——> 一次分配两页内存

malloc(5000) ——>分配两页内存,对剩余内存空间进行限制访问

malloc(3192) ——>检查是否使用完毕,并解除限制

内存的权限:PROT_READ 只读 PROT_WRITE 只写 PROT_EXEC 执行 PROT_NONE 无权限

虚拟内存:32位系统,三级间接寻页:1024*1024*1024=GB

63位系统,四级间接寻页,1024*1024*1024*1024=TB


 

二、进程状态以及状态转换

进程的五种常态

就绪态Ready:进程已准备除处理器外所需资源,等待cpu资源

运行态Running:占用cpu,正在执行指令

阻塞态Blocked:由于进程等待某种条件(如I/O操作或进程同步)被暂停(释放cpu),在条件满足之前无法继续执行。

挂起态Suspend:由于用户和系统的需要被暂停(释放cpu),把进程从内存转到外存

终止态Terminated:进程退出,释放进程内存资源

阻塞态和挂起态的区别?

1. 阻塞态可以被中断,在等待的资源得到满足后,进入就绪状态;挂起态无法被中断,只有将其挂起的对象唤醒后,才可以让其继续

2. 阻塞态释放了cpu,但是阻塞的进程还在内存中;挂起的进程通过swp交换到外存中(磁盘空间)

3. 阻塞态发生在进程等待资源时,挂起是由于用户和系统需要

任意状态都可以切换为终止态

Linux独有的状态

僵尸态Zombie:子进程退出结束,但是PCB残留,导致内存泄漏,叫做僵尸进程

孤儿态Orphan:父进程先挂掉,子进程会被1号进程领养,我们可以理解为操作系统,或者说父进程先结束,子进程必须被操作系统领养,因为无主进程,一直僵尸浪费资源,我们将这种进程称之为孤儿进程。 还会将自己变成后台进程。

分时复用原则(时间片)

多进程共享使用CPU,每个进程按时间片(10us)使用,而后快速切换,既可以让多个进程共享使用,以提高cpu使用率

单任务操作系统(软盘) ——> 多任务操作系统(充分共享使用硬件)

大多数系统的程序都是并发执行的(快速交替执行)

保存与恢复处理器现场

CPU原件:控制器、寄存器(Eax,Ebx,Edx,Eflag....)、编码器、译码器、运算器

寄存器:

内存:快速缓存数据及计算过程

每个进程都有自己独立的PCB,每个PCB都有一个内核栈指针kernel_ptr,指向自己的内核栈

多任务基准:分时+保存与恢复
 

什么是线程?什么是进程?

进程或者线程就是寄存器(CPU的访问权)和栈(内核栈)

进程、线程、纤程:调度单位

协程、管程:管理单元

超线程:硬件,模拟多核

二、进程源语

2.1 进程源语函数

系统支持进程开发提供的一系列API函数接口

pid_t pid;进程id类型

pid_t pid=getpid(void);//调用获取进程pid

2.1.1 fork() 创建子进程

pid_t pid=fork(); //创建子进程,执行一次创建一个进程

子进程与父进程pid连续,父进程最小

父进程调用fork,系统创建子进程,子进程从fork之后执行

关于子进程继承的问题?

 1. 拷贝继承

2. 区分父子进程代码段,实现父子执行不同的代码任务

使用fork的返回值pid进行父子进程的区分

子进程不允许踏出工作区

1. 永久阻塞 2. 进程退出

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

//parent start
int main(){
	pid_t pid;
	pid=fork();  //创建子进程
	if(pid>0){
		printf("parent pid %d, Running...\n",getpid());
	}
	else if(pid==0){
		//child start
		printf("child pid %d, Running...\n",getpid());
		while(1)
			sleep(1);
		//child end
	}
	else if(pid==-1){
		perror("fork call failed");
		exit(0);
	}
	printf("self id %d\n",getpid());
	while(1)
		sleep(1);

	return 0;
}
//parent end

windows的printf自带刷新缓冲区

linux下\n刷新缓冲区

3. 创建多进程

循环创建多进程

int i;
for(int i=0;i<3;i++){
    pid=fork();
    if(pid==0)
        break;
}

4. 子进程执行fork,并且得到返回值0

两个进程执行同一个fork()

栈帧

不同的函数有各自的函数栈帧地址,其他位置无法访问函数内部资源

父子进程可以共享同一个函数的栈帧地址,实现调用同一个函数,执行一部分

gdb可以实现栈帧的跳转

第一版 fork,拷贝继承版本

如果子进程不需要拷贝父进程数据,而是自行获取用户空间,那么父进程的拷贝流程和开销变成无意义的系统开销

第二版 vfork,无拷贝过程,但是不能直接使用

通过vfork创建的子进程没有用户空间,需要用户自行生成用户层,vfork必须结合execl函数使用

第三版 fork,读共享写复制

读时共享,虚拟地址不同,物理地址相同

父子进程写都会导致子进程复制共享内存数据

例题:

2*9+1=19个子进程

2*10-1=19

2.1.2 execl() 进程重载

系统名都是程序,执行命令时创建进程,完成特定功能

重载只替换进程工作数据,与PCB进程信息无关

    execl("程序路径",argv[0]/*文件名|命令*/,argv[1]/*参数*/,NULL/*哨兵,结束*/);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
	pid_t pid;
	pid=fork();
	if(pid>0){
		printf("parent pid %d\n",getpid());
		while(1)
			sleep(1);
	}
	else if(pid==0){
		printf("child pid %d\n",getpid());
		printf("child execl...\n");
		execl("/usr/bin/firefox","firefox","www.bilibili.com",NULL);
		printf("execl failed\n");
		exit(0);
	}
	else if(pid<0){
		printf("fork failed\n");
		exit(0);
	}
	return 0;
}

使用execl

1. vfork创建子进程无法直接使用,必须重载才可以使用

2. 重载方便程序的功能扩展,很多现有的命令在进程中可以直接使用

3. 动态迭代

在程序不下线的情况下,动态更新功能

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
	pid_t pid;
	printf("MyWeb Vesion 1.o Running...\n");
	while(1){
		pid=fork();
		if(pid==0)
			break;
		sleep(5);
	}
	if(pid==0)
		execl("./MOD/mod1","mod1",NULL);
	return 0;
}

此时只需要修改mod1.c文件内容并重新编译,就可以在线修改主程序的功能

2.1.3 wait() waitpid() 回收函数

僵尸态Zombie:子进程退出结束,但是PCB残留,导致内存泄漏,叫做僵尸进程

_EXIT(0) 结束进程时,完全释放了用户空间,释放了部分内核空间,只有PCB残留——内存泄漏

危害:1. 内存泄漏 2. 僵尸进程残留PCB无法给新进程使用,影响进程创建数量

系统为何要保留PCB?

僵尸进程需要父进程进行手动回收、检验子进程是否正常退出

关于ps -aux进程状态stat的中Ss、S<l、Ssl、SLl、SNl、R、R+的解释_ps aux 进程 tl sl-CSDN博客

int stat;
pid_t zpid=wait(NULL);    //表示只回收pcb不进行回收的验证操作,返回值为僵尸进程pid
pid_t zpid=wait(&stat);    //返回子进程状态

wait() 失败原因:没有子进程立即失败
wait() 是阻塞函数,阻塞回收,如果子进程未结束,无需回收,wait阻塞等待

 

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

int main(){
	pid_t pid;
	pid_t zpid;
	pid=fork();
	if(pid>0){
		zpid=wait(NULL);
		printf("parent pid %d, child pid %d, zpid %d\n",getpid(),pid,zpid); 
		while(1)
			sleep(1);
	}
	else if(pid==0){
		printf("child alive 10s\n");
		sleep(10);
		exit(0);
	}
	else {
		perror("fork failed");
		exit(0);
	}
	return 0;
}

只有父进程可以回收子进程,其他进程无法代替 

init 1号进程没有父进程,称作根进程

子进程先于父进程死亡,必然会成为僵尸;父进程先于子进程死亡,子进程必然会成为孤儿

waitpid(pid_t pid,int* state,WNOHANG/*非阻塞关键字*/);    
//返回值:>0 成功返回| -1 失败| =0 非阻塞返回
    pid==-1 回收任意子进程
    pid>0 回收指定子进程(进程号)
    pid==0 同组回收
    pid<-1 跨组回收(-进程组号)

waipid() 支持非阻塞回收,相比wait() 更加灵活。在等待回收时,父进程可以执行自己的任务和代码。

Linux之进程的基本概念(进程,进程组,会话关系)_linux 会话id-CSDN博客

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

int main(){
	pid_t pid;
	pid_t zpid;
	pid=fork();
	if(pid>0){
		while((zpid=waitpid(-1,NULL,WNOHANG))!=-1){
			if(zpid>0)
				printf("parent pid %d, child pid %d, zpid %d\n",getpid(),pid,zpid); 
			else if(zpid==0){
				printf("parent exec jobs\n");
				sleep(1);
			}
		}
		while(1)
			sleep(1);
	}
	else if(pid==0){
		printf("child alive 10s\n");
		sleep(10);
		exit(0);
	}
	else {
		perror("fork failed");
		exit(0);
	}
	return 0;
}

2.2 多进程拷贝

单进程的CPU使用资源受限,可以采用多进程模型,获取更多的时间片,加快任务完成速度,提高效率

两种常见的多进程:多进程任务较为复杂;多进程任务单一

串行:逐次完成任务

并行:多核CPU且每一个工作进程都能加载到对应的处理核心上【由CPU决定,不可控】

并发:单个处理器,逻辑上同步执行【有并行的概率】多进程并发、多线程并发

程序的执行效率由cpu的占用及使用频率决定,占用越多效率越高

单进程模型因阻塞或其他原因放弃本轮cpu使用,但是多进程或者多进程模型,根据就近原则,放弃的进程可以交替给相邻的进程,让任务继续

GitHub - red-dye123/colinLearning

四、进程间通信(IPC)

Linux 管道(匿名管道与命名管道)_匿名管道和命名管道的安全性-CSDN博客

进程间通信技术,便于多进程模型中,进程间共享或传递数据的一种手段(有多种方式)

1、管道(匿名管道、命名管道)

2、socket套接字

3、消息队列(Posix,SystemV)

4、signal信号

5、内存映射(mmap)

用户层无法通信(进程独占资源),内核层是共享内存。绝大多数的进程间通信手段,都是基于内核层实现的。

4.1 匿名管道

特征:

1、是一种传输介质

2、具备方向性(传输方向)

3、暂时存储数据

4.1.1 创建管道

//系统创建管道
pipe (int fds[2]);    //成功0,失败-1

弊端:匿名管道只支持亲元进程通信,如果进程不相关,无法使用

4.1.2 通信方向

一般采取单工传输(半双工使用,管理和控制复杂)

传输方式:

单工传输:任意时刻,非读即写,无法切换(管道、消息队列)

半双工传输:一个时刻,非读即写,不同时刻可以切换(可调节单工)

双工传输:任意时刻,同时读写(socket)

管道的引用计数=0:系统释放管道空间

不用的一端使用close()关闭

close(fds);    //引用计数-1

shutdown();    //清0引用计数
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

#define msg "hi son"

//单工传输,匿名管道,父写子读
int main(){
	pid_t pid;
	int fds[2];
	pipe(fds);//管道创建

	pid=fork();
	if(pid>0){
		close(fds[0]);//关闭读端
		write(fds[1],msg,strlen(msg));//发送数据
		printf("parent pid %d, write sucess\n",getpid());
		close(fds[1]);//计数清零,释放管道
		wait(NULL);
	}
	else if(pid==0){
		char buf[4096];//缓存管道4k
		bzero(buf,sizeof(buf));
		close(fds[1]);//关闭写端
		read(fds[0],buf,sizeof(buf));
		printf("child read msg=%s\n",buf);
		close(fds[0]);//计数清零,释放管道
	}
	else{
		perror("fork failed");
		exit(0);
	}
	return 0;

}

ubuntu 16.04往后版本匿名管道大小为4K,之前的版本为64K

匿名管道使用时四种特殊情况(具有普遍意义):

1、写阻塞:写端写管道,读端未读,管道写满后,系统阻塞写端

2、读阻塞:写端未写,管道为空,读端读管道,系统阻塞读端

3、写端关闭,读端读取管道剩余数据,管道空再次读,返回0

4、读端关闭,写端尝试写数据,系统向写端进程发送信号SIGPIPE,杀死写端进程

例:客户端向服务端发送一条请求数据,客户端异常关闭,服务端读取请求,处理请求,向客户端发送响应数据,结果服务端异常退出,为什么?

因为读端关闭,服务器写端尝试写数据时被信号杀死了

使用send(sock,buffer,len,MSG_NOSIGNAL); 在发送数据时忽略信号

send(sock,buffer,len,MSG_NOSIGNAL);    //忽略信号
recv(sock,buffer,MSG_DONTWAIT);    //非阻塞读

4.2 命名管道

管道文件:不是常规文件,是设备文件。

并不是采用文件通信,而是使用文件的操作方式,来访问使用管道(指向管道缓冲区的指针)

向管道文件读写的数据,都会重定向到管道缓冲区中

管道文件与管道缓冲区绑定,创建文件则创建缓冲区,删除文件则释放缓冲区

4.2.1 创建管道文件

mkfifio 管道文件名
mkfifio(char* name,0664);    //函数常见
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//文件相关头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define msg "pipe msg"
//写端
int main(){
	int wfd;
	mkfifo("串休",0664);    //创建管道
	wfd=open("串休",O_WRONLY);
	write(wfd,msg,strlen(msg));
	printf("write process %d, sucess\n",getpid());
	close(wfd);
	return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//文件相关头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//读端
int main(){
	int rfd;
	char buffer[4096];
	bzero(buffer,sizeof(buffer));
	rfd=open("串休",O_RDONLY);
	read(rfd,buffer,sizeof(buffer));
	printf("Read process %d, %s\n",getpid(),buffer);
	close(rfd);
	unlink("串休");    //删除管道(硬链接计数器-1)
	return 0;
}

发现写端在open阻塞。

管道文件的访问限制(权限):管道文件的访问要求,必须满足两种权限(O_WRONLY && O_RDONLY)才能成功打开管道,如果只有一种则需要等待另一种(open阻塞)

只要满足权限即可打开使用管道,没有进程数量限制(O_RDWR读写打开)

命名管道使用特殊情况(匿名管道的特殊情况也适用):

1、访问管道文件时必须满足读写权限,才可以打开管道

2、一个进程中包含多个读序列(多个线程),阻塞只对第一个读序列生效,其他读序列自动设置为非阻塞

避免了无意义的阻塞开销

3、管道的原子访问与非原子访问。

原子访问:只要用户写的数据量<=管道大小,则为原子写。如果写入量超过余量,系统会挂起写端,直到读取后余量满足才唤醒。

优点:保证数据传输的完整性,避免多个数据包掺杂在一起

缺点:传输效率低,因为写端会经常被挂起

非原子访问:写数量>管道大小,则为非原子。只要管道余量不为0,那么写端可以随时写任意数据量到管道中

优点:较高的通信传输速度

缺点:数据会被拆分切割,读端必须对读的数据进行验证,否则无法使用

数据包一般是定量的(对数据进行封装,协议、消息),一般不会是变量

4.3 mmap内存映射

通过内存映射手段完成通信

映射文件MapFile,把文件内容通过映射的方式写道内存中(读共享:虚拟不同,物理相同)

映射函数

void* ptr=mmap(NULL/*映射内存地址,参数为NULL时,系统在库空间申请内存;不为空时,在堆空间*/,
                size/*映射内存大小,一般为映射文件大小(映射文件必须有大小)*/,
                PROT_READ|PROT_WRITE/*内存映射权限*/,
                MAP_SHARED|MAP_PRIVATE/*映射方式:共享映射|私有映射*/,
                int fd/*映射文件的文件描述符*/,
                0/*映射偏移量*/);
//成功返回映射内存的地址;失败返回MAP_FALIED关键字
    munmap(ptr,size);    //释放映射内存
  • 私有映射:拷贝映射,将文件中的数据拷贝一份到进程的映射内存。拷贝的多份数据无关联,修改任意一端数据不会影响另一端。
  • 共享映射:同步映射,SYNC同步机制。修改任意一份映射数据(内存/文件)都会实时同步,保持一致性 

文件为空,但是有大小?

从小到大截断——ftruncate(int fd,size);        //使用截断的方可以扩展空文件

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/fcntl.h>
#include<sys/mman.h>

//映射,通过同步机制,修改文件

int main(){
	int* ptr=NULL;//指针类型决定操作的长度
	int fd=open("MapFile",O_RDWR);//读写打开映射文件
	int size=lseek(fd,0,SEEK_END);//计算文件大小
	if((ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)//内存映射
	{
		perror("mmap failed");
		exit(0);
	}
	close(fd);
	printf("映射成功,文件数据:%s",(char*)ptr);

	ptr[0]=0x34333231;
	printf("修改成功\n");
	return 0;
}

映射通信

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/mman.h>
#include<sys/fcntl.h>

//mmap 写端

typedef struct{
	int id;
	char name[20];
}msg_t;

int main(){
	int fd=open("MAP",O_RDWR|O_CREAT,0664);
	//截断空文件
	ftruncate(fd,sizeof(msg_t));//将文件截断为结构体大小

	msg_t* ptr=NULL;
	if((ptr=mmap(NULL,sizeof(msg_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)
		perror("mmap failed");
	close(fd);

	ptr->id=0;
	bzero(ptr->name,20);

	while(1){
		++(ptr->id);
		sprintf(ptr->name,"testName %d",ptr->id);//写端修改数据
		sleep(1);
	}
	munmap(ptr,sizeof(msg_t));
	return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/mman.h>
#include<sys/fcntl.h>

//mmap 读端

typedef struct{
	int id;
	char name[20];
}msg_t;

int main(){
	int fd=open("MAP",O_RDWR|O_CREAT,0664);
	
	msg_t* ptr=NULL;
	if((ptr=mmap(NULL,sizeof(msg_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0))==MAP_FAILED)
		perror("mmap failed");
	close(fd);

	while(1){
		printf("%s\n",ptr->name);//读端输出数据
		sleep(1);
	}
	munmap(ptr,sizeof(msg_t));
	return 0;
}

映射失败的原因?

1.权限不足,打开文件的权限<=映射权限

2.映射文件异常,映射大小为0,导致无法申请内存

为何会出现总线错误SIGBUS?

映射内存的大小超过了文件大小,则会发生越界/溢出错误

有些情况下,mmap可以代替read

mmap比较适合读取处理文件(适合大文件数据)?

读取文件的操作上,mmap映射手段效率更高,开销更小,相比read减少拷贝开销

大数据文件可以通过mmap进行偏移映射,分段处理

零拷贝技术:在读写时,可以减少拷贝开销,以及可以减少上下层级转换的次数

SendFile

应用于socket套接字、信号等

五、进程关系

5.1 亲缘关系

强亲缘(父子关系):子进程整个生命周期,父进程全程参与(创建、克隆、回收)

弱亲缘(多级亲缘关系):多级的亲缘关系,只需要继承即可

5.2 孤儿进程

 孤儿进程:父进程先于子进程退出(异常退出),子进程变为孤儿进程,引起隐患与危害

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
	pid_t pid;
	pid=fork();
	if(pid>0){
		printf("parent pid %d\n",getpid());
		sleep(5);
		exit(0);
	}
	else if(pid==0){
		while(1){
			printf("child pid %d, ppid %d\n",getpid(),getppid());
			sleep(1);
		}
	}
	else{
		perror("fork failed");
		exit(0);
	}
	return 0;
}

创建出孤儿进程发现其被托管,与当前终端Bash没有亲缘关系

注意此时进程不是前台进程,而是后台进程,需要使用ps aux查看进程号,然后kill -9 进程号杀死

孤儿进程的预防与处理

1.提高父进程健壮性

2.检测与处理孤儿

(1)利用匿名管道特性,读进程关闭,写进程被杀死,子进程为写端,定时向管道写数据。如果父进程退出,子进程被SIG_PIPE信号杀死

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h>

int main(){
	//管道检测孤儿进程并处理
	pid_t pid;
	int i=0;
	int fds[2];
	pipe(fds);
	for(i;i<2;i++){
		pid=fork();
		if(pid==0)
			break;
	}
	if(pid>0){
		close(fds[0]);
		printf("parent pid %d\n",getpid());
		while(1)
			sleep(1);
		close(fds[1]);
	}
	else if(pid==0){
		int len;
		int flag;
		char buffer[10];
		bzero(buffer,sizeof(buffer));
		close(fds[1]);
		//非阻塞读管道
		flag=fcntl(fds[0],F_GETFL);
		flag|=O_NONBLOCK;
		fcntl(fds[0],F_SETFL,flag);
		while(1){
			printf("child pid %d\n",getpid());
			if((len=read(fds[0],buffer,sizeof(buffer)))==-1){
				if(errno==EAGAIN){
					//非阻塞读
				}
				else{
					perror("read call failed");
					exit(0);
				}
			}
			if(len==0){
				printf("check parent exit, child %d exit\n",getpid());
				exit(0);
			}
			sleep(1);
		}
		close(fds[1]);
	}
	else{
		perror("fork failed");
		exit(0);
	}
	return 0;
}

(2)子进程通过信号检测父进程是否退出。如果是,子进程退出,避免孤儿

kill(getppid(),0);

5.3 进程组 Process_Group

管理多进程的结构

脱离组关系:

1.成为组长

2.

5.4 进程会话关系——守护进程

ps ajx    查看进程关系

终端进程管理关系,用于管理若干终端子进程

setsid();    //当前进程创建会话
getsid(getpid());    //获取当前进程会话id
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(){
	pid_t pid;
	pid=fork();
	if(pid>0){
		printf("parent pid %d, ppid %d\n",getpid(),getppid());
		while(1);
	}
	else if(pid==0){
		printf("child pid %d, ppid %d\n",getpid(),getppid());
		//创建新组
		setpgid(getpid(),getpid());
		//getsid(getpid());
		printf("change group sucess, pgid %d\n",getpgrp());
		while(1)
			sleep(1);
	}
	else{
		perror("fork failed");
		exit(0);
	}
	return 0;
}

某个进程脱离终端控制,不受终端控制,终端结束与进程无关

普通进程生命周期:随使用者持续,开启与关闭

用户自定义进程,拥有丰富的功能及任务

工作模式:在允许的情况下尽可能占用系统资源,完成软件功能给用户好的使用体验

守护进程生命周期:开机自启动,关机结束

服务型任务:为某个主要任务提供支持,功能支持或数据支持

工作模式:低开销,间隔执行,条件触发,定时触发

脱离控制终端

实现后台服务器程序(daemon process)

步骤:

开机启动

1.父进程创建子进程,父进程退出

2.子进程创建新会话(守护进程)

3.关闭无用的描述符

守护进程均为后台服务程序,不占用前台。

STDOUT_FILENO、STDIN_FILENO、STDERR_FILENO

关闭标准输出和标准输入,保留标准出错。此时发现perror仍会在终端上输出

dup复制文件描述符/dup2文件描述符重定向

需要使用dup2技术将错误信息重定向到文件中

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>

int main(){
	close(STDOUT_FILENO);
	close(STDIN_FILENO);
	int fd,efd;
	efd=open("ERROR",O_RDWR);
	dup2(efd,STDERR_FILENO);

	if((fd=open("noexits",O_RDWR))==-1)
		perror("open failed");
	return 0;
}

4.修改进程工作目录,改为根目录

进程的默认工作目录,为程序所在位置,如果使用./为当前位置

如果进程无回访,那么没有对程序文件的强制依赖

如果不修改目录,会导致挂载的u盘(程序文件)被占用,无法卸载

5.修改进程umask掩码,改为0002,避免不同设备中umask不一致导致的权限异常

初始权限与umask位运算

6.执行守护进程的核心任务,低开销

7.守护进程退出处理

例:每间隔3s向time.log文件中写入系统时间

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

void daemon_work(){
	//守护进程任务,低开销执行
	int fd;
	time_t tp;
	char tbuf[1024];
	bzero(tbuf,sizeof(tbuf));

	if((fd=open("time.log",O_RDWR|O_CREAT,0664))==-1)
		perror("open failed");
	while(1){
		tp=time(NULL);//返回种子
		ctime_r(&tp,tbuf);
		write(fd,tbuf,strlen(tbuf));
		bzero(tbuf,sizeof(tbuf));
		sleep(3);
	}
}

void Create_daemon(){
	//守护进程创建接口
	pid_t pid;
	pid=fork();
	if(pid>0){
		//1.父进程创建子进程,父进程退出
		exit(0);
	}
	else if(pid==0){
		//2.创建新会话,脱离控制终端
		setsid();
		//3.关闭无用描述符
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		int efd;
		dup2(efd,STDERR_FILENO);
		//4.修改进程工作目录,注意权限
		chdir("./");
		//5.修改umask
		umask(0002);
		//6.执行任务
		daemon_work();
		//7.退出处理,释放资源
	}
	else{
		perror("fork call failed");
		exit(0);
	}
}

int main(){
	//启动接口
	Create_daemon();
	return 0;
}

Linux实现进程的开机自启动

Shell脚本(命令解释器),只需要执行权限,不需要进行编译(Python的动态编译与其类似类似)

1.创建脚本文件,后缀sh可省

2.首行声明sh版本 #!/bin/bash

3.编写命令

  1 #!/bin/bash
  2 ### BEGIN INIT INFO
  3 # Provides:          daemon
  4 # Required-Start:    $remote_fs $syslog
  5 # Required-Stop:     $remote_fs $syslog
  6 # X-Start-Before:    kdm gdm3 xdm lightdm
  7 # X-Stop-After:      kdm gdm3 xdm lightdm
  8 # Default-Start:     2 3 4 5
  9 # Default-Stop:      
 10 # Description:       Start Daemon service
 11 ### END INIT INFO
 12 
 13 
 14 cd "/home/colin/colin/process"
 15 
 16 ./daemon

4.未脚本文件赋予执行权限

sudo chmod 0775 shellname

5.在脚本中加入启动块信息

显示行号:set nu

隐藏行号:set nonu

 

 从init.d中复制一份启动块信息即可

6.将自定义脚本移动到/etc/init.d目录

7.将脚本加入开启启动队列

sudo update-rc.d shellname start 启动顺序 启动级别.

sudo update-rc.d shellname remove

  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值