进程和线程都是现代操作系统中程序运行的基本单位,进程是一个具有独立功能的程序关于某个数据集合上的依次并发执行的运行活动,从进程演化出线程的主要目的就是为了更好地支持多处理器系统,提高性能;
在单线程结构的进程中,进程及时内部独立的执行单元,也是独立竞争资源的基本单元;
在多线程结构的进程中,进程是系统进行资源分配和保护的基本单元,而线程是进程内的独立的执行单元,线程不能离开进程和独立存在;
线程包含独立的堆栈和处理器及寄存器状态,每个线程共享其所附属的进程的所有资源;
一、进程
Linux操作系统使用一个称为进程控制块的数据结构task_struct来表示一个进程。当一个进程被创建时,系统为该进程建立一个task_struct结构,当进程结束时,系统会回收进程的task_struct结构;
进程可以产生子进程, 每一个进程都有一个标志,即进程ID,可通过getpid()函数获得其进程ID,通过getppid()函数获得其父进程ID,还有一些不常用函数,getpgrp()获得进程组ID,getpgid(pid_t pid)获得制定pid进程所属组的ID,getuid()获得进程所有者的ID,geteuid()获得进程有效用户的ID以及getegid()获得进程有效组ID;
1、进程的创建
一般情况下是使用fork(), vfork()两个函数来创建新的进程的,函数原型如下:
pid_t fork(void);
pid_t vfork(void);
这两个函数调用一次返回两次,向父进程返回刚创建子进程的ID,向子进程返回0,调用失败时,父进程返回-1,没有子进程创建。
fork()函数与vfork函数的区别:
调用fork函数时,子进程与父进程使用同一个代码段,因为其程序相同,对于数据段和堆栈段,系统则复制一份给新进程,并采用写时复制(copy-on-write)的技术,即当数据发生变化时,系统将有区别的页从物理上分离,使得在空间上的开销达到最小;fork()创建的子进程与父进程的执行顺序是无法控制的,需通过一系列的机制进行控制;
调用vfotk函数时,vfork()函数创建的子进程与父进程共享地址空间,即子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改同样为父进程可见,但是vfork()函数创建子进程后,父进程会被阻塞,知道子进程执行execv()或者exit();
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> int main(void) { int data = 0; pid_t pid; int choose = 0; while((choose = getchar()) != 'q') { switch(choose) { case '1': pid = fork(); if(pid < 0) perror("Error!\n"); if(pid == 0) { data++; exit(0); } wait(pid); if(pid > 0) printf("data is %d\n",data); break; case '2': pid = vfork(); if(pid < 0) perror("Error\n"); if(pid == 0) { data++; exit(0); } wait(pid); if(pid > 0) { printf("data is %d\n",data); } break; default: break; } } }
运行结果:
2.关于exec()类函数
<1>int execl(const char *path,const char *arg,...);
<2>int execlp(const char *file,const char *arg,...);
<3>int execle(const char *path,const char *arg,...,char * const envp[])
<4>int execv(const char *file, char * const arg[],...);
<5>int execve(const char *filename,char * const argv[],char *const envp[] );
<6>int execvp(const char *file,char *const argv[]);
后缀为p时,可以利用DOS的PATH变量查找到可执行文件;
后缀为l时,接受以逗号分隔的参数列表,列表以NULL指针作为结束标识;例:execl("/homr/robot/test",“test”,a,b,c,NULL),一般第一个参数为可执行文件名,a,b,c均为参数;
后缀为v时,接收以NULL结尾的字符串数组的指针,例:char *argv[] = {"/bin/cat","etc/passed","etc/group",NULL};
后缀为e时,传递指定参数envp;
以execlp函数为例:
#include <stdio.h> #include <error.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <string.h> char command[256]; void main() { int rtn; while(1) { printf(">"); fgets(command,256,stdin); command[strlen(command)-1] = 0; if(fork() == 0) { execlp(command,command,NULL); perror(command); exit(-1); } else { wait(&rtn); printf("child process return %d\n",rtn); } } }
结果如下:
3、进程等待
与vfork()不同,fork()创建的子进程与父进程的执行顺序是无法控制的,如果想要控制,必须有父进程使用wait()或waitpid(),其中wait()返回任一终止状态的子进程,而waitpid()等待特定的子进程。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int options);
对于wait():参数status用于保存被收集进程退出时的状态,它是一个指向int类型的指针,进程一旦调用wait(),将立即阻塞自己,由wait自动分析当前进程是否有某个子进程退出,如果找到一个已经僵死的子进程,wait()将收集這个子进程的信息,并彻底销毁后返回,若没有找到一个僵死的进程,将一直阻塞,即等待,如果不关心子进程的退出状态,可以设置为NULL;
对于waitpid():pid>0时,等待进程ID为pid的子进程,pid= -1时,等待任何一个子进程退出,此时与wait函数差不多,pid = 0时,等待同一个进程组的任何子进程,如果子进程加入了其他进程组,则waitpid()函数不会对他做任何理睬,pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值;optiona 可以进一步控制waitpid()函数的操作,参数值可以为0,也可以为WNOHANG(如由pid指定的子进程并不立即可用,则waitpid()函数不阻塞,返回0)和WUNTRACED(如pid指定的任一子进程已暂停,且未报告过,即没有获得其状态,则返回status).
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pc,pr; pc = fork(); if(pc < 0) printf("Error occured on forking.\n"); else if(pc == 0) { sleep(10); exit(0); } else { do{ pr = waitpid(pc,NULL,WNOHANG); if(pr == 0) { printf("No child exited\n"); sleep(1); } }while(pr == 1); if(pr == pc) printf("successfully get child %d\n",pr); else printf("some error occured\n"); } return 0; }
结果如下:
关于参数status:
如果参数status的值不是NULL,wait()或者waitpid()会把子进程退出时的状态取出并放入其中,由于这些信息存放在一个整数的不同二进制位中,Linux提供了6个宏用来检查子进程的返回状态;
<1>WIFEXITED(status),如果子进程通过_exit()或者exit()正常退出,则该宏为真,此时可执行WEXITSTATUS(status)获取子进程传送给_exit()或exit()参数的低八位;
<2>WIFSIGNALED(status),如果子进程是由于得到的信号没有被捕捉而导致退出,则为真,此时可通过WTERMSIG(status)取出子进程终止的信号编号;
<3>WIFSTOPPED(status),如果子进程没有终止,但停止并可以重新执行时,返回真,仅出现在waitpid()中使用了WUNTRACED时;
<4>WEXITSTATUS(status),得到status的低八位,即退出码;
<5>WTERMSIG(status),得到导致进程退出的信号编号;
4.进程终止
一般情况下,进程通过exit()或_exit()进行退出;
函数原型:
#include <stdlib.h> void exit(int status); #include <unistd.h> void _exit(int status)
exit()和_exit()的区别:
_exit()直接使进程停止运行,而调用exit()之前要检查文件的打开情况,把文件缓冲区的内容写回文件。
/*exittest.c*/ #include <stdio.h> #include <stdlib.h> int main() { printf("output begin\n"); printf("content in buffer"); exit(0); } /*_exittest.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("output begin!\n"); printf("content in buffer"); _exit(0);
结果如下: