【系统编程】进程(概念、Linux命令、诞生与死亡、创建、退出、回收)

 linux系统编程大纲
1.
进程概念、进程诞生与死亡、进程函数接口、进程意义
2. 进程通信方式,有名管道、无名管道、信号、消息队列、共享内存、信号量。
3. 进程信号集、如何设置阻塞属性。
4. 线程概念,线程与进程区别?线程诞生与死亡,函数接口。
5. 线程同步互斥方式,有名信号量,无名信号量,互斥锁,读写锁,条件变量。
6. 拓展 -> 线程池  -> 同时处理多个任务。


目录

一、进程概念

二、关于查看进程信息的linux命令

三、进程诞生与死亡

四、信号

五、进程创建

六、进程回收

七、进程退出

八、从内存角度分析父子进程资源问题



一、进程概念

1. 什么是程序?什么是进程?二者有什么区别?
     程序是一堆待执行的代码。  -> 静态的文本数据。  project.c (C程序) /  project (可执行程序)
     进程是当程序被CPU加载时,根据每一行代码做出相应的动作,才能形成一个真正动态的过程,那么这个过程就称之为进程!  -> 动态过程!

2. 如何在linux中开启新的进程?
     在linux执行程序即可。
     程序: project
    开启新的进程: ./project  -> 开启进程!

3. 当程序被执行时,除了在内存空间中分配空间之外,还会分配一个task_struct结构体给进程。
   也就是说:每启动一个进程,就会得到一个task_struct结构体!

  结构体在哪里?  -> 其实在一个头文件中: /usr/src/linux-headers-3.5.0-23/include/linux/sched.h  ->1229行。

4. 进程是资源分配的最小单位。所有的进程都是1号进程的子进程,一个进程至少有一个线程,也可以有多个线程。

5. 进程内存分布:每个进程一般都拥有4G的虚拟内存(真实并没有)。创建一个子进程,子进程也拥有独立的4G虚拟内存,并且复制父进程的运行状态(代码都是一样的,并且代码运行到哪都一样)

二、关于查看进程信息的linux命令

1. 查看整个系统所有进程的关系网  -> pstree
gec@ubuntu:~$ pstree
init─┬─NetworkManager───{NetworkManager}
      ├─accounts-daemon───{accounts-daemon}
      ├─acpid
      ├─anacron
      ├─gnome-terminal─┬─bash───pstree

init进程称之为"祖先进程"
init进程有5个子进程,分别是:NetworkManager、 accounts-daemon、acpid、anacron、gnome-terminal
gnome-terminal进程有1个子进程是bash进程
bash进程有1个子进程是pstree进程。

2. 查看系统所有进程的PID号  ->  ps -ef
gec@ubuntu:~$ ps -ef
UID        PID      PPID         C          STIME             TTY          TIME         CMD
root         1           0             0           23:04               ?            00:00:00        /sbin/init   -> 祖先进程
gec       2250       1             0           23:04               ?            00:00:01       gnome-terminal
gec       2259  2250            0          23:04                pts/0       00:00:00       bash
gec       2646  2259            0          23:57                pts/0       00:00:00       ps -ef

UID -> 用户名,谁创建这个进程的!
PID -> 自身进程的ID号
PPID -> 父进程的PID号
CMD -> 进程的名字

祖先进程的PID一定是等于1

3. 查看进程CPU占用率/当前系统总进程数/进程状态个数  -> top
gec@ubuntu:~$ top   -> 按"q"返回终端!

Tasks: 151 total        -> 当前系统总进程数
3 running                  -> 3个进程正在运行
148 sleeping             -> 148个正在睡眠
0 stopped                  -> 0个暂停
0 zombie                   -> 0个僵尸

                                                                    %CPU
997   root         20   0 99824  29m 6700 S    1.7      3.0   0:09.39 Xorg
2250  gec        20   0 92272  15m  11m S     0.7     1.6    0:02.33 gnome-terminal  

%CPU  -> CPU使用率  -> 动态更新

 

三、进程诞生与死亡

1、进程的状态

      当执行一个程序,就诞生了一个新的进程。

      就绪态 ---》等待cpu资源,不占用CPU资源,不运行代码

      运行态 ---》得到cpu资源,运行代码

      僵尸态 ---》占用cpu的资源,不运行代码,不可以切换到就绪态/运行态

      死亡态 ---》正常的结束,不占用cpu资源,不运行代码

       睡眠态 ---》sleep()函数之类的

       暂停态 ---》收到19) SIGSTOP信号(查看信号的命令:kill -l,占用CPU资源),不运行代码,可以切换到就绪态/运行态

进程的生老病死

2. 什么是占用CPU资源?
其实代表task_struct结构体资源没有被释放。

3. 进程的生老病死过程需要注意点:
1)进程在暂停态收到继续信号时,切换到就绪态,而不是运行态。
2)进程退出时,一定会变成僵尸态。
3)进程不可以同时拥有两个父亲。
4)孤儿进程特点: 当自己还在运行态时,父进程已经退出了,马上寻找init作为自己的继父。
5)init进程特点:一定会帮所有的孤儿回收资源。

四、信号

查看命令:kill -l

给进程发送信号:1、kill + 进程号     

                             2、kill + -代表信号的数字 + 进程名

进程可以设定要不要阻塞某个信号

进程收到信号,有三种反应:1、默认信号本来的动作     

                                               2、忽略,但是9号信号和19号信号不能被忽略   

                                               3、做自己自定义的事

 

五、进程创建

1、创建新的进程

 #include <unistd.h>

函数:pid_t fork(void);

说明:子进程跟父进程运行在分开的内存空间,意味着父子进程是互相独立的,谁先运行不一定。

返回值: -1 : 创建子进程失败

                0 : 表示在子进程中,但是0并不是子进程的id,只是为了区分父子进程

              >0 : 表示在父进程中,返回的pid是子进程的id

创建的子进程会复制一份一模一样的父进程的资源给子进程,包括程序计数器的指针,导致了子进程不能从头开始执行父进程的代码,只能执行fork()后面的代码

程序计数器:用来存储程序执行的指令,处理器会执行程序计数器中的指令

2、查看自身的PID号以及父进程的PID号     

getpid()         getppid()    -> man 2 getpid

功能: 获取ID号  
使用格式:
        #include <sys/types.h>
        #include <unistd.h>

       pid_t getpid(void);  -> 获取自身ID
       pid_t getppid(void); -> 获取父进程ID

    参数: 无
    返回值:  getpid() 返回值自身的ID号
                    getppid() 返回父进程的ID号

六、进程回收

理论上,子进程应该由父进程回收,但是有时候子进程没结束父进程就结束了,那么子进程理论上应该由1号进程来收养。

怎么避免子进程未结束父进程就结束了?

等待回收子进程:

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

     1、pid_t wait(int *status);

             功能: 使得子进程改变状态  wait for process to change state  -> 子进程从僵尸态变成死亡态

             status:填int*指针,那么指针保存子进程的退出状态
                          填NULL,不关注子进程的退出状态,只回收资源

            返回值:
                         成功: 退出的子进程的ID
                         失败: -1

 

    2、waitpid(pid_t pid, int *wstatus, int options);

参数:pid:

小于-1,取pid的绝对值,等待这个绝对值id进程组中的任意子进程

等于-1,等待任意进程,相当于wait();函数的效果

等于0,等待当前进程组的任意子进程

大于0,等当id位pid的子进程

七、进程退出

      1. 进程退出函数作用是什么?
           当进程执行了exit()/_exit(),整个进程就会马上退出。

          

     2. 函数如何使用?  
         exit()  ->  man 3 exit

                #include <stdlib.h>
                void exit(int status);

        _exit()  ->  man 2 _exit

               #include <unistd.h>
               void _exit(int status);

              status:进程的退出状态   -> 返回父进程(前提父进程必须主动回收资源)
              0   -> 代表进程正常退出
              非0 -> 代表进程异常退出

              返回值:无

 3. exit()/_exit()有什么区别?
       这两个函数功能类似,都是用于退出进程,但是两者缓冲区处理机制不一样

  exit()  -> 进程退出时,输出缓冲区数据,再退出!
  _exit()  -> 进程退出时,不会输出缓冲区数据,直接退出!

4.在程序中,exit()函数与return语句区别?

拓展结论:由于在main函数内调用return相当于程序的退出,所以在main函数内执行exit()与return是一样的。

八、从内存角度分析父子进程资源问题

父进程fork出一个子进程之后,会将自己资源拷贝一份资源(除了PID号)到子进程中。


练习1:让父进程打印hello,子进程打印apple,要求子进程一定要先打印。

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

int main()
{
	pid_t x;
	x = fork();
	if(x > 0) //父进程
	{
		usleep(100000);
		printf("helloworld!\n");
	}
	
	if(x == 0)//子进程
	{
		printf("apple!\n");
	}
	
	return 0;
}

  结论:使得父子进程处理不同的任务,只需要判断返回值即可!

 练习2: 在父进程中打印自己与孩子的PID号,在子进程中打印自己与父亲的PID号。要求子进程先执行。

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

int main()
{
	pid_t x;
	x = fork();
	if(x > 0) //父进程
	{
		usleep(100000);
		printf("parent pid = %d\n",getpid());
		printf("child pid = %d\n",x);
	}
	
	if(x == 0)//子进程
	{
		printf("apple!\n");
		printf("child pid = %d\n",getpid());
		printf("parent pid = %d\n",getppid());
	}
	
	return 0;
}

练习3: 验证孤儿进程的父进程是祖先进程。

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

int main()
{
	pid_t x;
	x = fork();
	if(x > 0) //父进程
	{
		sleep(2); //2秒之后,子进程就是一个孤儿进程
	}
	
	if(x == 0)//子进程
	{
		printf("my parent id = %d\n",getppid()); //id = 2892
		sleep(5);
		printf("my parent id = %d\n",getppid()); //id = 1
		printf("helloworld!\n");
	}
	
	return 0;
}

练习4:父进程还在,并且主动回收资源。

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

int main()
{
	pid_t x;
	x = fork();
	if(x > 0) //父进程
	{
		wait(NULL); //阻塞等待子进程的退出,然后再帮子进程回收资源。
		printf("hello!\n");
	}
	
	if(x == 0)//子进程
	{
		printf("apple!\n");  -> 退出时有父进程帮自己回收资源,所以我就不找继父。
	}
	
	return 0;
}

练习5:验证进程的生老病死.jpg  情况二,看看是不是父进程不退出,那么子进程永远都是一个僵尸

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

int main()
{
	pid_t x;
	int i;
	x = fork();
	if(x > 0)
	{
		for(i=0;i<20;i++)
		{
			printf("%d\n",i);
			sleep(1);
		}
	}
	
	if(x == 0)
	{
		sleep(3);   -> 在3~20秒内,子进程都是僵尸
		printf("I am exit!\n");
	}
	
	return 0;
	
}

 

 

 

 

 

 

 

 

 

 

 

发布了64 篇原创文章 · 获赞 82 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40602000/article/details/101165182
今日推荐