一、Linux系统编程:进程基础

1 进程基础

1.1 概念

  • 定义
    程序在计算机上的一次执行过程,执行中的程序。
  • 本质
    1、程序在地址空间中按照代码逻辑控制流执行
    2、资源分配最小单位

进程是一个抽象概念

1.2 进程与程序

  • 区别
进程程序
动态静态
有生命周期指令集合
只能对应一个程序可以对应多个进程
  • 从代码到程序
    在这里插入图片描述
  • 从程序到进程
    1、内核将程序读入内存,为程序镜像分配内存空间。
    2、内核为该进程分配进程标志符PID。
    3、内核为该进程保存PID及相应的进程状态信息。
    在这里插入图片描述
    进程控制块(PCB):保存进程控制信息

1.3 进程状态

在这里插入图片描述

  • 三态
    1、运行(running)态:程序正在CPU上执行
    2、就绪(ready)态:进程已获得到除CPU以外的所有必要的资源,获得CPU立即执行
    3、等待(wait)态:又称为阻塞(blocked)态或睡眠(sleep)态,指等待某个事件发生而无法执行时,放弃CPU
  • 五态
    1、创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态
    2、就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行
    3、执行状态:进程处于就绪状态被调度后,进程进入执行状态
    4、阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用
    5、终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行

1.3.1 查看进程状态

查看进程的命令

OS命令e.g.
Windowstasklisttasklist /FI "PID eq 进程PID"
Linuxps / pstree / top-

1、ps命令

  • 查看某进程

(1)通过进程PID查看:ps -p 进程PID

[root@localhost 1]# ps -p 3179
   PID TTY          TIME CMD
  3179 pts/0    00:04:08 a.out

(2)通过命令行查看:ps -C 命令行

[root@localhost 1]# ps -C a.out 
   PID TTY          TIME CMD
  3179 pts/0    00:02:19 a.out

(3)查看程序的PID: pidof 程序名

[root@localhost 1]# pidof a.out
3179
  • 查看进程
风格命令属性说明
BSD风格ps auxa: 终端上所有用户的进程; u:以用户为中心显示详细信息, x:无终端进程
System V风格ps -efe:所有进程; f:树状显示
  • 列表示说明
    ps aux
    在这里插入图片描述
    ps -ef
    在这里插入图片描述
标识含义
USER用户
PID进程ID
%CPU进程占用的CPU百分比
%MEM占用内存的百分比
VSZ进程虚拟大小
RSS常驻内存(内存中页的数量)
TTY终端ID
STAT进程状态
START启动进程的时间
TIME进程消耗CPU的时间
COMMAND命令的名称和参数
  • 进程状态标识
标识含义
D不可中断Uninterruptible(usually IO)
R正在运行,或在队列中的进程
S处于休眠状态
T停止或被追踪
Z僵尸进程
W进入内存交换(从内核2.6开始无效)
X死掉的进程
<高优先级
n低优先级
s包含子进程
+位于后台的进程组

2、pstree命令
以树状图的方式展现进程之间的派生关系
需要安装

yum install psmisc

在这里插入图片描述
3、 top命令
实时显示系统中各个进程的资源占用,类似Windows任务管理器。
在这里插入图片描述

1.3.2 获取PID

进程标识pid:进程身份证号

函数接口
pid_t getpid()获取当前进程ID
pid_t getppid()获取当前进程父进程ID

实例:

#include <iostream>
#include <unistd.h>
using namespace std;
int main(){
    cout << getpid() << " " << getppid() << endl;//父进程创建的子进程
}
[root@localhost 1]# ps
   PID TTY          TIME CMD
  2837 pts/0    00:00:00 bash
  3758 pts/0    00:00:00 ps
[root@localhost 1]# ./a.out 
3766 2837

可以看到bashpid2837 ,而进程a.outppid也是2837,是因为bash创建的a.out

1.3.3 如何杀死进程

OS命令
Windowstaskkill /F /PID 进程标识/taskkill /F /IM 程序名
Linuxkill 进程标识PID

1、执行死循环

#include <iostream>
using namespace std;
int main(){
    for(;;){}
}

2、Crtl+z可以停止进程,bg可以打入后台执行,fg可以把执行程序放入前端

[root@localhost 1]# ./a.out 
^Z
[1]+  Stopped                 ./a.out
[root@localhost 1]# bg
[1]+ ./a.out &
[root@localhost 1]# fg
./a.out

3、放入后端之后执行ps或者top可以看到后台执行的a.out
也可以用jobs可以查看所有后台执行的程序

[root@localhost 1]# bg
[1]+ ./a.out &
[root@localhost 1]# ps
   PID TTY          TIME CMD
  2837 pts/0    00:00:00 bash
  3461 pts/0    00:00:26 a.out
  3494 pts/0    00:00:00 ps

4、kill杀死进程

[root@localhost 1]# kill 3461
[1]+  Terminated              ./a.out
[root@localhost 1]# ps
   PID TTY          TIME CMD
  2837 pts/0    00:00:00 bash
  3508 pts/0    00:00:00 ps

1.4 如何创建进程

1.4.1 分叉函数fork()

  • 返回值
返回值含义
-1失败
0子进程逻辑控制流
其他(子进程PID)父进程逻辑控制流
  • 特点
    (1)调用一次,返回两次
    (2)相同但是独立的地址空间
    (3)并发(交叉)执行
    (4)共享文件

实例1:调用一次,返回两次

#include <stdio.h>
#include <unistd.h>
 int main(){
    printf("PID:%d,PPID:%d\n",getpid(),getppid());
    pid_t pid = fork();
    if(pid == 0){// child
        printf("this is child\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }else{
        printf("this is father\n");
        printf("res:%d,PID:%d,PPID:%d\n",pid,getpid(),getppid());
    }
}

执行结果:

PID:4183,PPID:2837
this is father
res:4184,PID:4183,PPID:2837
this is child
res:0,PID:4184,PPID:2294

分析:父进程pid4183,返回值为4184,创建了pid4184的子进程,子进程的返回值为0.

实例2:相同但是独立的地址空间&并发执行

#include <stdio.h>
#include <unistd.h>
int main(){
    int j=0;
    pid_t pid = fork();
    if(pid == 0){// child
        int k;
        for(k=0;k<150;k++)
            printf("this is child: %p\t j%d\n",&j,++j);
    }else{
        int k;
        for(k=0;k<150;k++)
            printf("this is father: %p\t j%d\n",&j,--j);
    }
}
this is father: 0x7fff15c2a240	 j-147
this is father: 0x7fff15c2a240	 j-148
this is father: 0x7fff15c2a240	 j-149
this is father: 0x7fff15c2a240	 j-150
this is child: 0x7fff15c2a240	 j1
this is child: 0x7fff15c2a240	 j2
this is child: 0x7fff15c2a240	 j3
this is child: 0x7fff15c2a240	 j4
this is child: 0x7fff15c2a240	 j5
...
...
this is child: 0x7fff15c2a240	 j147
this is child: 0x7fff15c2a240	 j148
this is child: 0x7fff15c2a240	 j149
this is child: 0x7fff15c2a240	 j150

说明:
1、父子进程访问共同的地址,但是有各自的执行结果,是因为父类为子类创造了一块与父类完全相同的虚拟内存;
2、因执行过程快,看不到并发执行的过程,实际为并发执行。

实例3:共享文件

#include <stdio.h>
#include <unistd.h>
int i = 100;
int main(){
    int j=100;
    FILE* fd = fopen("./test","w+");
    pid_t pid = fork();
    if(pid == 0){// child
        int k;
        for(k=0;k<10;k++)
            fprintf(fd,"this is child: i%d\t j%d\n",++i,++j);
    }else{
        int k;
        for(k=0;k<10;k++)
            fprintf(fd,"this is father: i%d\t j%d\n",--i,--j);
    }
    fprintf(fd,"%d\t%d\n",i,j);
}
[root@localhost internet]# ./a.out 
[root@localhost internet]# cat test 
this is father: i99	 j99
this is father: i98	 j98
this is father: i97	 j97
this is father: i96	 j96
this is father: i95	 j95
this is father: i94	 j94
this is father: i93	 j93
this is father: i92	 j92
this is father: i91	 j91
this is father: i90	 j90
90	90
this is child: i101	 j101
this is child: i102	 j102
this is child: i103	 j103
this is child: i104	 j104
this is child: i105	 j105
this is child: i106	 j106
this is child: i107	 j107
this is child: i108	 j108
this is child: i109	 j109
this is child: i110	 j110
110	110
  • 本质
    复制+分叉
    在这里插入图片描述
概念状态硬件特点
并发(concurrency)两个或者多个进程在同时存在单核进程指令同时或者交错执行。
并行(parallellism)两个或者多个进程在同时执行多核一种特殊的并发

1.4.2 执行函数exec()

exec() 特点:

  • 一次调用,失败返回
  • 改朝换代,取而代之
    PID不变
    地址空间内容变化

实例:

#include <iostream>
#include <unistd.h>
using namespace std;

extern char** environ;
int main(){
    //g++ exe.cpp -o exec
    //execl("/bin/g++","g++","exec.cpp","-o","exec",0);
    //execle("/bin/g++","g++","exec.cpp","-o","exec",0,environ);
    //execlp("/g++","g++","exec.cpp","-o","exec",0);
    char* cmd[] = {"g++","exec.cpp","-o","exec2",NULL};

    //execv("/bin/g++",cmd);
    //execve("/bin/g++",cmd,environ);
    //execvp("g++",cmd);
    execl("/bin/ps","ps",NULL);
    cout << getpid() << endl;
    return 0;
}
[root@localhost 1]# ./a.out 
   PID TTY          TIME CMD
  2837 pts/0    00:00:00 bash
  5094 pts/0    00:00:00 ps
  • which g++可以查看g++命令所在地址

1.4.3 系统函数system()

  • 特点
    一次调用,一次返回
  • 本质
    shell执行命令/程序

实例:

#include <iostream>
#include <unistd.h>
#include <cstdlib>
using namespace std;
int main(){
    cout << getpid() << endl;
    system("ps -o pid,ppid,cmd,s");
    cout << getpid() << endl;
    return 3;
}
[root@localhost 1]# g++ system.cpp 
[root@localhost 1]# ./a.out 
5275
   PID   PPID CMD                         S
  2837   2828 bash                        S
  5275   2837 ./a.out                     S
  5276   5275 ps -o pid,ppid,cmd,s        R
5275

./a.out的父进程是bashps -o pid,ppid,cmd,s 的父进程是./a.out

1.5 如何结束进程

方式说明
main函数退出只能用在main函数内
调用exit()函数一般用在main函数以外的函数
调用_exit()函数一般用来结束子进程
调用abort()函数一般用来异常退出
信号终止终止其他进程

1.6 如何停止进程

1.6.1 休眠

int sleep(unsigned int secs)
  • 参数
    secs指定休眠的秒数,-1表示永久休眠
  • 返回值
    未休眠的秒数
  • 特性
    如果没有信号中断,休眠指定秒数返回0,否则马上返回未休眠的秒数。

实例:计数器

#include <iostream>
#include <unistd.h>
#include <cstdio>
using namespace std;
int main(){
    for(int i = 0;i < 10;i++){
        //cout << i << endl;
        printf("\r%d",i);
        fflush(stdout);
        sleep(1);
    }
}

1.6.2 等待

pid_t wait(int* status):等价pid_t waitpid(-1,stauts,0)
pid_t waitpid(pid_t pid,int * status,int options)
  • 参数
参数含义说明
pid等待的进程1、<-1:等待进程组为pid的所有进程;2、-1: 等待任何子进程;3、0:等待同组的进程;4、>0:进程为pid 的子进程
status子进程结束状态1、判断正常结束:使用WIFEXITED(status);2、判断异常结束使用WIFSIGNALED(status);3、判断暂停使用WIFSTOPPED(status)
options选项WNOHANG若子进程没有结束,返回0,不予以等待;若子进程结束,返回该子进程的ID。WUNTRACED若子进程进入暂停状态,则马上返回,但子进程的结束状态不予以理会。
  • 返回值
返回值含义
-1失败
其他等待的PID

1.7 特殊进程

概念出现条件导致结果是否有害
孤儿进程父进程先于子进程退出init进程作为新的父进程无害
僵尸进程子进程退出,父进程没有获取子进程的状态信息调用waitwaitpid有害,避免出现僵尸进程

1.7.1 僵尸进程

实例:
1、子进程执行完先退出,父进程一直执行

#include <iostream>
#include <unistd.h>
using namespace std;

int main(){
     pid_t pid = fork();
     if(pid == 0){
         cout << "Child:" << getpid() << endl;
     }else{
        for(;;){}
     }
}

2、执行,Crtl+z停止,执行bg打入后台,查看进程状态

[root@localhost fork]# ./a.out 
Child:5714
^Z
[1]+  Stopped                 ./a.out
[root@localhost fork]# bg
[1]+ ./a.out &
[root@localhost fork]# ps
   PID TTY          TIME CMD
  2837 pts/0    00:00:00 bash
  5713 pts/0    00:00:09 a.out
  5714 pts/0    00:00:00 a.out <defunct>
  5728 pts/0    00:00:00 ps
[root@localhost fork]# ps -o pid,ppid,cmd,s 
   PID   PPID CMD                         S
  2837   2828 bash                        S
  5713   2837 ./a.out                     R
  5714   5713 [a.out] <defunct>           Z
  5750   2837 ps -o pid,ppid,cmd,s        R

3、此时子进程为僵尸进程,无法通过kill pid杀死

[root@localhost fork]# kill 5714
[root@localhost fork]# ps -o pid,ppid,cmd,s 
   PID   PPID CMD                         S
  2837   2828 bash                        S
  5713   2837 ./a.out                     R
  5714   5713 [a.out] <defunct>           Z
  5827   2837 ps -o pid,ppid,cmd,s        R

4、只能通过杀死父进程来杀掉僵尸进程

[root@localhost fork]# kill 5713
[1]+  Terminated              ./a.out
[root@localhost fork]# ps -o pid,ppid,cmd,s 
   PID   PPID CMD                         S
  2837   2828 bash                        S
  5848   2837 ps -o pid,ppid,cmd,s        R

如何避免僵尸进程?

1、父进程等待子进程退出
实例:父进程加入wait(),帮助回收子进程

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
using namespace std;

int main(){
     pid_t pid = fork();
     if(pid == 0){
         cout << "Child:" << getpid() << endl;
     }else{
        for(;;){
            sleep(1);
            wait(NULL);
        }
     }
}
[root@localhost fork]# ./a.out 
Child:6121
^Z
[1]+  Stopped                 ./a.out
[root@localhost fork]# bg
[1]+ ./a.out &
[root@localhost fork]# ps -o pid,ppid,cmd,s 
   PID   PPID CMD                         S
  2837   2828 bash                        S
  6120   2837 ./a.out                     S
  6135   2837 ps -o pid,ppid,cmd,s        R

实例2:

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;
//子进程退回后父进程还在跑,占用资源,会出现僵尸进程,无法用KILL杀掉,只能通过杀掉父进程。
//如何避免僵尸进程?使用wait(NULL);
int main(){
    cout << getpid() << endl;
    pid_t pid = fork();
    cout << pid << endl;
    if(pid == 0){
        sleep(5);
        cout << "child:"<< getpid() << endl;
    }else{
        cout << "before wait" << endl;
        wait(NULL);
        cout << "end wait" << endl;
    }
}
[root@localhost fork]# ./a.out 
6261
6262
before wait
0
child:6262
end wait

执行过程:
1、打印父进程pid,子进程和父进程同时执行,执行fork()后,打印父进程的返回值(即子进程的pid),父进程打印before wait,子进程打印返回值(即0)。
2、子进程等待5s,子进程打印child:pid后结束。父进程执行wait(NULL)杀掉僵尸进程,打印end wait,父进程结束。

注:一般调用fork(),都要用wait();

2、更加安全的方式
实例:

#include <iostream>
#include <list>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;
int main(){
    cout << getpid() << endl;
    list<pid_t> pids;
    pid_t pid = -1;
    for(int i = 0;i < 10;i++){
    	if(pid == 0){
	    sleep(5);
    	    cout << "pid:"<< getpid() << endl;
    	}else{
	    pid = fork();
	    pids.push_front(pid);
	}
    }
    if(pid != 0){
	for(auto pid:pids){
            cout << "before wait:"<< pid << endl;
            waitpid(pid,NULL,0);
            cout << "end wait:" << pid << endl;
	}
        pause();
    }
}

自动调用handler杀掉僵尸进程

#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>

using namespace std;
void handler(int){
    pid_t pid = wait(NULL);
    printf("%d exit\n",pid);
}
int main(){
    signal(SIGCHLD,handler); //当一个子进程结束后,系统自动调用handler杀掉僵尸进程
    pid_t pid = -1;
    for(int i = 0;i < 10;i++){
    	if(pid == 0){
	    sleep(5);
	    printf("pid:%d\n",getpid());
    	}else{
	    pid = fork();
	}
    }
}

运行过程:第一编循环pid!=0生成一个子进程和一个父进程,第二次循环,父进程pid=0继续生成子进程,子进程等待5s输出,然后继续下一次循环。最后一次循环父进程一共生成9个子进程,每一个子进程结束都会调用handler函数收尸。

注:子进程退出时候,会给到SIGCHLD一个信号,父进程执行handler自动收尸

1.7.2 孤儿进程

实例1:孤儿进程

#include <iostream>
#include <unistd.h>
using namespace std;
//子进程在跑,而父进程提前退出,会出现孤儿进程
int main(){
    if(fork()){
    	cout << "parent:" << getpid() << endl;
	sleep(10);
    	cout << "parent:" << getpid() << "exit" << endl;
    }else{
    	cout << "child:" << getpid() << endl;
	sleep(50);
    	cout << "child:" << getpid() << "exit" << endl;
    }
}
[root@localhost fork]# ./a.out 
parent:8433
child:8434
parent:8433exit
[root@localhost fork]# ps
   PID TTY          TIME CMD
  2837 pts/0    00:00:00 bash
  8434 pts/0    00:00:00 a.out
  8458 pts/0    00:00:00 ps

父进程退出,子进程还在跑,出现孤儿进程,孤儿进程无害,最终会被回收。

实例2:孤儿进程

#include <iostream>
#include <unistd.h>
using namespace std;
//孤儿进程无害,僵尸进程有害
int main(){
    pid_t pid = -1;
    for(int i = 0;i < 10;i++){
        if(pid == 0){
             sleep(5);
             cout << i << " child pid: " << getpid() << endl;
        }else{
             pid = fork();
        }
    }
}
7 child pid: 8554
8 child pid: 8555
9 child pid: 8556
6 child pid: 8553
8 child pid: 8554
9 child pid: 8555
7 child pid: 8553
9 child pid: 8554
8 child pid: 8553
9 child pid: 8553

1.8 进程间的通信(Inter-Process Communication,IPC)方式

  • (1)管道:
    它是半双工的,具有固定的读端和写端;它只能用于父子进程或者兄弟进程之间的进程的通信;
    它可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
  • (2)FIFO:
    可以在无关的进程之间交换数据,与无名管道不同;
  • (3)消息队列
    基本原理:
    A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。
    特点:
    消息队列是保存在内核中的消息链表,每个消息体都是固定⼤⼩的存储块。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
    如果没有释放消息队列或者没有关闭操作系统,消息队列会⼀直存在。
  • (4)信号量
    信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
    特点:
    1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
    2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
    3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
    4)支持信号量组。
  • (4)共享内存
    共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区;
    共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值