进程的简单介绍与实现

2019-07-14 07:22发布

什么是进程?什么是程序? 进程在系统中能够独立运行,并分配资源的基本单位。而程序由多个进程组成,一个程序最少有一个进程。进程是动态的,程序是静态的。

进程控制块(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] = '