IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> unix-linux 进程管理 -> 正文阅读

[系统运维]unix-linux 进程管理

进程和程序

?1) 进程就是运行中的程序。 一个运行着的程序,可能有多个进程。进程在操作系统中执行特定的任务。
?2) 程序是存储在磁盘上,包含可执行机器指令和数据的静态实体。 进程或者任务是处于活动状态的计算机程序。
进程(程序是静态的,进程是动态的(运行中的程序))
一个程序可以被多次运行,对应多个进程(类似QQ双开)
一个程序中可以开辟多个进程(类似QQ多个人聊天)
(这是两个概念)

进程是内存分配的最小单位,内存分配是面向进程的。
线程是操作系统调度的最小单位,操作系统的任务调度是面向线程的
函数是C语言程序的基本组成单位 

进程的分类

可以分为前台进程和后台进程

也可以分为交互进程、批处理进程和守护进程三类。
守护进程总是活跃的,一般是后台运行。
守护进程一般是由系统在开机时通过脚本自动激活启动,
或者由超级用户root来启动。

守护进程

分为:
1.独立启动守护进程:随系统启动而启动,启动后一直常驻内存,所以会一直占用系统资源
一直在运行,优点当外界有要求时响应速度快,此类守护进程通常保存在/etc/rc.d/init.d

2.超级守护进程:系统启动时,由一个统一的守护进程xinet来负责管理一些进程
当外界有要求,需要通过xinet转接才可以唤醒被xinet管理的进程
优点:最初只有xinet一个守护进程占用资源,其他的内部服务并不一直占系统资源,只有请求到来时服务进程才会被唤醒。

编写守护进程的步骤:
1.创建子进程,父进程退出
	让init(1)进程收养
2.在子进程中创建新对话 
setsid()函数 创建一个新的会话
	让进程摆脱原会话的控制
	让进程摆脱原进程组的控制
	让进程摆脱原控制终端的控制
3.改变当前目录为根目录
	chdir(“/”);
4.重设文件权限掩码
	umask(0)
5.关闭文件描述符
	for(i=0;i<3;i++)
		close(i);
	0 1 2
6.守护进程退出处理
 信号处理

守护进程的特点:
	守护进程都是具有超级用户的权限
	守护进程的父进程都是init进程
	守护进程都不用控制终端,其TTY列以”?”表示  TPGID为-1  
	守护进程都是各自进程组会话过程的唯一进程

dameon.c

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

volatile sig_atomic_t running = 1;

void sigint_handler(int sig){
	running = 0;
}

int main(){
	pid_t id = fork();
	if(id == -1){
		perror("fork");
		return -1;
	}
	if(id > 0){//父进程
		return 0;  //父进程
	}
	printf("%d\n",getpid());
	setsid();
	chdir("/");
	umask(0);
	int i;
	for(i=0;i<3;i++)
		close(i);
	//signal(SIGINT,sigint_handler);//如果进程接收到SIGINT函数 去执行对应函数
	signal(2,sigint_handler);//如果进程接收到SIGINT函数 去执行对应函数
	//异步
	while(running){
		int fd = open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0644);
		if(fd == -1){
			perror("open");
			return -1;
		}
		char *p = "这是一个守护进程!\n";
		write(fd,p,strlen(p));
		close(fd);
		sleep(3);
	}
	
	return 0;	
}

查看进程 ps 这是静态的

查看进程有BSD(伯克利)和SVR4两种风格可以选择(命令行输入)

BSD:

ps axu 
a - 所有用户有控制终端的进程 
x - 包括无控制终端的进程 
u - 以详尽方式显示 
w - 以更大列宽显示
例.   ps aux | grep 进程号
	ps显示全部进程 管道grep  grep是查找工具
	能把与进程号有关的数据按正则表达式打印出来
	ps aux | wc 统计  

| 管道
连接的多个命令 把前一个命令的输出作为后一个命令的输入

SVR4:

ps -efl 
-e或-A - 所有用户的进程 
-a - 当前终端的进程 
-u 用户名或用户ID - 特定用户的进程 
-g 组名或组ID - 特定组的进程 
-f - 按完整格式显示 
-F - 按更完整格式显示 
-l - 按长格式显示

进程信息列表

?USER/UID: 进程属主。
?PID: 进程ID。
?%CPU/C: CPU使用率。
?%MEM: 内存使用率。
?VSZ: 占用虚拟内存大小(KB)。
?RSS: 占用物理内存大小(KB)。
?TTY: 终端次设备号,“?”表示无控制终端,如后台进程。
?STAT/S: 进程状态。可取如下值: ?O - 就绪。等待被调度。
?R - 运行。Linux下没有O状态,就绪状态也用R表示。
?S - 可唤醒睡眠。系统中断,获得资源,收到信号,都可被唤醒,转入运行状态。
?D - 不可唤醒睡眠。只能被wake_up系统调用唤醒。
?T - 暂停。收到SIGSTOP信号转入暂停状态, 收到SIGCONT信号转入运行状态。
?W - 等待内存分页(2.6内核以后被废弃)。
?X - 死亡。不可见。
?Z - 僵尸。已停止运行,但其父进程尚未获取其状态。
?< - 高优先级。
?N - 低优先级。
?L - 有被锁到内存中的分页。实时进程和定制IO。
?s - 会话首进程。
?l - 多线程化的进程。
???在前台进程组中。



?START/STIME: 进程开始时间。
?TIME: 进程运行时间。
?COMMAND/CMD: 进程指令。
?F: 进程标志。可由下列值取和: ?1 - 通过fork产生但是没有exec。
?4 - 拥有超级用户特权。

?PPID: 父进程ID。
?NI: 进程nice值,-20到19,可通过系统调用或命令修改。
?PRI: 进程优先级。 
 静态优先级 = 80 + nice,60到99,值越小优先级越高。 
 内核在静态优先级的基础上,根据进程的交互性计算得到实际(动态)优先级 ,以体现对IO消耗型进程的奖励,和对处理器消耗型进程的惩罚。
?ADDR: 内核进程的内存地址。普通进程显示“-”。
?SZ: 占用虚拟内存页数。
?WCHAN: 进程正在等待的内核函数或事件。
?PSR: 进程被绑定到哪个处理器。

top 动态展示进程

类似windows下的任务管理器
在top界面可以用来监测
kill命令适用于对进程的操作

父子概念 fork

在这里插入图片描述

进程标识符

#include <unistd.h>
getpid  - 获取进程ID
getppid - 获取父进程ID
getuid  - 获取实际用户ID
geteuid - 获取有效用户ID
getgid  - 获取实际组ID
getegid - 获取有效组ID

fork

创建一个子进程,失败返回-1
调用一次,返回两次 
分别在父子进程中返回子进程的PID和0。 
如果成功:表明成功创建了一个子进程
在父进程中,返回的值为子进程ID,因为有getppid可以在子进程中返回父进程
在子进程中,返回的值为0
fork之前的代码只有父进程执行,fork之后的代码父子进程都有机会执行,受代码逻辑的控制而进入不同分支。
子进程拷贝父进程的地址空间(拷贝包括全局区、堆栈区的数据)
子进程获得父进程数据段和堆栈段(包括I/O流缓冲区)的拷贝
父子进程共享代码区,因为一模一样,不需要拷贝
子进程拷贝父进程的文件描述符表(进程级),共享文件表(内核级)
子进程创建之后,和父进程一样等待操作系统的调度,谁先谁后不确定
子进程会继承父进程的信号处理方式以及环境列表
父子进程可以执行相同的代码,也可以根据返回值不同让其执行不同的代码分支
#include <stdio.h>
#include <unistd.h>


int main(){
	pid_t mainId = getpid(); //主进程 父进程
	pid_t id = fork();
	if(id == -1){
		perror("fork");
		return -1;
	}

	if(mainId == getpid()){//主进程 父进程
		printf("我是父进程:%d,有一个子进程:%d\n",mainId,id);	
	}else{
		printf("我是子进程:%d,有一个父进程:%d\n",getpid(),mainId);
	}
	sleep(1);
	return -1;	
}

僵尸进程(子进程结束后,父进程没有获取子进程状态,没有回收其资源)
孤儿进程(父进程先于子进程结束)

vfork

该函数的功能与fork基本相同,二者的区别:
调用vfork创建子进程时并不复制父进程的地址空间 
子进程可以通过exec函数族,直接启动另一个进程替换自身,进而提高进程创建的效率。
vfork一定会保证子进程先执行
vfork的子进程如果没用exec函数创建进程取代,就一定要用exit()来取代进程结束
vfork不用exec系列函数会一直执行

vfork和exec系列函数一般联合使用

#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
//终端bash  等待用户输入  vfork创建一个子进程 子进程去执行输入的命令
int main(){
	pid_t id = vfork();

	if(id == -1){
		perror("fork");
		return -1;
	}

	if(id == 0){//vfork创建的子进程不会拷贝地址空间
		printf("子进程:%d 父进程:%d\n",getpid(),getppid());
		char *argv[] = {"test","name","abc","123",NULL};
		char *envp[] = {"NAME=zzxx","PATH=/","PASS=123",NULL};
		int ret = execve("test",argv,envp);//启动一个进程替换当前进程
		if(ret == -1){
			perror("execve");
			exit(-1);
		}
		printf("不会执行\n");
	}
	id = wait(NULL);
	printf("%d子进程结束了!\n",id);
	
	return 0;	
}

如何创建n个进程

下面这个例子是打印五次
里面有两种方法

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

int main(){
	
	pid_t id = fork();//2
	/*if(id == 0){
		id = fork();//
		if(id == 0){
			id = fork();
			if(id == 0){
				fork();	
			}
		}
	}*/
	
	//快速创建指定数量的进程数	
	pid_t id[4];
	int i;
	for(i=0;i<4;i++){
		id[i] = fork();
		if(id[i]==0){
			printf("我是子进程:%d,父进程:%d\n",getpid(),getppid());
			break;
		}else{
			printf("我是父进程:%d,创建子进程:%d\n",getpid(),id[i]);
		}
	}

	printf("Hello world!\n");
	getchar();//进程停留,等待输入
	return 0;	
}

//如果把id[i]==0这个条件改成id[i]!=0
//就是从父生四子变成子生子生子

进程的正常退出

调用进程退出 
其父进程调用wait/waitpid函数返回status的低8位。

进程退出之前 
先调用所有事先通过atexit/on_exit函数注册的函数, 
冲刷并关闭所有仍处于打开状态的标准I/O流, 
删除所有通过tmpfile函数创建的文件。
(tmpfile创建临时文件)

用EXIT_SUCCESS/EXIT_FAILURE
常量宏(可能是0/1)作参数,调用exit()函数表示成功/失败,提高平台兼容性。

该函数不会返回。
该函数的实现调用了_exit/_Exit函数。

_exit/_Exit
调用进程退出,其父进程调用wait/waitpid函数返回status的低8位。
进程退出之前,先关闭所有仍处于打开状态的文件描述符 
将其所有子进程托付给init进程(PID为1的进程)收养, 
向父进程递送SIGCHLD()信号。
该函数不会返回。

echo $?      可以用于在命令行获取退出值
-1   255  因为低8位
256的话就是  0

atexit()函数能够在结束前调用退出函数

可以用多个atexit 先执行下面的  3  2  1
atexit(quit1)
atexit(quit2)
atexit(quit3)
//执行与注册顺序相反
//可以用atexit/on_exit注册进程退出函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void quit1(void){
	printf("%d进程结束!\n",getpid());	
}

void quit2(void){
	printf("进程退出,关闭IO流!\n");	
}
void quit3(void){
	printf("进程退出,删除临时文件(tmpfile)!\n");	
}
int main(){
	atexit(quit1);//注册 进程退出函数 进程结束之前会调用quit1函数
	atexit(quit2);
	atexit(quit3);
	printf("main end!\n");
	return 0;	
}

on_exit()函数

on_exit()打印的是return 的值和data的地址
//执行与注册顺序相反
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

struct ES{
	int n;
	double d;
};
struct ES e = {9527,3.1415};
void quit1(int status,void *ptr){
	printf("status = %d,ptr = %p *ptr = %d\n",status,ptr,*(int*)ptr);	
}
void quit2(int status,void *ptr){
	printf("status = %d,*ptr = %s\n",status,(char*)ptr);	
}
void quit3(int status,void *ptr){
	struct ES e = *(struct ES*)ptr;
	printf("%p\n",ptr);
	printf("status = %d, *ptr = {%d,%lf}\n",status,e.n,e.d);
	printf("%d %lf\n",((struct ES*)ptr)->n,((struct ES*)ptr)->d);
}
int main(){
	int data = 1024;
	printf("&data = %p\n",&data);
	on_exit(quit1,&data);
	on_exit(quit2,"process over!");
	on_exit(quit3,&e);
	printf("%p\n",&e);
	printf("main end!\n");
	return 0;	
}

标C
exit   _exit
atexit  on_exit

gnu c
_Exit(status)

wait/waitpid

1. 当一个进程正常或异常终止时,内核向其父进程发送SIGCHLD信号。
父进程可以忽略该信号,或者提供一个针对该信号的信号处理函数,默认为忽略。
2. 父进程调用wait函数:
	1) 若所有子进程都在运行,则阻塞。 
	2) 若有一个子进程已终止,则返回该子进程的PID和终止状态(通过status参	数)。 
	3) 若没有需要等待子进程,则返回失败,errno为ECHILD。
3. 在任何一个子进程终止前,wait函数只能阻塞调用进程,而waitpid函数可以有更多选择。
4. 如果有一个子进程在wait函数被调用之前,已经终止并处于僵尸状态,wait函数会立即返回,并取得该子进程的终止状态。
5. 子进程的终止状态通过输出参数status返回给调用者,若不关心终止状态,可将此参数置空。
6. 子进程的终止状态可借助sys/wait.h中定义的参数宏查看:
WIFEXITED() 
子进程是否正常终止,是则通过WEXITSTATUS()宏,获取子进程调用exit/_exit/_Exit函数,所传递参数的低8位。 
 因此传给exit/_exit/_Exit函数的参数最好不要超过255。
?WIFSIGNALED() 
子进程是否异常终止,是则通过WTERMSIG()宏获取终止子进程的信号。
?WIFSTOPPED() 
子进程是否处于暂停,是则通过WSTOPSIG()宏获取暂停子进程的信号。
?WIFCONTINUED() 
子进程是否在暂停之后继续运行 

wait.c

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


int main(){
	pid_t id = fork();
	if(id == 0){//子进程
		printf("子进程:%d\n",getpid());
		int retdata = 0;
		scanf("%d",&retdata);
		//return retdata;
		exit(retdata);//_exit(retdata);_Exit(retdata);
	}
	int status = 0;
	int childid = wait(&status);
	//等待子进程结束 如果有一个子进程已经结束,立即返回,如果没有子进程结束,则等待 阻塞
	wait函数 用于获取子进程结束的返回值的低8位 但是不会把返回的结果放于status低八位,放在第八位到第16位,所以我们>>8  
	if(WIFEXITED(status)){
		printf("有一个子进程%d正常结束了,返回信息为:%d %d\n",childid,status>>8,WEXITSTATUS(status));
	}else{
		//printf("有一个子进程非正常结束!\n");	
		if(WIFSIGNALED(status)){
			printf("子进程%d是被%d信号异常终止的!\n",childid,WTERMSIG(status));	
		}
	}
	if(WIFSTOPPED(status)){
		printf("子进程暂停运行!\n");	
	}
	return 0;	
}

在这里插入图片描述

waitpid.c

waitpid 可以选择性的等待子进程
可以选择等待指定的子进程  也可以是任意子进程
可以选择阻塞等待  也可以选择非阻塞等待(WNOHANG)
WNOHANG 
非阻塞模式,若没有可用的子进程状态,则返回0。 
WUNTRACED 
若支持作业控制,且子进程处于暂停态,则返回其状态。
WCONTINUED 
若支持作业控制,且子进程暂停后继续,则返回其状态。
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

void showStatus(int status){
	if(WIFEXITED(status)){
		printf("进程正常结束,返回值为:%d\n",WEXITSTATUS(status));	
	}else if(WIFSIGNALED(status)){
		printf("进程异常终止,终止信号为:%d\n",WTERMSIG(status));	
	}
	
}
int main(){
	pid_t ids[5];
	int i;
	for(i=0;i<5;i++){
		ids[i] = fork();
		if(ids[i]==-1){
			perror("fork");
			return -1;
		}
		if(ids[i] == 0){//子进程跳出循环
			break;	
		}
	}

	if(i<5){//子进程
		printf("******我是子进程:%d, 是第%d个进程!\n",getpid(),i+1);
		sleep(i*10);
		printf("******我是子进程:%d, 我死了!\n",getpid());
		return i+1;
	}
	
	int status = 0;
	for(i=0;;i++){
		pid_t inid = 0;
		printf("[-1:任意一个子进程 0:同一个进程组的任意子进程 >0:指定子进程]:");
		scanf("%d",&inid);
		//pid_t id = waitpid(inid,&status,0);	
		pid_t id = waitpid(inid,&status,WNOHANG);//非阻塞模式 
		//如果等待的子进程目前没有结束 直接返回
		if(id == -1){
			--i;
			if(errno == ECHILD){//errno==ECHILD 表明没有子进程了
				printf("所有的子进程都已退出!\n");	
				break;
			}
			continue;
		}else if(id == 0){
			printf("等待的子进程没有结束!\n");	
		}
		printf("有一个子进程:%d结束了,",id);
		showStatus(status);
	}
	return 0;	
}

exec

man execle
man execve

在这里插入图片描述

int execl(const char *path,const char *arg,…)
	path: 执行的命令或者执行的程序名   有带路径的
	例system(“a.out”);//执行a.out
	arg,…;main函数的参数列表  最后一个参数一定是NULL

int execlp(const char *file,const char *arg,…);
	file:执行的程序名   如果不带路径,从path环境变量中查找路径。但是也可以带路径
int execle(const char *path,const char *arg,…,char *const envp[])
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
int execpve(const char *file,char *const argv[],char *const envp[]);

int execve(const char *filename,char *const argv[],char *const envp[]);

数组都以空指针结束
+p:可以不带路径,若第一个参数中不包含“/”,则将其视为文件名,根据PATH环境变量搜索该文件。从path环境变量指定的目录下查找
+e:带环境列表的
+l:传arg使用可变长参数列表list
+v:传arg时用数组

evec.c

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

int main(int argc,char *argv[]){
	printf("进程号:%d 父进程:%d\n",getpid(),getppid());
	printf("main参数列表:\n");
	int i;
	for(i=0;i<argc;i++){
		printf("%s\n",argv[i]);	
	}
	printf("--------------------------------\n");
	printf("环境列表:\n");
	extern const char **environ;
	const char **env = environ;
	for(;*env!=NULL;env++){
		printf("%s\n",*env);	
	}
	return 0;	
}

exec.c

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

int main(){
	//函数名中没有p  程序必须带路径
	//l: 参数以可变参数列表形式传递
	//int ret = execl("/bin/ls","~","-l","-a",NULL);//excel创建一个新的进程替换掉当前进程
	//int ret = execlp("ls","~","-l","-a",NULL);//命令可以不带路径 PATH
	//int ret = execlp("/bin/ls","~","-l","-a",NULL);
	char* argv[] = {"~","-l","-a",NULL};
	char* envp[] = {"NAME=WD","AGE=18","PATH=/usr/bin:/bin/",NULL};
	//v: 参数列表用数组传递
	//int ret = execv("/bin/ls",argv);
	//int ret = execvp("ls",argv);
	//int ret = execle("test","test","a","b","123",NULL,envp);
	char* args[] = {"test","name","afbc","123",NULL};
	//int ret = execve("test",args,envp);
	int ret = execvpe("./test",args,envp);
	if(ret == -1){
		perror("execl");	
		return -1;
	}

	printf("main end!\n");//不会执行  exec函数会替换掉当前函数
	return 0;	
}

system

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


int main(){
	system("clear");//system("cls");//fork+exec
	system("ls");
	system("ps -elf");
	printf("hello\n");
	return 0;	
}

寻找一个宏定义

找出所有的.h文件
	find / -name “*.h”

sudo find / -name “*.h”
从文件中找这个宏
grep “context” 01.c
	查找关键字
sudo find / -name “*.h” | xargs grep “int”     
没有xargs就是把前面的文件内容作为查找的对象,从满足要求的文件中查找满足要求的内容
有了就是查找想要的内容

进程调度

算法:
1.先来先服务FCFS
	将进程变为就绪状态的先后次序排列成队,队列前面的先运行
2.时间片轮转RR
	分时系统中,将CPU的处理时间划分为一个个的时间片,按照队列中的进程轮流运行一个时间片
3.优先级算法
	给每个进程分配一个优先级(数值)
	优先级可以是静态的,也可以是动态的
	进程调度按照优先级进行调度
4.多级反馈队列
	综合上面几种方式

linux 采用的是基于优先级可抢占式的调度系统,并使用schedule函数来实现进程调度的功能
linux内核还不是可抢占式的,用户态才能被抢占
PRI静态优先级  80+NI
NI进程nice值

scanf,sleep的时候会返回可运行状态进程队列,等待的时候让出CPU

CPU暂停运行(收到信号)
SIGSTOP
唤醒
SIGCONT
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章           查看所有文章
加:2021-08-06 16:00:22  更:2021-08-06 16:00:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/17 10:00:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码