首先,什么是进程?
在windows下打开任务管理器,会弹出这个界面:
对,他们就是进程,正在运行的程序,就是进程。
程序和进程是不同的东西
程序是写在磁盘中,进程是在内存中。进程指一个程序的执行过程。
程序是静态的。进程是动态的,包含动态创建 调动 消亡的过程。
进程状态:
· 执行态:该进程正在,即进程正在占用 CPU。
· 就绪态:进程已经具备执行的一切条件,正在等待分配 CPU 的处理时间片。
· 等待态:进程不能使用 CPU,若等待事件发生则可将其唤醒。
查看进程
ps
ps -aux
把进程状态的标志整理一下
R:正在执行 S:阻塞状态 T:暂停状态 Z:僵尸进程(不存在但暂时无法消除) D:不可中断的进程 <:高于优先级的进程 N:低于优先级的进程 L:有内存分页分配并锁在内存中 s 进程的领导者(在它之下有子进程); l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads); + 位于后台的进程组
ps -ef
PPID父进程
0号进程是调度进程,不执行任何程序,是内核的一部分,系统进程。
1号进程是init,所有进程的祖先。init负责 引导系统 启动守护进程和运行必要的程序。他不是系统进程,但是以系统的超级用户特权运行。
父进程主要负责子进程空间的清理。
并发:
同时发生(宏观)
看起来像同时发生,其实并不是。只是CPU运行速度很快。
并行:
同时执行(微观)
多个CPU真的可以同时执行不同的进程。
同步:
宏观上同步,同的意思是协同,进程间协同有序进行,不是同时。
一组进程为了协调推进速度,在某些地方需要等待或唤醒,这种进程之间的相互制约就叫进程同步
进程之间需要通信。
异步:
一个异步过程调用发出后,等到收到信息通知,再去执行,在此期间他在执行自己的事情。
同步是两个进程相关的,一个进程需要阻塞等待另一个进程。
异步是两个进程无关,各自执行各自的程序
获取进程ID
pid_t getpid(void);
获取父进程ID
pid_t getppid(void);
返回值为进程ID号
试验一下
#include <unistd.h> #include <stdio.h> int main(int argc, char const *argv[]) { pid_t pid; pid_t ppid; pid = getpid(); ppid = getppid(); printf("%d ",pid); printf("%d ",ppid); while(1); return 0; }
得出父进程和自身的pid。
然后使用 ps -aux找到这两个进程ID
父进程:终端
循环本身的进程:
创建进程
pid_t fork(void);
这个函数返回值有2个
返回值为 -1 时说明进程创建失败
在子进程中 返回值为0
在父进程中 返回值为子进程的pid
执行fork()后,给父进程返回子进程的PID
子进程获得父进程的数据空间、堆和栈的复制。
父子进程并不是共享这些资源,他们分别拥有两份不同的空间。
使用方法如下:
pid_t ret; //位于fork前定义变量在父子进程中都存在,但彼此独立 ret = fork(); if(ret == -1) { perror(“fork fail ”); return -1; } else if(ret == 0) { //仅子进程执行代码 } else { //仅父进程执行代码 }
这里说一下perror()
该函数时用来返回错误信息,输入参数为你要打印的那句话的头,系统会自动为你补全失败原因。
什么样的函数可以使用perror?
我们 man fork 看一下 fork 里的详细信息
能找到这样一个返回值信息
上面明确写了,当返回值为-1时,会设置error。
有这样说明的函数,才可以使用 perror() 来打印错误信息。
使用示例:子进程每次加1 父进程每次加2
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> int main(int argc, char const *argv[]) { pid_t ret; int sum; ret = fork(); if(ret == -1) { perror("fork creat error "); exit(1); } else if(ret == 0) { //child while(1) { sum ++; printf("child :%d ",sum); sleep(1); } } else { //father while(1) { sum += 2; printf("father :%d ",sum); sleep(1); } } exit(0); }
运行结果如下:
再生个儿子:
int main(int argc, char const *argv[]) { int a = 100; int n = fork(); if (n < 0) { //错误退出 } else if (n == 0) { //子进程1 } else { //父进程 int m = fork(); if(m < 0) { //错误 } else if(m == 0) { //子进程2 } else { //父进程代码 } } return 0; }
创建进程还有一个函数 vfork()
fork() vfork() 的区别:
1.父子进程调度:
使用fork(),父子进程先后运行顺序是不固定的,当其中一个进程阻塞后会调度另一个进程运行。
使用vfork(),一定是先运行子进程,等待子进程运行结束后再运行父进程,
在子进程运行期间内即使子进程进入阻塞态也不会运行父进程。
2.进程空间:
使用fork(),子进程会拷贝 在fork()函数前 父进程的空间,且彼此独立
使用vfork(),父子进程共享空间(vfork()函数之前的缓存空间共用),
子进程退出运行时需要使用exit(0)等方法退出,而不是使用return
(如果直接以return结束子进程,由于父子进程共享空间,导致父进程的堆栈受到影响,父进程中的数据发生错乱)
简单一点:
fork:子进程拷贝父进程的数据段 vfork:子进程与父进程共享数据段 //-------------------------------------------------- fork:父、子进程的执行次序不确定 vfork:子进程先运行,父进程后运行
我让他循环5次,然后把创建进程的函数换成这个,得到现象如下:
销毁进程
常见终止进程的方式:
主动:
1、在主函数中 return,注意return 只是结束函数,而如果主函数都结束了,那进程肯定就结束了。
2、exit() 标准函数 <stdlib.h>
3、_exit() 系统调用函数 <unistd.h>
被动:
4、接收到某个信号 比如CTRL+C
5、kill
进程替换
可以理解为 鸠占雀巢
在 Linux 中使用 exec 函数族主要有两种情况:
· 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何 exec 函数族 让自己重生;
· 如果一个进程想执行另一个程序,那么它就可以调用 fork 函数新建一个进程,然后调用任何一个 exec,这样看起来就好像通过执行应用程序而产生了一个新进程。(这种情况非常普遍)
比如我现在正在执行一个 test.exe 的程序,程序代码中有一个 进程替换的函数(exec) 参数为 qq,那么就切换为执行qq 不再执行test.exe了
但是进程的 PID还是原来的 PID,换的是程序。
int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg,..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[],char *const envp[]);
第一个参数放文件名/带路径的文件名
根据函数名字找一下规律,exec的后缀
l v 表示参数呈现形式
l list 列出参数列表
v vector 参数用数组储存起来
p path 可以使用路径
e environ 参数中有环境数组
如果成功执行,函数就不回来了。。。
如果执行失败,返回值为 -1.
最后一个参数,必须以空指针 NULL 作为结束。
可以利用exec替换程序的特性,将子进程替换为要运行的程序,实现父子进程并发运行,并且各自的空间不受干扰
int ret = fork(); if(ret == 0) { //子进程 execl(/*替换执行新程序*/); } else{ //父进程 }
试验一下:
先写一个子进程中”重生”的程序:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char const *argv[]) { int sum = 0; while(1) { printf("aaa.exe : %d ", sum++); sleep(1); } return 0; }
然后写主函数:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> int main(int argc, char const *argv[]) { pid_t ret; int sum = 0; ret = fork(); if(ret == -1) { perror("fork creat error "); exit(1); } else if(ret == 0) { //child printf("go to aaa.exe "); execl("./aaa.exe","./aaa.exe",NULL); printf("back to child "); } else { //father for(int i = 1;i != 10;i++) { sum += 2; printf("father :%d ",sum); sleep(1); } } exit(0); }
运行结果如下:
可以看出并没有打印出 back to child 这句话,证明程序走exec走了之后就没有回来
等待进程
如同我们刚才做的实验一般,父子进程分别做自己的事情,并发 、异步,那么父子进程肯定都有执行完任务的时候
那么问题就来了。我们前面提过,父进程的责任之一就是处理子进程的终止。如果子进程先执行完了任务,父进程没有等待,那么子进程就变成了僵尸进程,父进程不收尸,那么已经结束的子进程就会一直占用资源。如果是父进程先于子进程完成任务,父进程自己exit()了,那么子进程就变成了孤儿进程,被祖先进程 init 收养。
头文件
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *status);
参数是保存子进程退出状态的指针
可以用下表的宏来查看退出状态:
返回值:
成功返回获取到退出状态进程的PID (退掉的子进程)
失败返回 -1
看个例子:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> void print_exit_status(int status) { /* 自定义打印子进程退出状态函数 */ if (WIFEXITED(status)) /* 正常退出,打印退出返回值 */ printf("normal termination, exit status = %d ", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) /* 因信号异常退出,打印引起退出的信号 */ printf("abnormal termination, signal number = %d ", WTERMSIG(status)); else printf("other status "); /* 其它错误 */ } int main(int argc, char *argv[]) { pid_t pid; int status; if ((pid = fork()) < 0) { /* 创建子进程 */ perror("fork error"); exit(-1); } else if (pid == 0) { exit(7); /* 子进程调用 exit 函数,参数为 7 */ } if (wait(&status) != pid) { /* 父进程等待子进程退出,并获取退出状态*/ perror("fork error"); exit(-1); } print_exit_status(status); /* 打印退出状态信息 */ if ((pid = fork()) < 0) { /* 创建第二个子进程 */ perror("fork error"); exit(-1); } else if (pid == 0) { abort(); /* 子进程调用 abort()函数异常退出 */ } if (wait(&status) != pid) { /* 父进程等待子进程退出,并获取退出状态*/ perror("fork error"); exit(-1); } print_exit_status(status); /* 打印第二个退出状态信息 */ return 0; }
运行结果如下:
pid_t waitpid (pid_t pid, int *status, int options);
参数:
pid 要等待的进程pid
或者:
-1 表示等待任意一个子进程,这样与wait等效
0 等待 与调用进程处于同一进程组 的进程
<-1 等待进程组的所有子进程
status 和上面wait一样
options 0 或者 下列选项
简单的说 ,waitpid可以做到:1、等待一个特定的子进程。2、非阻塞获得子进程的状态
刚才我们使用过的
wait (&status);
等效于
waitpid (-1, &status, 0);
这里说一下阻塞式和非阻塞式
引用一个故事:
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
在这里呢,阻塞式,就是父进程一直等,等到子进程有返回为止。
就像老张等谁开一样,不高效。非阻塞就是隔一会父进程去看看子进程完事了没。
那么我们如何修改参数,让wait实现阻塞非阻塞的效果?
waitpid(pid,status,WNOHANG);
意思就是每隔一小段时间,子进程都会返回一个值,直到有一次返回的值是子进程pid,证明子进程死亡。
补充前面提过的一个概念:进程组
每个进程,有属于自己的组,就是他所在的进程组。
进程组和进程一样,有自己的ID
可以这样获取进程组ID
//返回值为调用该函数的进程的进程组ID pid_t getpgrp(void);
每一个进程组,有一个进程组组长,进程组的ID,就是组长的ID
int setpgid(pid_t pid, pid_t pgid);
将pid 进程的进程组ID设置为 pgid。如果这两个参数相等,是将pid任命为进程组组长。
再fork后调用该函数
父进程设置其子进程的进程组ID,然后子进程自己设置自己的进程组ID
这样可以保证父子进程在同一个进程组。
既然有了进程组,就要提一下对话期的概念
对话期 是一个或多个进程组的集合。
建立一个对话期
pid_t setsid(void);
成功返回进程组 ID
失败返回 -1
(1) 此进程变成该新对话期的对话期首进程(session leader,对话期首进程是创建该对话期的进程)。此进程是该新对话期中的唯一进程。
(2) 此进程成为一个新进程组的组长进程。新进程组I D是此调用进程的进程ID。
(3) 此进程没有控制终端(下一节讨论控制终端)。如果在调用setsid之前此进程有一个控制终端,那么这种联系也被解除。
如果调用该函数的进程不是进程组组长,才会创建一个新对话期。
如果是组长,则返回出错。
守护进程
Daemon进程,后台服务进程。
下面是一个创建守护进程的方法。
代码如下:
/*dameon.c 创建守护进程实例*/ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<sys/types.h> #include<unistd.h> #include<sys/wait.h> #define MAXFILE 65535 int main() { pid_t pc; int i,fd,len; char *buf="This is a Dameon "; len =strlen(buf); pc=fork(); //第一步 if(pc<0) { printf("error fork "); exit(1); } else if(pc>0) exit(0); /*第二步*/ setsid(); /*第三步*/ chdir("/"); /*第四步*/ umask(0); /*第五步*/ for(i=0;i<MAXFILE;i++) close(i); /*这时创建完守护进程,以下开始正式进入守护进程工作*/ while(1) { if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0) { perror("open"); exit(1); } write(fd, buf, len+1); close(fd); sleep(10); } }