文章目录
一.进程的概念
什么是进程?进程这个概念是针对系统而言,对于我们来说,我们面对的概念是程序。当输入指令执行一个程序的时候,系统将启动一个进程。
进程顾名思义就是正在进行的程序,或者说是正在运行的程序,
Linux 下一个进程在内存中有三部分数据: "代码段“ 、“堆栈段”和“数据段”。
1.代码段
代码段就是存放了程序代码
2.堆栈段
存放的是程序的返回地址、程序的参数以及程序的局部变量。
3.数据段
存放的是程序的全局变量,常数以及动态数据分配的数据空间(比如用 new 函数分配的空间)
4.注意事项
如果系统同时运行多个相同的程序,它们的“代码段”是相同的,但是“堆栈段” 和 “数据段” 是不同的(相同的程序,处理的数据同)
二.进程的编号
1.查看进程
(1)ps 查看当前终端的进程,ps 是 Process Status 的缩写。
(2)ps -ef 查看系统的全部进程
(3) ps -ef|more 查看系统的全部进程,结果分页显示
(4)ps-ef|grep 服务端 :查看系统全部的进程,然后从结果集中过滤出包含“服务端”这个词的结果。
2.一些常见的标识
UID : 启动进程的操作系统用户;UID,是用户身份证明(User Identification)的缩写。
PID : 进程编号;Process Identification 的缩写
PPID : 进程的父进程的编号;Parent Process Identification
C :CPU 使用的资源百分比
STIME:进程启动的时间 ;S——start
TTY:进程所属的终端;是系统进程的话,就是一个问号“?”
终端是一种字符型设备。它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写。Teletype是最早出现的一种终端 设备,很象电传打字机(或者说就是),是由Teletype公司生产的。设备名放在特殊文件目录/dev/下。
TIME:使用掉的CPU的时间
CMD:执行的是什么指令;command
3.getpid 库函数
getpid 库函数的功能是获取本程序运行时进程的编号。
(1)函数的声明:
pid_t getpid();
函数没有参数,返回值是进程的编号,pid_t 就是 typedef int pid_t。
(2)函数的代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("本程序的进程编号是:%d\n",getpid());
sleep(30); //是为了方便查看进程
}
1)执行程序
2)查看正在执行的程序
4.注意细节
(1)进程的编号是系统动态分配的,相同的程序在不同的时间执行,进程的编号是不同的。
getpid 这个程序在上文的执行时的编号是 77173,到了这里执行时,编号是77192
(2)进程的编号会循环使用,但是在同一时间,进程的编号是唯一的,也就是说:不管任何时间,系统不可能存在两个编号相同的进程。
三.多进程
fork 在英文中的意思是“分叉”,在一个进程中,如果使用了 fork 函数,就产生了另一进程,于是进程就“分叉” 了,所以这个名字取得很形象。有了多进程就可以提高CPU的利用率。
1.函数声明
pid_t fork();
fork 函数用于产生一个新的进程,函数的返回值 pid_t 是一个整数,在父进程中,返回值是子进程;在子进程中,返回值是0。
2.函数代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("本程序的进程编号是“:%d\n",getpid());
sleep(40);
int ipid=fork();
printf("fork ok\n");
sleep(1); //等待进程的生成
printf("pid=%d\n",ipid);
if( ipid!=0) printf("父进程的编号是:%d\n",getpid());
else printf("子进程的编号是:%d\n",getpid());
sleep(30); //方便查看
}
(1)运行效果:
(2)查进程编号
这里确实是生成了两个进程,一个是 77750,另一个是77753,其中 77753 是 77750 的子进程。
3.运行结果分析
(1)这里的 “fork ok” 为什么打印了两次?看看代码只有一行啊。
printf("fork ok\n");
(2)为什么父进程和子进程编号都打印了?这里可是 if else 语句,不应该只是打印其中一个进程的编号吗?
if( ipid!=0) printf("父进程的编号是:%d\n",getpid());
else printf("子进程的编号是:%d\n",getpid());
这到底发生了什么,调用了 fork() 函数 ,因为 fork() 函数创建了一个新的进程(子进程)与原来的进程(父进程)一模一样。还记得前面的影分身之术吗?复制了一模一样的。
或者说,像下面这个图一样。当程序执行到 fork 函数的时候,就分叉(创建了一个子进程),这两个进程都是要走完剩下的代码的,如果正常运行。
子进程和父进程使用相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦可是运行,它就复制了父进程的一切数据,然后各自运行,相互之间没有影响。
也就是说从下面这段代码之后,
int ipid = fork();
这个程序就有两个进程了。两个进程都会执行,剩下的代码,所以才会有上面的运行结果。
四.fork() 函数的返回值
fork() 函数对返回值做了特别地处理,调用 fork 函数之后,在子进程的中 fork() 的返回值是 0 。在父进程中 fork() 的返回值仍是原来进程的编号(父进程)。也就是说这个函数会返回两个值。
我们就可以通过 fork() 的返回值来区分父进程和子进程,然执行不同的代码
1.子进程和父进程执行不同代码示例
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void fatherFun()
{
printf("我是老子,我喜欢孩子他娘\n");
}
void childFun()
{
printf("我是孩子,我喜欢西施\n");
}
int main()
{
if( fork() >0 )
{
printf("这是父进程,将调用fatherFun()\n");
fatherFun();
}
else
{
printf("这是子进程,将调用childFun()\n");
childFun();
}
sleep(2);
printf("我执行完\n");
sleep(2);
}
运行效果:
2.父子进程分别操作变量
在上文已经提到,子进程拷贝了父进程的堆栈段和数据段,也就是说:在父进程中定义的变量子程序中会复制一份副本,fork 之后,父子进程对变量的操作,不会影响到彼此。
(1)测试代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int a=520;
if( fork()>0 )
{
a=521;
printf("父进程:我将521赋值给 a,a=%d\n",a);
}
else
{
a=250;
printf("子进程:我将250赋值给 a,a=%d\n",a);
}
sleep(1);
}
(2)运行效果
五.Linux 常识补充
在Linux系统中运行任何一个命令,比如 ls ,当运行一个命令时,系统会生成一个子进程来运行 ls 这个命令对应的程序。
用 ps -ef|more 来查看,Linux系统有两个最原始的进程,
然后由进程 2 去生成其他的进程
六.fork() 生成多个子进程
1.由父进程生成 10 个子进程
1.1 错误代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int pid[10];
printf("本程序的编号是:%d\n\n",getpid());
for(int ii=0;ii<10;ii++)
{
if( fork()==0)
{
printf("我是第%d个子进程,pid[%d]=%d\n",ii,ii,getpid());
}
}
sleep(1);
}
1.2 运行结果
1.3 错误分析
为什么会这样呢?我只是想生成10个子进程而已,但是这不止10个了。我先将这程序的流程图画出来就知道了。
从代码和流程图可以知道,当 fork()>0 时,程序会执行下一次循环,再生成一个子进程。当 fork()==0 时,程序会打印生成一个子进程,然后执行下一次循环。也就是说不打印生成子进程程序会执行下一次循环,在生成一个子进程;打印的话,也会执行下一次循环,再生成一个子进程。一次循环不止生成一个子程序。他们两个重复了。
1.4 改进方法
(1)解决方法就是让其中一个生成了一个子进程就跳出整个循环,不要参与其中。改进的代码如下,当 fork() ==0 时打印了一次后就马上跳出整个循环,不要再执行下一次循环再生成一个子进程了。当 fork()>0 时,不打印,马上执行下一次循环,生成一个子进程。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int pid[10];
printf("本程序的编号是:%d\n\n",getpid());
for(int ii=0;ii<10;ii++)
{
if( fork()==0)
{
printf("我是第%d个子进程,pid[%d]=%d\n",ii,ii,getpid());
return 0; //跳出整个循环
}
}
continue; //fork() >0,执行下一次循环,生成一个子进程
sleep(1);
}
(2)运行结果:
(3)用 ps -ef|grep 10个 查看进程编号,及子进程是由哪个进程生成的。
1.5 子进程的生成顺序
为什么子进程的生成顺序不一样,因为 fork() 生成子进程的时间太短了,有的子进程生成的时间点几乎是一致的,哪个先跑完(跑得更快)剩下的代码都不一定的。
只要在生成了一个子进程后,就设置一个等待的时间,让这个子进程先跑完就可以解决了。
运行结果:
2.由子进程生成多代子进程
(1)代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
for (int ii =0;ii<10;ii++)
{
if(fork()>0) return 0;
printf("第%d 代,编号为:%d\n",ii+1,getpid());
}
sleep(100);
}
~
(2)运行效果
(3)用命令 ps -ef|grep 查看
为什么这里不能看到10个子进程对应的父进程?