目录
进程
进程的概念
程序:二进制文件,占用的磁盘空间
进程: 启动的程序
所有的数据都在内存中,需要占用更多的系统资源
cpu,物理内存
并行和并发
1.并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
注:并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。如:打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结 束的动作。那么,就可以说听音乐和打游戏是并发的。
2.并行:当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
注:其实决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行
3.并行和并发的区别:
| 并发,指的是多个事情,在同一时间段内同时发生了。 |
| 并行,指的是多个事情,在同一时间点上同时发生了。 |
| 并发的多个任务之间是互相抢占资源的。并行的多个任务之间是不互相抢占资源的、 只有在多CPU或者一个CPU多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的 |
PCB(进程控制块)
Process Control Block
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,linux内核的进程控制块是task_struct结构体
/user/src/linux-headers-3.16.0-30/include/linux/sched.h头文件中可以查看struct task_struct结构体定义。内部 成员有很多,我们重点掌握以下部分即可。
| 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数 |
| 进程的状态,有就绪,运行,挂起,停止等状态 |
| 进程切换时需要保存和恢复一些CPU寄存器 |
| 描述虚拟地址空间的信息 |
| 描述控制终端的信息 |
| 当前工作目录(Cueernt Working Directory) |
| umask掩码 |
| 文件描述符,包含很多指向file结构体的指针 |
| 和信号相关的信息 |
| 用户id和组id,stat |
| 会话(Session)和进程组 |
| 进程可以适用的资源上线(Resource Limit),umilit -a |
进程的状态
进程基本的状态有五种,分别为初始态,就绪态,运行态,挂起态和终止态。 其中初始态为进程准备阶段,常常与就绪态结合来看。

进程的控制
fork函数
1.一个进程,包括代码、数据和分配给进程的资源。
2.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的 事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
3.一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
包含的头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t fork(void);
两个返回值:
=0:当前进程为子进程
>0:当前进程为父进程
‐1:出错
4.


5.用户数据一样,进程ID不一致。
1.fork函数的返回值
2.子进程创建成功之后,子进程的执行位置
3.父子进程的执行顺序 不一定
4.如何区分父子进程
6.getpid/getppid函数
getpid:得到当前进程的PID
getppid:得到当前进程的父进程的PID
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
int i = 0;
for(i = 0;i < 4;i++)
{
printf("----i = %d----\n",i);
}
pid = fork();
if(pid > 0)
{
printf("this is father progress %d\n",getpid());
}
else if(pid == 0)
{
printf("this is child progress %d,father progress is %d\n",getpid(),getppid());
}
for(i = 0;i < 4;i++)
{
printf("i = %d\n",i);
}
return 0;
}

ps和kill命令
ps
查看进程信息
部分参数:
a : 显示现行终端机下的所有程序,包括其他用户的程序
u: 以用户为主的格式来显示程序状况
x: 显示所有程序,不以终端机来区分
ajx


父进程id pid 组id 回话id
kill
向指定的进程发送信号
kill可将指定的信息送至程序。预设的信息为SIGTERM(15),可将指定程序终止。若仍无法终止该程序,可使用 SIGKILL(9)信息尝试强制删除程序。程序或工作的编号可利用ps指令或job指令查看。
参数:
-a:当处理当前进程时,不限制命令名和进程号的对应关系;
-l:若不加选项,则-l参数会列出全部的信息名称;
-p:指定kill 命令只打印相关进程的进程号,而不发送任何信号;
-s:指定要送出的信息;
-u:指定用户。
#include <stdio.h>
int main()
{
while(1)
{
printf("hello world!\n");
sleep(1);
}
return 0;
}

注:kill hello.c 是不对的,kill执行程序
父子进程间的数据共享
1.fork之后两个地址空间区数据完全相同
2.后续各自进行了不同的操作
父进程:num‐‐
子进程:num++
物理地址:i -> f_num,z_num
3.各个进程的地址空间中的数据是完全独立的。对于同一个变量,读时共享,写的时候分别在物理地址上拷贝一份变量进行单独读写
4.父子进程之间可不可以通过全局变量通信? 不能,两个进程内存不能共享
exec函数族
1.让父子进程来执行不相干的操作,能够替换进程地址空间的代码.text段,执行另外的程序,不需要创建额外的的地址空间,当前程序中调用另外一个应用程序。
2.指定执行目录下的程序
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
path : 要执行程序的路径(最好是绝对路径)
变参arg : 要执行的程序需要的参数
第一位arg: 占位
后边的 arg: 命令的参数
参数写完之后:null
一般用来执行自己写的程序
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int i = 200;
int main()
{
pid_t pid;
int i = 0;
pid = fork();
if(pid > 0)
{
i += 400;
printf("i = %d\n",i);
printf("this is father progress %d\n",getpid());
}
else if(pid == 0)
{
execl("/bin/ls","ls""-l",NULL);
i+= 200;
printf("i = %d\n",i);
printf("this is child progress %d,father progress is %d\n",getpid(),getppid());
}
return 0;
}
注:execl会替换进程中的.text内容,其后的程序不会再执行。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int i = 200;
int main()
{
pid_t pid;
int i = 0;
pid = fork();
if(pid > 0)
{
i += 400;
printf("i = %d\n",i);
printf("this is father progress %d\n",getpid());
}
else if(pid == 0)
{
execl("/home/u/process/hello","hello",NULL);
i+= 200;
printf("i = %d\n",i);
printf("this is child progress %d,father progress is %d\n",getpid(),getppid());
}
return 0;
}

3.执行PATH环境变量能够搜索到的程序
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
file :执行的命令名字
第一个arg:占位
后边的arg:命令的参数
参数写完之后:NULL
执行系统自带的程 序:/bin/xx 比如:ps aux
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int i = 200;
int main()
{
pid_t pid;
int i = 0;
pid = fork();
if(pid > 0)
{
i += 400;
printf("i = %d\n",i);
printf("this is father progress %d\n",getpid());
}
else if(pid == 0)
{
execlp("ps","ps","aux",NULL);
i+= 200;
printf("i = %d\n",i);
printf("this is child progress %d,father progress is %d\n",getpid(),getppid());
}
return 0;
}

4.执行指定路径, 指定环境变量下的程序
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
5.返回值:
如果函数运行成功不返回
如果执行失败,打印错误信息,退出子进程
6.它们的一般规律如下:
| l (list) | 命令行参数列表 |
| p (path) | 搜素file时使用path变量 |
| v (vector) | 使用命令行参数数组 |
| e(environment) | 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量 |
孤儿进程和僵尸进程
孤儿进程
1.一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作
为了释放子进程的占用的系统资源:
进程结束之后,能够释放用户区空间
释放不了PCB,必须由父进程释放
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
printf("parent process pid %d\n",getpid());
}
else if(pid == 0)
{
sleep(2);
printf("child process pid %d,ppid %d\n",getpid(),getppid());
}
return 0;
}

僵尸进程
1.一个比较特殊的状态,当进程退出,父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会在以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
2.是一个已经死掉了的进程。无法被kill
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
while(1)
{
sleep(1);
printf("parent process pid %d\n",getpid());
}
}
else if(pid == 0)
{
printf("child process pid %d,ppid %d\n",getpid(),getppid());
}
return 0;
}

注:此时无法kill僵尸进程,kill父进程可解决僵尸进程
进程回收
wait 阻塞函数
函数作用:
1.阻塞并等待子进程退出
2.回收子进程残留资源
3.获取子进程结束状态(退出原因)
pid_t wait(int *wstatus);
返回值:
‐1 : 回收失败,已经没有子进程了
>0 : 回收子进程对应的pid
参数 :
status判断子进程如何退出状态
1.WIFEXITED(status):为非0 ,进程正常结束
WEXITSTATUS(status) :
如上宏为真,使用此宏,获取进程退出状态的参数
2.WIFSIGNALED(status):为非0,进程异常退出
WTERMSIG(status):
如上宏为真,使用此宏,取得使进程种植的那个信号的编号
调用一次只能回收一个子进程
等待子进程结束
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int i = 0;
for(i = 0;i < 4;i ++)
{
printf("----i = %d ----\n",i);
}
pid = fork();
if(pid > 0)
{
pid_t wpid;
wpid = wait(NULL);
printf("wpid = %d\n",wpid);
printf("this is father process %d\n",getpid());
}
else if(pid == 0)
{
printf("this is child process %d,father pid is %d\n",getpid(),getppid());
}
for(i = 0;i < 4;i++)
{
printf("i = %d\n",i);
}
return 0;
}

判读子进程退出状态,正常退出
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int i = 0;
for(i = 0;i < 4;i ++)
{
printf("----i = %d ----\n",i);
}
pid = fork();
if(pid > 0)
{
int status;
pid_t wpid;
wpid = wait(&status);
printf("wpid = %d\n",wpid);
//nomal exit
if(WIFEXITED(status))
{
printf("exit value is %d\n",WEXITSTATUS(status));
}
printf("this is father process %d\n",getpid());
}
else if(pid == 0)
{
printf("this is child process %d,father pid is %d\n",getpid(),getppid());
}
for(i = 0;i < 4;i++)
{
printf("i = %d\n",i);
}
return 0;
}

注:此时的退出值为函数的返回值,return 0
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int i = 0;
for(i = 0;i < 4;i ++)
{
printf("----i = %d ----\n",i);
}
pid = fork();
if(pid > 0)
{
int status;
pid_t wpid;
wpid = wait(&status);
printf("wpid = %d\n",wpid);
//nomal exit
if(WIFEXITED(status))
{
printf("exit value is %d\n",WEXITSTATUS(status));
}
//abnormal exit
if(WIFSIGNALED(status))
{
printf("exit by signal is %d\n",WTERMSIG(status));
}
printf("this is father process %d\n",getpid());
}
else if(pid == 0)
{
while(1)
{
sleep(1);
printf("this is child process %d,father pid is %d\n",getpid(),getppid());
}
}
for(i = 0;i < 4;i++)
{
printf("i = %d\n",i);
}
return 0;
}

注:此时返回的为kill的信号值9
vfork创建进程
区别一:vfork可以直接使用父进程存储空间,不拷贝
区别二:vfork可以保证子进程先运行,当子进程调用exit退出后,父进程才执行
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if(pid > 0)
{
while(1)
{
printf("cnt = %d\n",cnt);
printf("parent pid is %d\n",getpid());
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
cnt++;
printf("cnt = %d\n",cnt );
printf("child pid is %d\n",getpid());
sleep(1);
if(cnt == 3)
{
exit(0);
}
}
}
return 0;
}

进程退出
正常退出
1.main函数调用return
2.进程调用exit(),标准C库
3.进程调用 _exit() 或者 _Exit(),属于系统调用
补充:
4.进程最后一个线程返回
5.最后一个线程调用pthread_exit
异常退出
1.调用abort函数
2.当进程收到某些信号时,比如ctrl +C
3.最后一个线程对取消(cancellation)请求做出相应
不管进程如何终止,最后都会执行内核中的同一段代码,这段代码和相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任一一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit, _exit 和 _ Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传送给函数,在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得终止状态。
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);

被折叠的 条评论
为什么被折叠?



