什么是进程?什么是程序?
进程在系统中能够独立运行,并分配资源的基本单位。而程序由多个进程组成,一个程序最少有一个进程。进程是动态的,程序是静态的。
进程控制块(PCB)
在Linux中task_struct结构体即是PCB。PCB是进程的唯一标识,PCB由链表实现(为了动态插入和删除)。
进程创建时,为该进程生成一个PCB;进程终止时,回收PCB。
PCB包含信息:1、进程状态(state);2、进程标识信息(uid、gid);3、定时器(time);4、用户可见寄存器、控制状态寄存器、栈指针等(tss)
每个进程的task_struct和系统空间堆栈存放位置如下:两个连续的物理页【《Linux内核源代码情景分析》271页】
系统堆栈空间不能动态扩展,在设计内核、驱动程序时要避免函数嵌套太深,同时不宜使用太大太多的局部变量,因为局部变量都是存在堆栈中的。
每个进程都有一个
非负的
唯一进程ID(PID)。虽然是唯一的,但是PID可以重用,当一个进程终止后,其他进程就可以使用它的PID了。
PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,但是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操作。
在linux系统可通过如下命令进行进程操作:
查看进程:
ps
查看当前进程
top
查看系统的所有进程
杀死进程:
kill pid ---》尝试杀死进程
kill -9 pid ---》发送终止信号
其他操作:
./hello & ---》后台运行
killalll 进程的名称 ---》如果不知道进程的pid可以用此方法
killall -STOP 进程名 ---》暂停进程
killall -CONT 进程名 ---》继续运行进程
进程的运行状态示意图:
---------------------------------------------------------------------
1.进程的创建
一个进程的创建必须依赖另一个进程调用fork函数创建。
头文件:
#include
函数原型:
pid_t fork(void);
返回值:成功返回子进程的pid号,失败返回-1
注意:因为子进程会拷贝父进程fork后的内容作为自己的信息,所以fork函数就提供了两个返回值用于区别是父进程还是子进程。此外,父子进程在fork后,他们的内存空间是完全独立的。
fork() == 0 ---》子进程里面的内容
fork() > 0 ---》父进程里面的内容
fork() < 0 ---》创建进程失败
2.获取进程号
头文件:
#include
#include
函数原型:
pid_t getpid(void); ---》获取当前进程号
pid_t getppid(void); ---》获取父进程号
3.父进程等待子进程结束
头文件:
#include
#include
函数原型:
pid_t wait(int *status);
返回值:回收子进程pid号,失败返回-1
参数一:保存进程退出状态
函数原型:
pid_t waitpid(pid_t pid ,int *status,int options);
参数一:需要等待的子进程pid号
pid = -1 等待任意进程,与wait()函数用法一样
pid = 0 等待相同组的进程
参数二:保存进程的退出状态
参数三:设置该函数是否阻塞,0为阻塞,WNOHANG为不阻塞
-----------------------------------------------------------------------------
示例程序:
#include
#include
#include
#include
#include
int main()
{
pid_t pid = fork();
int n=0;
int status;
if(pid == 0)
{
printf("创建子进程:pid = %d,它的父进程pid=%d
",getpid(),getppid());
exit(0);
}
else if(pid > 0)
{
wait(&status);//等待子进程退出,参数为NULL表示退出状态不关注,参数为&status为获取退出状态
printf("子进程退出状态为%d
",status);
//waitpid(pid,NULL,0);//等待子进程退出,参数三0为阻塞,WNOHANG为不阻塞
printf("父进程:pid = %d
",getpid());
}
else
{
printf("创建进程失败!
");
exit(1);
}
return 0;
}
退出状态解析——宏函数
WIFEXITED() ---》判断是否正常退出,返回真与假
WEXITSTATUS() --->获取正常退出的状态,成功返回状态值
WIFSIGNALED() ---》判断是否被信号杀死,返回真与假
WTERMSIG() ---》获取杀死进程的信号值,成功返回信号值
WIFSTOPPED() ---》判断是否给信号暂停,返回真与假
-------------------------------------------------------------------
练习:获取杀死子进程进程的信号值
#include
#include
#include
#include
#include
#include
int main()
{
//创建子进程
int pid =fork();
if(pid==0)
{ int i=0;
while(1)
{
printf("i=%d
",i++);
sleep(1);
if(i==100)
{
exit(5);
}
}
}
//等待子进程结束
int status;
int ret = wait(&status);
/*
//判断是否为正常退出状态
if(WIFEXITED(status))
{ //打印退出状态的值
printf("ret=%d status=%d
",ret,WEXITSTATUS(status));
}
else
{
printf("error exit
");
}
*/
//判断是否为信号杀死
if(WIFSIGNALED(status))
{ //获取杀死改进程的信号值
printf("ret=%d status=%d
",ret,WTERMSIG(status));
}
else
{
printf("error exit
");
}
//exit(0);
return 0;
}
---------------------------------------------------------------------
进程的退出标志:
return 0; ---》函数的退出标志,但是假如在main函数中他就是进程的退出标志
头文件:
#include
函数原型:
void exit(int status);---》自己回收系统分配的资源,例如清空缓存区,但是该退出标志效率较低。
参数一:传递给父亲进程的一个退出标志
使用_exit或者_Exit退出进程的话,系统是不会回收资源的,必须要利用wait去回收,效率高
头文件:
#include
函数原型:
void _exit(int status);
头文件:
#include
函数原型:
void _Exit(int status);
---------------------------------------------------------------------
使子进程与父进程脱离关系
exec函数族让子进程自己去跑一个main函数脱离父亲进程
头文件:
#include
函数原型:
int execl(const char *path, const char *arg, ...);
int execv(const char *path, const char * argv[]);
int execle(const char *path, const char *arg,...,
char * const envp[]);
int execlp(const char *file,
const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
以上所有exec函数族的返回值:失败发回-1 只要返回值不为-1都是执行成功
函数使用:
int execl(const char *path, const char *arg, ...);
参数一:要执行的子进程的绝对路径
参数二:要执行的子进程名称与参数
注意传递参数的结束值必须为NULL;
int execv(const char *path, const char * argv[]);
参数一:要执行的子进程的绝对路径
参数二:要传递给子进程的参数
用法execv与execl是一样的,只不过一个传递的是数组,一个传递的是参数。
int execle(const char *path, const char *arg, ..., char * const envp[]);
用法execle与execl是一样的,只不过execle他可以传递环境变量值给子进程
例子1:
execl("/home/gec/hello","hello",NULL);
例子2:传递参数
execl("/home/gec/hello","hello","nihao","wohao",NULL);
---------------------------------------------------------------------------------------------------
练习:利用execl函数去调用其他进程作加法运算。
int atoi(const char *nptr);把字符串转换为整型
int sprintf(buf,"%d",a); 把整形转换为字符串类型
-------------------------------add.c-----------------------------------------------
#include
int main(int argv,char *argc[])
{
printf("argc[1] = %s
",argc[1]);
printf("argc[2] = %s
",argc[2]);
int a = atoi(argc[1]);
int b = atoi(argc[2]);
int c = a+b;
printf("result = %d
",c);
return 0;
}
---------------------------------execl.c--------------------------------------------
#include
#include
#include
#include
int main(int argv,char *argc[])
{
pid_t pid = fork();
if(pid == 0)
{
//调用execl函数让子进程去执行其他函数
// execl("/mnt/hgfs/ARM开发板/内核/add","add",argc[1],argc[2],NULL);
char *arg[] = {"add",argc[1],argc[2],NULL};
execv("/mnt/hgfs/ARM开发板/内核/add",arg);//参数一为要执行的子进程的绝对路径 ,参数二为要传递给子进程的参数
}
int status;
int ret = wait(&status);
if(WIFEXITED(status))//判断是否为正常退出状态
printf("ret = %d,status = %d
",ret,WEXITSTATUS(status));
else
printf("退出失败!
");
return 0;
}
带p的exec族函数:他是获取了系统的环境变量,可以不用写绝对路径
*** 但是参数一的进程必须要在环境变量PATH中****
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
--------------------------------------------------------------------------------------------------
#include
#include
#include
#include
#include
char command[100];
int main()
{
int ret;
while(1)
{
printf("输入要执行的命令>:");
fgets(command,100,stdin);//将以回车结束输入的行缓冲区的内容给command
command[strlen(command)-1] = '