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 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux系统编程——进程管理 -> 正文阅读

[系统运维]Linux系统编程——进程管理

作者:token keyword


概述

  • 程序是存放在存储介质上的一个可执行文件
  • 进程是程序执行的过程
  • 进程的一次执行过程,其状态是变化的,其包括进程的创建、调度和消亡
  • 在 Linux 系统中,进程是管理事务的基本单元。进程拥有自己独立的处理环境和系统资源。
  • 可使用exec族函数可以由内核将程序读入内存,使其执行起来成为一个进程

进程状态

  • Linux下进程有5种运行状态:R 运行状态 ,S可中断等待状态 ,D不可中断等待状态,Z僵尸状态,T停止状态
  • 按进程运行的整个生命周期可以将进程简单划分为三种状态:
    • 就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间。
    • 运行态:该进程正在占用 CPU 运行
    • 阻塞态:正在执行的进程由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞态
  • 进程调度机制:时间片轮转上下文切换
    进程三态模型

进程控制块

  • 当进程执行时,系统会开辟一段内存空间存放与此进程相关的数据信息,而这个数据信息是通过结构体(task_struct)来存放,我们把这个存放进程相关数据信息的结构体称为进程控制块。操作系统就是通过这个进程控制块来操作控制进程。
  • 进程控制块存放在内核中(/usr/src/linux-headers-4.15.0-30/include/linux/sched.h

ps命令(shell)

  • Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程

  • ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程时间监控,应该用 top 工具

  • ps命令:ps [参数] 用来显示当前进程的状态
    命令参数:

    参数含义
    a显示所有进程
    -a显示同一终端下的所有程序
    -A显示所有进程
    c显示进程的真实名称
    N反向选择
    e等于“-A”
    e显示环境变量
    f显示程序间的关系
    H显示树状结构
    r显示当前终端的进程
    T显示当前终端的所有程序
    u指定用户的所有进程
    -au显示较详细的资讯
    -aux显示所有包含其他使用者的行程
    -C<命令>列出指定命令的状况
    –lines<行数>每页显示的行数
    –width<字符数>每页显示的字符数
    –help显示帮助信息
    –version显示版本显示

进程号

  • 每个进程都由一个进程号来标识,其类型为 pid_t(无符号整型),由系统随机分配,进程号的范围:0~32767

  • 进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用

  • 进程号PID:标识进程的一个非负整型数

  • 父进程号PPID:任何进程( 除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号

  • 进程组号PGID: 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号

  • PID 0 为交换进程swapper,进行进程调度

  • PID 1 为init进程,进行系统初始化,收留孤儿进程

  • 获取进程号的函数:

    #include <sys/types.h>
    #include <unistd.h>
    
    /**********************
    功能:获取本进程号(PID)
    参数:无
    返回值:本进程号
    *********************/
    pid_t getpid(void);
    
    /***************************************
    功能:获取调用此函数的进程的父进程号(PPID)
    参数:无
    返回值:调用此函数的进程的父进程号(PPID)
    参数为 0 时返回当前进程组号,否则返回参数指定的进程的进程组号
    ****************************************/
    pid_t getppid(void);
    
    /***************************************
    功能:获取进程组号(PGID)
    参数:pid:进程号
    返回值:参数为 0 时返回当前进程组号,否则返回参数指定的进程的进程组号
    ***************************************/	
     pid_t getpgid(pid_tpid);
    

进程的虚拟地址空间

  • 每个进程都有自己独立的4G内存空间,但实际上这个内存是不存在的,是虚拟内存,每次访问内存空间地址的时候,都需要翻译成实际物理地址

进程空间

  • 栈stack:存放局部变量,函数参数
  • 堆heap:动态分配内存
  • 未初始化数据段bss:未初始化的变量
  • 数据段static:全局变量,静态变量,静态局部变量
  • 代码段code: 包含了进程运行的程序机器语言指令。文本段具有只读属性,以防止进程通过错误指针意外修改自身指令
  • 系统之所以分成这么多个区域,主要基于以下考虑:
    • 代码段和数据段分开,运行时便于分开加载,在哈佛体系结构的处理器将取得更好得流水线效率。
    • 代码时依次执行的,是由处理器 PC 指针依次读入,而且代码可以被多个程序共享,数据在整个运行过程中有可能多次被调用,如果将代码和数据混合在一起将造成空间的浪费。
    • 临时数据以及需要再次使用的代码在运行时放入栈中,生命周期短,便于提高资源利用率。
    • 堆区可以由程序员分配和释放,以便用户自由分配,提高程序的灵活性。

创建一个进程fork()

  • 一个进程,包括代码、数据和分配给进程的资源。fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

  • 一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

  • 当一个c程序使用fork后,fork上面的代码里的数据两进程不共享,相互独立;下面的代码两个进程都会执行,如果要两进程执行不同任务需用fork的返回值区分

  • 函数原型:

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t fork(void);
    
  • 功能:用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程

  • 返回值:

    • 成功:返回两个值,子进程中返回 0,父进程中返回子进程号
    • 失败:返回-1
  • 如果父进程先于子进程完成退出,此时子进程失去父亲成为孤儿进程init进程(PID 1)成为其新的父进程

  • 示例代码:

    //fork函数测试
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int main()
    {
    	printf("before fork, pid:[%d]\n", getpid());
    	//创建子进程
    	//pid_t fork(void);
    	pid_t pid = fork();//两个进程会同时执行下面的代码
    	if(pid<0) //fork失败的情况
    	{
    		perror("fork error");
    		return -1;
    	}
    	else if(pid>0)//父进程
    	{
    		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
    		//sleep(1);
    	}
    	else if(pid==0) //子进程
    	{
    		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
    	}
    	
    	printf("after fork, pid:[%d]\n", getpid());
    
    	return 0;
    }
    
    

进程挂起

  • 进程在一定时间内没有任何动作,成为进程的挂起

  • sleep函数:

    #include<unistd.h>
    unsigned int sleep(unsigned int sec);
    
  • 功能:进程 挂起指定的秒数,直到指定时间用完或收到信号才解除挂起

  • 返回值:到指定时间返回0,有信号中断返回剩余秒数


进程等待

  • 进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵尸进程

  • 当一个进程正常或异常终止时,内核就向其父进程发送 SIGCHLD 信号,而父进程可以通过 wait() 或 waitpid()函数等待子进程结束,获取子进程结束时的状态,同时回收他们的资源

  • 父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

  • 原型:

    #include <sys/types.h>
    #include <sys/wait.h>
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);
    

wait

  • 父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

  • 参数status用来保存被收集进程退出时的一些状态

    • 参数设置为NULL:如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD
    • 参数设置为WIFEXITED(status): 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
    • 参数设置为WEXITSTATUS(status) :当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出WEXITSTATUS(status) 就会返回5

waitpid()

  • 比wait多了两个参数提供额外的选项

  • 功能:等待子进程终止,如果子进程终止了,此函数会回收子进程的资源。

  • 参数:

    pid:

    • pid > 0 等待进程 ID 等于 pid 的子进程
    • pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会等待它
    • pid = -1 等待任一子进程,此时 waitpid 和 wait 作用一样。
    • pid < -1 等待指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值

    options :

    • 0:同 wait(),阻塞父进程,等待子进程退出
    • WNOHANG:没有任何已经结束的子进程,则立即返回
    • WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)
  • 返回值:

    • 当正常返回的时候,waitpid() 返回收集到的已经回收子进程的进程号
    • 如果设置了选项 WNOHANG,而调用中 waitpid() 发现没有已退出的子进程可等待,则返回 0
    • 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在,如:当 pid 所对应的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid() 就会出错返回,这时 errno 被设置为 ECHILD

wait示例:

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>//pid_t的定义
#include <unistd.h>
#include <wait.h>
#include <errno.h>
#include <stdlib.h>
/***********************************************************
    功能说明:进程等待wait()方法的应用
    author: linux.sir@qq.com
***********************************************************/
void waitprocess();
int main(int argc, char * argv[])
{
  waitprocess();  
}
void waitprocess()
{
	int count = 0;
	pid_t pid = fork();//创建子进程,返回两个值,父进程返回子进程ID>0,子进程返回0
    int status = -1;  
    if(pid<0)
    {
   		printf("fork error for %m\n",errno );
    }else if(pid>0) {//父进程空间
    	printf("this is parent ,pid = %d\n",getpid() );
    	wait(&status);//父进程执行到此,马上阻塞自己,直到有子进程结束。当发现有子进程结束时,就会回收它的资源。    
    }else{ //子进程空间
    	printf("this is child , pid = %d , ppid = %d\n",getpid(),getppid());
    	int i;    
    	for (i = 0; i < 10; i++){
        	count++;
      		sleep(1);//进入睡眠,暂时释放时间片,给其他线程
      		printf("count = %d\n", count);      
        }
        exit(5); //非0表示子进程异常退出,返回至wait处,回收子进程资源
    }
    printf("child exit status is %d\n", WEXITSTATUS(status));//status是按位存储的状态信息,需要调用相应的宏来还原一下  
    printf("end of program from pid = %d\n",getpid() );  //父进程结束
}


进程退出

函数:

#include <stdlib.h>

void exit(int status);
/*
功能:
	结束调用此函数的进程。
参数:
	status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。
返回值:
	无
*/


#include <unistd.h>

void _exit(int status);
/*
功能:
	结束调用此函数的进程。
参数:
	status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。
返回值:
	无
*/
    
#include <stdlib.h>

void _Exit(int status);
/*
功能:
	结束调用此函数的进程。
参数:
	status:返回给父进程的参数(低 8 位有效),至于这个参数是多少根据需要来填写。
返回值:
	无
*/
  • return仅调用堆栈的返回,将控制权交给主调函数,只在主函数里相当于exit;使用exit结束进程将控制权交给操作系统或父进程
  • exit()属于标准库函数,_exit()属于系统调用函数,调用 exit() 函数,会刷新 I/O 缓冲区而_exit()不会(使用_exit函数退出printf的调试信息可能不会被打印
    在这里插入图片描述

使用vfork创建进程

函数:

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

 pid_t vfork(void);
  • 子进程共享父进程的地址空间,所以vfork不会克隆父进程,子进程运行时父进程被挂起
  • vfork保证子进程先运行,在它调用 exec(进程替换) 或 exit(退出进程)之后父进程才可能被调度运行。如果子进程没有调用 exec, exit, 程序则会导致死锁
  • 由vfork创建进程时与父进程公用一块地址空间,那么他的创建进程的效率很高。但是fork后来实现了写时拷贝技术,他的效率大大提高,而且进程独立,所以vfork就被淘汰来

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
int main(int argc, char *argv[])
{
	pid_t pid;
	
	pid = vfork();	// 创建进程
	if(pid < 0){ // 出错
		perror("vfork");
	}
	
	if(0 == pid){ // 子进程
		sleep(3); // 延时 3 秒
		printf("i am son\n");
		
		_exit(0); // 退出子进程,必须
	}else if(pid > 0){ // 父进程
		
		printf("i am father\n");
	}
	
	return 0;
}

进程替换:exec函数族

  • exec函数族可以在进程中启动另一个程序,它可以根据指定的文件名或目录名找到可执行文件

  • 被启动的程序取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了,所以通常fork一个新进程给exec执行

  • 被启动的程序除了二进制文件,也可以是任何Linux下可执行的脚本文件,所以shell命令也可以用exec执行

  • 函数原型:

    #include <unistd.h>
    
    int execl(const char *path, const char *arg, ...
          /* (char  *) NULL */);
          
    int execlp(const char *file, const char *arg, ...
           /* (char  *) NULL */);
           
    int execle(const char *path, const char *arg, ...
           /*, (char *) NULL, char * const envp[] */);
           
    int execv(const char *path, char *const argv[]);
    
    int execvp(const char *file, char *const argv[]);
    
    int execvpe(const char *file, char *const argv[],
            char *const envp[]);
    
    
  • 函数返回值:调用成功函数不会返回;出错返回-1,失败原因记录在error中

  • exec族函数一个有6个,其中只有 execve() 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数

在这里插入图片描述

exec后面的字母:

  • l (list) 命令行参数列表

  • p (path) 搜素file时使用path变量

  • v (vector) 使用命令行参数数组

  • e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

参数说明:

  • 带l execl,execlp,execle:表示后边的参数以可变参数的形式给出且都以一个空指针结束。这里特别要说明的是,程序名也是参数,所以第一个参数就是程序名
  • 带p execlp,execvp:表示第一个参数无需给出具体的路径,只需给出函数名即可,系统会在PATH环境变量中寻找所对应的程序,如果没找到的话返回-1
  • 带v execv,execvp:表示命令所需的参数以char *arg[]形式给出且arg最后一个元素必须是NULL
  • 带e execle:将环境变量传递给需要替换的进程,原来的环境变量不再起作用

以PS举例:


char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};

execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-08 11:55:18  更:2021-08-08 11:55:40 
 
开发: 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年12日历 -2024/12/28 3:18:22-

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