Linux之应用层看进程

Linux的进程(从应用层看)

说明

  1. 进程是一个程序的一个执行的过程
  2. 进程是一个独立的可调度的任务
  3. 进程是系统资源分配的最小单位
  4. 进程是动态的;程序是静态的,保存在磁盘上的有序集合
  5. 进程具有并发性、交互性、独立性、动态性
  6. 进程有:交互式进程、批处理进程、守护进程

1. 查看所有进程命令

  1. 查看所有进程的命令

    ps aux

    1. 动态显示进程信息命令

    top

2. 进程关系树

列出关系树的命令 pstree

通过关系树可以知道最先启动的进程是 init(PID 1)

3. 程序中获取当前进程的pid

每个进程都有属于自己的pid, 父进程是ppid

1. 函数
pid_t getpid(void);//返回值:用来获取当前进程的PID
pid_t getppid(void);//返回值:parent //用来获取当前进程父进程的PID	
  1. 代码示例

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	printf("my id is %d\n",getpid()); //获取当前进程的PID
    	printf("my parent id is %d\n",getppid());//获取当前父进程的PID
    	return 0;
    }
    
    

4. 进程说明

  1. 进程PID的最大值

    32位 PID 32767

    1. pid号的顺序

    顺序增加

    1. pid的回收

    每次进程结束,系统自动回收PID,虽然回收了PID但是不会立刻使用,当系统所有的PID都用没了,才开始使用使用的PID

    1. 进程和终端

    所有运行在linux终端的进程,他们的父进程都是终端,如果终端结束,终端上的所有进程结束

6. 进程的状态

就绪态、运行态、阻塞态

7. 创建子进程

  1. 需要fork函数来

    include <unistd.h>

     pid_t fork(void);
    
  2. 功能

    用于创建一个子进程,当执行完fork就会出现一个新进程(新进程为子进程吗,原来的进程为父进程)

    fork执行完,子进程会复制 父进程所有的东西,并且从fork语句后面开始执行

    父进程创建子进程,子进程会copy父进程资源,然后两个进程空间完全独立,
    子进程某个变量改变,父进程的不变

    1. 例子
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	fork();//执行完此语句后,创建除了一个子进程,
    	printf("Hello World!!\n");
    	return 0;
    }
    

    会打印出来两个 Hello World,分别是父进程和子进程打印的

    1. 如何判断, 父进程和子进程

    通过函数的返回值判断区分是当前是父进程还是子进程

    pid_t //函数的返回值

    pid_t pid = fork();

    //pid == -1 //fork函数执行失败

    //pid > 0 //说明为父进程,并且pid的值为子进程的PID

    // pid == 0 //说明为子进程

  3. 代码例子

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

int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork failed");
		return -1;
	}
	else if(pid > 0)//说明为父进程
	{
		sleep(1);//加上延时,为了让子进程先运行
		printf("I am parent,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
	}
	else if(pid == 0)//说明为子进程
	{
		printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
	}
	return 0;
} 

8. _exit和exit的区别

  1. exit函数

    1. 执行此函数后,结束当前进程(在结束当前进程前会刷新缓存区)

    2. 头文件和函数原型

      include <stdlib.h>

       void exit(int status)
      
    3. 参数

      status是一个整形的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示结束

      其他值表示出现了错误

      1. 例子
      	#include <stdio.h>
      	#include <stdlib.h>
      
      	int main(int argc, const char *argv[])
      	{
      		printf("hello world!!!"); //注意此处没有换行符
      		exit(0); //执行词语句后,程序结束
      		printf("hahahahahahaha!!!"); //此语句未被执行
      		return 0;
      	}
      
  2. _exit函数

    1. 功能

      执行此函数后,结束当前进程,//注意:_exit()调用程序之后直接结束程序,不刷新缓存区

    2. 头文件和函数原型

      include <unistd.h>

      void _exit(int status);

    3. 例子

      	#include <stdio.h>
      	#include <unistd.h>
      	#include <stdlib.h>
      
      	int main(int argc, const char *argv[])
      	{
      		printf("hello world!!!");//注意此处没有换行符
      		_exit(0); //执行此语句后程序结束,但是没有刷新缓存区,hello world打印输出不成功,程序运行什么也不显示
      		printf("hahahahahahaha!!!"); //此语句依然未执行
      		return 0;
      	}
      

9. 父进程不应该比子进程先结束,父进程要负责回收子进程的资源

  1. 孤儿进程

    父进程先结束,子进程未结束,子进程默认会1号进程init(福利院)接管,回收子进程的资源

    1. 代码示例

      #include <stdio.h>
      #include <unistd.h>
      
      int main(int argc, const char *argv[])
      {
      	pid_t pid = fork();
      	if(pid == -1)
      	{
      		perror("fork failed");
      		return -1;
      	}
      	else if(pid > 0)//说明为父进程
      	{
      		printf("I am parent,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
      	}
      	else if(pid == 0)//说明为子进程
      	{
      		printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
      	}
      	return 0;
      } 
      

      /////打印输出结果
      I am parent,myid is 4490 myppid is 3821 pid is 4491
      linux@ubuntu:~/0703/prcess$ I am child,myid is 4491 myppid is 1 pid is 0

      /////发现子进程的父进程ID是1,为init

    2. 僵尸进程

    子进程先结束,父进程未结束,但是父进程未调用wait族函数来回收子进程的资源,此时子进程叫做僵尸进程

    1. 例子

      	#include <stdio.h>
      	#include <unistd.h>
      
      	int main(int argc, const char *argv[])
      	{
      		pid_t pid = fork();
      		if(pid == -1)
      		{
      			perror("fork failed");
      			return -1;
      		}
      		else if(pid > 0)//说明为父进程
      		{
      			while(1) //父进程未结束
      			{
      				;//父进程开心的坐着自己的事
      			}
      		}
      		else if(pid == 0)//说明为子进程
      		{
      			printf("child will over!!!\n");
      			printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
      			exit(0); //子进程结束 
      		}
      		return 0;
      	} 
      

      ////////////重新开启一个新的终端

      linux@ubuntu:~$ ps aux | grep a.out
      linux 4550 88.2 0.0 1984 284 pts/2 R+ 22:59 0:30 ./a.out
      linux 4551 0.0 0.0 0 0 pts/2 Z+ 22:59 0:00 [a.out] //Z+说明为僵尸状态,zombie,僵尸进程

  2. 如何回收子进程的资源

    调用wait族函数

    1. wait族函数

      1. 功能

        当父进程执行此函数,父进程阻塞等待子进程结束

        父进程执行到wait函数就会阻塞,等待子进程的结束,一旦有一个

      2. 头文件及函数原型

        #include <sys/types.h>
        #include <sys/wait.h>
        
        pid_t wait(int *status);
        
        wait(NULL);
        int statu;
        wait(&statu);
        
      3. 函数参数

        status是整形指针,指向的对象用来保存子进程退出时的状态

        status若是空,表示忽略子进程退出时的状态

        status若不为空,表示保存子进程退出时的状态

        获取结束信息:

          	1. WIFEXITED(status)     测子进程是否正常退出(调用exit或者_exit()或者从main返回)  如果是正常退出 返回真 否则返回假
              2. WEXITSTATUS(status)   这个宏使用的前提是子进程正常退出  它会获取子进程调用exit中  exit里的参数
        
      4. 函数返回值

        成功:

        pid_t //返回值时结束的那个子进程的id

        失败:

        -1

      5. 例子

        对获取的子进程的结束状态的验证

        status //输出参数,里面保存的是子进程结束时的状态

        pid_t //返回值是结束的那个子进程的id

        #include <stdio.h>
        #include <stdlib.h>
        #include <unistd.h>
        
        int main(int argc, const char *argv[])
        {
        	int status;
        	pid_t child_id;
        	pid_t pid = fork();
        	if(pid == -1)
        	{
        		perror("fork failed");
        		return -1;
        	}
        	else if(pid > 0)//说明为父进程
        	{
        	//	child_id = wait(NULL);//当父进程执行此语句后,处于阻塞状态,直到子进程结束后结束阻塞
        		child_id = wait(&status);//当父进程执行此语句后,处于阻塞状态,直到子进程结束后结束阻塞
        		printf("statu is %d\n",status);
        		printf("WIFEXITED(status) is %d WEXITSTATUS(status) is %d\n",WIFEXITED(status),WEXITSTATUS(status));
        	}
        	else if(pid == 0)//说明为子进程
        	{
        		int x = 5;
        		sleep(1);//虽然子进程加了延时,为了让父进程先运行,但是父进程执行wait()函数后处于阻塞状态
        		x /= 0;// 执行此语句后,程序挂掉了
        		printf("++++++++++++++++++++\n"); 
        		exit(5);
        	}
        	return 0;
        } 	
        
      6. 例子 对阻塞功能和返回值的验证

        #include <stdio.h>
        #include <unistd.h>
        
        int main(int argc, const char *argv[])
        {
        	pid_t child_id;
        	pid_t pid = fork();
        	if(pid == -1)
        	{
        		perror("fork failed");
        		return -1;
        	}
        	else if(pid > 0)//说明为父进程
        	{
        		child_id = wait(NULL);//当父进程执行此语句后,处于阻塞状态,直到子进程结束后结束阻塞
        		printf("child_id is %d\n",child_id);//wait函数解除阻塞后的返回值,为刚结束子进程的PID
        		printf("I am parent,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
        	}
        	else if(pid == 0)//说明为子进程
        	{
        		sleep(1);//虽然子进程加了延时,为了让父进程先运行,但是父进程执行wait()函数后处于阻塞状态
        		printf("I am child,myid is %d myppid is %d pid is %d\n",getpid(),getppid(),pid);
        	}
        	return 0;
        } 
        
  3. waitpid()函数

    1. 功能

      指定等待某一个子进程

    2. 头文件和函数原型

    #include <sys/types.h>
    #include <sys/wait.h>
    		   pid_t waitpid(pid_t pid, int *status, int options);
    		
    			waitpid(3830,NULL,WNOHANG); //不阻塞
    			waitpid(3830,NULL,0); //阻塞
    
    1. 参数说明

      pid_t pid //pid > 0 回收进程的ID

      int *status //同wait,用来保存子进程结束的状态的

      option:

      WNOHANG 表示不阻塞,waitpid不阻塞而立即返回,此时值为0,结束后返回值为结束子进程pid

      0 阻塞

    2. 返回值

      0: 已经结束运行的子进程进程号
      0: 使用WNOHANG且没有子进程退出时,一直返回0,当子进程结束的时候,返回的是子进程的PID
      -1:出错

    3. 应用举例

      waitpid(31111, NULL, 0);

      //指定阻塞等待PID 31111 子进程结束

    4. 例子

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <stdlib.h>
    int main()
    {
    	pid_t pid;
    	pid = fork();
    	if(pid == 0)
    	{
    		sleep(5);
    		
    		exit(0);
    	}
    	else if(pid > 0)
    	{
    		while(1)	//循环测试子进程是否退出
    		{
    			int ret = waitpid(pid,NULL,WNOHANG);	//调用waitpid,且父进程不阻塞
    			if(ret == 0)
    			{
    				puts("child is not exited!!");	//若子进程未退出,则父进程暂停1s
    				sleep(1);
    			}
    			else if(pid == ret)	//若发现子进程退出,打印输出相应情况
    			{
    				printf("child is exited!!\n");
    				break;
    			}
    			else
    			{
    				printf("some error occured!!\n");
    				break;
    			}
    		}
    	}
    	return 0;
    }
    

10. Linux的进程分类

  1. 前台进程

    前台进程占用终端

    1. 后台进程

    后台进程不占用终端

    1. 切换前后台的快捷键

    ./a.out & //将程序运行在后台

    jobs //查看后台进程的运行情况

    fg 1 //将后台进程编号为1的进程拿到前台运行

    ctrl + z //将前台进程放到后台并处于停止状态

    ctrl + c //结束进程

11. 如何编写守护进程

  1. 查看守护进程

    ps -axj

  2. 如何创建守护进程

    需要五个步骤

    1. 创建子进程,结束父进程(让其成为孤儿进程)
    2. 子进程创建新会话(setsid()函数)
    3. 修改当前目录(chdir(“/tmp”)) //不一定修改为这个目录
    4. 重置文件权限掩码(umask(0)) //不是必须的
    5. 关闭子进程从父进程复制过来的文件描述符
  3. 例子

    ///////打开一个文件,文件描述符从3开始,0、1、2已经被标准输入、标准输出、标准错误说出占用

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    
    int main(int argc, const char *argv[])
    {
    	pid_t pid = fork();
    	if(pid > 0)
    	{
    		//1.创建一个子进程,结束父进程
    		exit(0);
    	}
    	else if(pid == 0)
    	{
    		//2.创建一个新的会话,脱离原来会话,进程组,最主要脱离原来的控制终端
    		setsid();
    		//3.修改进程当前目录 
    		chdir("/tmp");
    		//4.重置文件权限掩码
    		umask(0);
    		//5.关闭文件描述符
    		int num = getdtablesize();
    		int i;
    		for(i = 0; i < num; i++)
    		{
    			close(i);
    		}
    		//////守护进程做的事//////
    		int fd = open("hello.txt",O_WRONLY | O_CREAT,0666);
    		while(1)
    		{
    			write(fd,"hello\n",strlen("hello\n")+1);
    			sleep(1);
    		}
    	}
    	return 0;
    
    }
    
  4. 例子创建守护进程,时间每隔两秒,向mydaemon.txt文件写入系统时间

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    #include <time.h>
    
    int main(int argc, const char *argv[])
    {
    	pid_t pid = fork();
    	if(pid > 0)
    	{
    		//1.创建一个子进程,结束父进程
    		exit(0);
    	}
    	else if(pid == 0)
    	{
    		//2.创建一个新的会话,脱离原来会话,进程组,最主要脱离原来的控制终端
    		setsid();
    		//3.修改进程当前目录 
    		chdir("/tmp");
    		//4.重置文件权限掩码
    		umask(0);
    		//5.关闭文件描述符
    		int num = getdtablesize();
    		int i;
    		for(i = 0; i < num; i++)
    		{
    			close(i);
    		
    
    		}
    		//////守护进程做的事//////
    		int fd = open("hello.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    		time_t rawtime;
    		char buf[100] = { 0 };
    		while(1)
    		{
    			time(&rawtime);
    			sprintf(buf,"%s",ctime(&rawtime));
    			write(fd,buf,strlen(buf));
    			sleep(2);
    		}
    	}
    	return 0;
    
    }
    

12. exec函数族(通过下面的函数可以执行另一个程序)

  1. 功能

    用fork函数创建子进程后,子进程往往要调用一种exec族函数来执行另一个程序。

    当进程调用一种exec函数时,该进程完全由新程序替换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段

    exec函数族, 一个进程中只会执行一次, 执行成功,原进程退出--新进程开始运行

    1. 头文件及函数原型
    (2)头文件及函数原型
    
    	#include <unistd.h>
    
    	int execl(const char *path, const char *arg, ...);
    	int execlp(const char *file, const char *arg, ...); 
    	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 execvpe(const char *file, char *const argv[],
    		  char *const envp[]);
    

    l //list 缩写 希望接收以逗号分隔的参数列表,列表以NULL作为结尾标志

    p //PATH 系统会按照PATH环境变量中路径进行查找命令

    e //enviroment 缩写 指定当前进程所使用的环境变量

    v //vertor 缩写 参数传递为以NULL结尾的字符指针数组

    六个函数返回:若出错则为- 1,若成功则不返回

    1. 例子 execl()
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	execl("/bin/ls","ls","-l",NULL); //因为没有p指定PATH环境变量,所以ls命令需要加上路径
    	return 0;
    }
    
    1. 例子 execv
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	char *args[] = {"ls","-l",NULL};//注意字符指针数组最后一个元素一定要是NULL
    
    	execv("/bin/ls",args);
    	return 0;
    }
    
    1. 例子 execvp
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	char *args[] = {"ls","-l",NULL};//注意字符指针数组最后一个元素一定要是NULL
    
    	execvp("ls",args);
    	return 0;
    }
    
    1. 例子 execle
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	char *envp[] = {"PATH=/tmp","NAME=zhangsan","HOME=haha",NULL};
    	execle("/usr/bin/env","env",NULL,envp);
    	return 0;
    }
    
    1. 例子 execvpe
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	char *args[] = {"env",NULL};
    	char *envp[] = {"PATH=/tmp","NAME=zhangsan","HOME=haha",NULL};
    	execvpe("env",args,envp);
    	return 0;
    }
    
    1. 例子 execlp 经常与多进程组合使用,用一个子进程单独执行execlp程序
    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, const char *argv[])
    {
    	pid_t pid;
    	pid = fork();
    	if(pid > 0)
    	{
    		wait(NULL);
    		printf("I am parent!!!\n");
    	}
    	else if(pid == 0)
    	{
    		execlp("date","date",NULL);
    		printf("hello world!!!\n");//此语句未被执行
    	}
    	return 0;
    }
    
posted @ 2021-05-12 09:51  make_wheels  阅读(188)  评论(0编辑  收藏  举报