一、进程三种状态
1.就绪状态(Ready)
当进程已分配到除CPU以外的所有必须的资源,只要获得处理机便可以立即执行,这时的进程状态就被称为就绪状态。
2. 执行状态(Running)
当进程已获得CPU,其程序正在CPU上执行,此时的进程状态被称为执行态。
3. 阻塞状态(Blocked)
又称为等待、挂起状态。
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件有很多种,例如,等待I/O完成、申请缓存区不能满足、等待条件(信号)等。
4. 状态转换
处于阻塞态的进程在事件发生后不会直接进入到运行态,而是先进入到就绪态。
二、进程相关命令
1. ps查看进程
一般使用 ps -aux(进程状态) 或 ps -ef(父子进程关系)
使用ps命令时查看的进程状态标记符
R:正在执行
S:阻塞状态
T:暂停状态
Z:僵尸进程(不存在但暂时无法消除)
D:不可中断的进程
<:高于优先级的进程
N:低于优先级的进程
L:有内存分页分配并锁在内存中
s 进程的领导者(在它之下有子进程);
l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads);
+ 位于后台的进程组
2. kill消灭进程
一般使用 kill -9 pid 消灭指定进程 (类似于Ctrl+c)
(给进程ID为pid的进程发送一个编号为9的信号)
kill命令本质是给一个指定的进程发送一个信号。
信号分为很多种,大多数情况下,进程接收到信号后会结束运行。进程中可以更改对信号的处理方式,但是无法更改对9、19信号的处理方式,防止特殊情况发生无法终止进程。
三、相关函数
1.相关头文件
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
2.获取进程ID
grtpid()
原型:pid_t getpid(void);
返回值:成功,当前进程的ID
getppid()
原型:pid_t getppid(void);
返回值:成功,当前进程的父进程ID
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
int main(void)
{
pid_t pid,ppid;
pid = getpid();
ppid = getppid();
printf("pid = %d\r\nppid = %d\r\n",pid,ppid);
}
3.运行进程
system()
在一个进程中运行另一个进程,运行完后回到原进程
原型:int system(const char * string); 参数:const char * string
待执行的命令(以字符串的形式),比如 “ls -l” 功能:system()会调用 fork()产生子进程,由子进程来调用/bin/sh-c
string 来执行参数 string 字符串所代表的命令,此命令执行完后随即返回 原调用的进程。在调用 system()期间
SIGCHLD 信号会被暂时搁 置,SIGINT 和 SIGQUIT 信号则会被忽略
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
int main(void)
{
pid_t pid,ppid;
pid = getpid();
ppid = getppid();
printf("MAIN:pid = %d , ppid = %d\r\n",pid,ppid);
system("pwd");
}
4.替换进程
exec函数族 execl(),execlp(),execv(),execve(),execvp()
用新程序替换原进程的程序(进程ID不变,仅仅是执行代码发生了变化)
execl()
原型:int execl(const char *
path,const char * arg,…); 参数:const char * path 待执行程序的路径
const char * arg,… 待执行程序的argv[0]~argv[n],且再加上参数NULL(或 (char *)0) 返回值:成功,不返回(即execl()函数执行成功,原位于execl()之后的代码将不会被执行)
失败,返回-1 功能:用参数指定的进程替换当前进程
int main(void)
{
pid_t pid,ppid;
pid = getpid();
ppid = getppid();
printf("MAIN:pid = %d , ppid = %d\r\n",pid,ppid);
execl("/bin/pwd","pwd",NULL);
printf("hello");//未执行
}
在exec函数族中l和v不能同时出现
- 若出现了l,则需要将Shell中的各参数分别作为函数参数
- 若出现了v,则需要将Shell中的各参数整合在一起作为函数参数
Eg:
char *str[]={”ls”,”-al”,”/etc/passwd”,(char * )0};
execv(“/bin/ls”,str);
- 若出现了p,则参数const char * path 中可以省略PATH变量中包含的路径
Eg:
execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);
等价
execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);
4.创建进程
原型:pid_t fork(void);
返回值:创建成功,会在父进程中返回子进程的PID,会在子进程中返回0
创建失败,会在父进程中返回-1
功能:创建一个子进程
备注:当fork()函数成功执行时,位于fork()函数之后的将分别被父进程和新创建的子进程各执行一遍
int main(void)
{
pid_t pid,ppid;
int i=0;
fork();
i++;
printf("i=%d\r\n",i);
printf("pid = %d , ppid = %d\r\n",getpid(),getppid());
}
两次显示变量i的值都是1,说明fork()执行后,父子进程拥有独立的进程空间,只是fork()执行时,子进程先拷贝了父进程的用户空间(因为在子进程执行时可以直接使用变量i而不需要定义)(相当于父子进程中各有一个变量i,彼此在CPU物理内存空间上独立)
5.结束进程
进程结束的方式:
a) 该进程的main函数自然返回
(比如:执行return 0;)
b) 该进程调用exit()
c) 该进程收到信号,导致进程(非正常结束)
函数名: void exit(int status);
功能: 使进程自然结束
函数参数:int status 用于在进程结束时返回其父进程的数据status & 0377
返回值: 该函数不会返回
备注: return 0;使函数结束,但由于结束函数不一定是main函数,所以调用return 0;后不一定使进程结束;而在任一子函数中调用exit(0)都会使当前进程结束运行。在一个进程中调用exit(0)会使当前进程结束运行。 exit(0)近似于在main()中调用return 0;
6.进程的等待
函数名: *pid_t wait(int status);
功能:
- 等待(期间,等待状态)一个子进程的状态改变(运行结束)
- 让系统回收本次结束的子进程的空间
函数参数:int *status
与本次结束的子进程中调用exit(int status)搭配
用于返回exit()参数中的值
返回值:
成功,本次结束的子进程的PID
失败,-1
备注:调用该函数时,会使当前进程进入到等待状态,直到一个子进程结束运行/状态改变。若没有子进程,则该函数不生效。
int main(void)
{
pid_t ret;
ret = fork();
if(ret == -1)
{
printf("fork failed\n");
return -1;
}
else if(ret == 0)//child only
{
printf("I am child\n");
sleep(5);
printf("ret = %d , PID = %d , PPID = %d\n",ret,getpid(),getppid());
sleep(5);
}
else//father only
{
printf("I am father\n");
printf("ret = %d , PID = %d , PPID = %d\n",ret,getpid(),getppid());
wait(NULL); //父进程等待子进程运行结束,让系统回收子进程的空间避免出现僵尸进程。
printf("one of my child is dead\n");
}
exit(0);//child and father
}
函数名:*pid_t waitpid(pid_t pid, int status, int options);
功能 :
- 等待(期间,等待状态)一个子进程的状态改变(运行结束)
- 让系统回收本次结束的子进程的空间
函数参数:
pid_t pid
<-1,等待进程组ID为 |pid|的任一一个子进程结束
=-1,等待任一一个子进程结束
=0 , 等待同组的一个子进程结束
>0 , 等待进程ID为pid的子进程结束
int *status 与本次结束的子进程中调用exit(int status)搭配
用于返回exit()参数中的值
int options 一般填0
返回值 成功,本次结束的子进程的PID
失败,-1
四、实践
1.实现父子进程有效执行不同的程序代码
通过fork()函数在父子进程中不同返回值实现
int main(void)
{
pid_t pid,ppid;
int i=0;
pid_t ret;
ret = fork();
if(ret < 0)
{
printf("create child process failed\r\n");
return -1;
}
if(ret == 0)
{
//Child
printf("I am child,pid = %d,ppid = %d\r\n",getpid(),getppid());
}
else
{
//Parent
printf("I am parent,pid = %d,ppid = %d\r\n",getpid(),getppid());
}
}
2.设计一个程序,实现一个进程和其两个子进程,交替显示数据1~15(每隔1s显示一个数据)
比如:
C1:1
C2:2
P :3
C1:4
C2:5
P :6
…….
int main(int argc,char *argv[])
{
int i;
pid_t ret1,ret2;
ret1 = fork();
if(ret1 == 0)//child1 only
{
for(i=1;i<=15;i+=3)
{
sleep(1);
printf("C1:%d\n",i);
sleep(2);
}
}
else //parent only
{
ret2 = fork();
if(ret2 == 0) //child2 only
{
for(i=2;i<=15;i+=3)
{
sleep(2);
printf("C2:%d\n",i);
sleep(1);
}
}
else //parent only
{
for(i=3;i<=15;i+=3)
{
sleep(3);
printf("P :%d\n",i);
}
waitpid(ret1,NULL,0);//wait(NULL);
waitpid(ret2,NULL,0);//wait(NULL);
}
}
exit(0);
}