学习笔记(二)之进程:
进程:本质是程序一次动态执行的过程 动态的 有生命周期 不能保存(临时存放于RAM上)
程序:本质是由源码编译生成的二进制可执行文件(指令和数据的集合) 静态的 无生命周期
可以报存(ROM)
注:即使是同一个程序多次执行产生进程也是不同的进程!!!
进程动态执行的一次过程:
1.创建 fork exec
2.调度
分时:将CPU的工作时间进行等均切割,进行合理分配,实现宏观上并行,微观上串行
进程基本三态: 就绪态 运行态 阻塞态
进程管理命令:
1.ps 进程信息截屏
ps -ef
ps aux
UID:用户编号
PID:进程编号 init 1 始祖进程
PPID:父进程编号
STAT:进程状态
R:运行态
T:暂停态
S:阻塞态
Z:僵尸态
<:进程优先级较高
N:进程优先级较低
s:进程的领导者
l:多线程
+:在前台运行
2.kill命令:给指定进程发送信号
kill -信号编号 PID
kill -l:列出信号 SIGINT 2 ctrl+c
SIGSTOP 19 ctrl+z
SIGKILL 9
top 类似windows 任务管理器 q退出界面
pstree 以树状图形式显示所有进程
3.执行 CPU执行
4.消亡 exit _exit
进程三要素:
1.程序 指令代码
2.内存分段
3.进程控制块 PCB task_struct 结构体 用于记录进程信息(PID,STAT,指向父进程的指针,记录所有子进程的列表)
getpid:获取调用进程的PID
pid_t getpid(void);
getppid:获取调用进程父进程的PID
pid_t getppid(void);
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 5 6 7 int main(int argc, const char *argv[]) 8 { 9 pid_t pid,ppid; 10 pid = getpid(); 11 ppid = getppid(); 12 13 printf("%d %d\n",pid,ppid); 14 while(1) 15 { 16 ; 17 } 18 19 return 0; 20 }
进程创建:fork
pid_t fork(void);
功能:将当前调用进程进行精确的拷贝产生一个新的进程,新的进程称为子进程,调用进程称为父进程
返回值:失败在父进程中返回-1,并设置errno号,子进程不会被创建
成功时,在父进程中看到的返回值是子进程的pid,在子进程中看到的返回值是0
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <stdlib.h> 5 #include <sys/wait.h> 6 7 8 9 int main(int argc, const char *argv[]) 10 { 11 pid_t pid; 12 pid_t pid_ret; 13 14 15 pid = fork(); //该语句后子进程出现 16 if(pid < 0) //创建子进程失败 17 { 18 perror("fail to fork"); 19 exit(1); 20 } 21 else if(pid == 0) //子进程看到的代码块 22 { 23 printf("pid:%d child_pid:%d ppid:%d\n",pid,getpid(),getppid()); 24 sleep(5); 25 exit(0); 26 } 27 else // pid>0 父进程看到的代码块,得到的返回值是子进程的pid 28 { 29 printf("child_pid:%d pid:%d\n",pid,getpid()); 30 // pid_ret = wait(NULL);//阻塞等待子进程退出 31 32 // pid_ret = waitpid(-1,NULL,0);waitpid阻塞 33 34 // pid_ret = waitpid(-1,NULL,WNOHANG);waitpid非阻塞 35 printf("pid_ret:%d\n",pid_ret); 36 exit(0); 37 } 38 39 return 0; 40 }
进程退出:exit man 3 _exit man 2
exit
void exit(int status);
功能:退出当前进程,刷新和关闭所有打开的文件流,并返回退出状态给当前进程的父进程 status & 0377
参数:退出状态可以自定义 官方提供的宏可以直接使用 EXIT_SUCCESS 0 EXIT_FAILURE 1
_exit
void _exit(int status);
功能:立刻退出当前进程,关闭所有属于该进程的文件描述符,将所有的子进程交给init关系,给当前进程的父进程发送SIGCHLD
的信号,并返回退出状态给父进程
参数:退出状态可以自定义 官方提供的宏可以直接使用 EXIT_SUCCESS 0 EXIT_FAILURE 1
wait
pid_t wait(int *status);
功能:1.用与父进程中阻塞等待任意子进程状态改变(1.子进程退出(使用exit) 2.被信号停止 3.被信号恢复),2.当子进程退出时父进程会使用wait"收尸"(回收子进程相关资源),回收不及时会造成僵尸进程!
返回值:成功返回被回收的子进程的PID,失败返回-1
参数:如果不关子进程的状态改变,则直接填NULL
如果关心子进程状态改变,准备一个int型变量,取地址放入wait中,结合一下宏函数操作:
宏函数:
WIFEXITED(status) 判断子进程是否正常退出(使用exit _exit) 如果是返回true
WIFSIGNALED(status) 判断子进程是否被信号中断 如果是则返回true
WEXITSTATUS(status) 使用时必须保证该子进程退出时使用的时exit或者_exit退出
解析子进程使用exit函数返回status中的信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <stdlib.h> 5 #include <sys/wait.h> 6 7 8 9 int main(int argc, const char *argv[]) 10 { 11 pid_t pid; 12 13 14 pid = fork(); //该语句后子进程出现 15 if(pid < 0) //创建子进程失败 16 { 17 perror("fail to fork"); 18 exit(1); 19 } 20 else if(pid == 0) //子进程看到的代码块 21 { 22 printf("pid:%d child_pid:%d ppid:%d\n",pid,getpid(),getppid()); 23 sleep(10); 24 exit(2); 25 } 26 else // pid>0 父进程看到的代码块,得到的返回值是子进程的pid 27 { 28 printf("child_pid:%d pid:%d\n",pid,getpid()); 29 int s; 30 wait(&s);//阻塞等待子进程退出 31 if(WIFEXITED(s)) 32 { 33 printf("child is exit!\n"); 34 printf("exit_status:%d\n",WEXITSTATUS(s)); 35 } 36 else if(WIFSIGNALED(s)) 37 { 38 printf("child is killed by signal!\n"); 39 } 40 41 exit(0); 42 } 43 44 return 0; 45 }
waitpid:
pid_t waitpid(pid_t pid, int *status, int options);
参数:
1.pid:
-1:代表等待任意子进程状态发生改变
>0:(子进程pid编号)等待指定的子进程状态发生改变
0:代表等待与当前进程同一组内的所有子进程的状态发生改变
2.同wait一致
3.选项 0 默认阻塞(与wait一致) WNOHANG 默认非阻塞
返回值:成功返回回收子进程的pid号,失败返回-1,如果选项使用的是WNOHANG,则没有子进程退出时返回0
waitpid(-1,&status,0) == wait(&status)
僵尸进程:僵尸进程是问题,要解决 详见:man 2 wait NOTES
孤儿进程:不是问题,当父进程退出,子进程还没有结束,所有的子进程就变成孤儿进程,交给init管理
init能一瞬间回收所有退出的子进程
1.wait waitpid 无法同时回收多个退出的子进程,有效率问题
2.信号注册 SIGCHLD
signal
sighandler_t signal(int signum, sighandler_t handler);
功能:向内核注册指定信号,当当前进程收到该对应信号时,执行指定操作
参数:1.需要被注册的信号 2.指定操作
signal(SIGCHLD,SIG_IGN); 信号忽略操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <stdlib.h> 5 #include <sys/wait.h> 6 #include <signal.h> 7 8 9 10 int main(int argc, const char *argv[]) 11 { 12 signal(SIGCHLD,SIG_IGN); 13 //向内核注册SIGCHLD,当该进程收到这个信号时,选择忽略 14 15 pid_t pid; 16 pid_t pid_ret; 17 18 19 pid = fork(); //该语句后子进程出现 20 if(pid < 0) //创建子进程失败 21 { 22 perror("fail to fork"); 23 exit(1); 24 } 25 else if(pid == 0) //子进程看到的代码块 26 { 27 printf("pid:%d child_pid:%d ppid:%d\n",pid,getpid(),getppid()); 28 exit(0); 29 } 30 else // pid>0 父进程看到的代码块,得到的返回值是子进程的pid 31 { 32 sleep(3); 33 pid_ret = waitpid(-1,NULL,WNOHANG);//waitpid非阻塞 34 printf("pid_ret:%d\n",pid_ret); 35 exit(0); 36 } 37 38 return 0; 39 }
3.父子孙
父创建子 子立马创建孙 子自杀 父进程回收子,父做自己的事 孙交给init,孙进程做自己的事
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 6 7 int main(int argc, const char *argv[]) 8 { 9 pid_t pid1,pid2; 10 11 12 pid1 = fork(); //父创建子进程 13 if(pid1 < 0) 14 { 15 perror("fail to fork1"); 16 exit(1); 17 } 18 else if(pid1 == 0) //子进程 19 { 20 pid2 = fork(); //子创建孙 21 if(pid2 < 0) 22 { 23 perror("fail to fork2"); 24 exit(1); 25 } 26 else if(pid2 == 0)//孙进程 27 { 28 sleep(5); 29 printf("父亲被残忍杀害,爷爷不管,交给孤儿院!\n"); 30 printf("getppid:%d\n",getppid()); 31 exit(0); 32 } 33 else //子进程 34 { 35 printf("惨无人道被杀害的子进程,心态爆炸!\n"); 36 exit(0); 37 } 38 } 39 else //父进程 40 { 41 wait(NULL); 42 //回收自杀的子进程,由于子进程创建孙进程很快,不做任何事就退出,父进程等待回收时基本不会受到影响 43 printf("嘿嘿嘿!\n"); 44 exit(0); 45 } 46 47 return 0; 48 }
exec函数族: 将指定的新的可执行文件替换当前进程的0~3G空间
l:list(以列表形式排布) p:path(通过shell查找) e:env 环境 v: vector
execl:
int execl(const char *path, const char *arg, ...);
参数:1.新的可执行文件的路径 2.命令行传参,以列表形式排布,以NULL结尾 3. ...代表可变长度传参
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 4 5 6 int main(int argc, const char *argv[]) 7 { 8 printf("before execl!\n"); 9 10 execl("/home/xyx/teach/18072/Pro_2/hello","./hello",NULL); 11 12 printf("after execl!\n"); 13 14 return 0; 15 }
execlp:
int execlp(const char *file, const char *arg, ...);
参数:1.新的可执行文件名(自动查找路径) 2.命令行传参,以列表形式排布,以NULL结尾 3. ...代表可变长度传参
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 4 5 6 int main(int argc, const char *argv[]) 7 { 8 printf("before execlp!\n"); 9 10 execlp("ls","ls","-l",NULL); 11 12 printf("after execlp!\n"); 13 14 return 0; 15 }
execv:
int execv(const char *path, char *const argv[]);
参数:1.新的可执行文件路径 2.命令行传承,以数组方式填写(自己准备数组,最后一个元素依旧是NULL)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 4 5 6 int main(int argc, const char *argv[]) 7 { 8 printf("before execv!\n"); 9 char * a[] = {"./hello",NULL}; 10 execv("/home/xyx/teach/18072/Pro_2/hello",a); 11 printf("after execv!\n"); 12 return 0; 13 }
execle:
int execle(const char *path, const char *arg, ..., char * const envp[]);
参数:1.新的可执行文件的路径 2.命令行传参,以列表形式排布,以NULL结尾 3. ...代表可变长度传参 4.自己提供的环境变量
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 4 5 6 int main(int argc, const char *argv[]) 7 { 8 printf("before execle!\n"); 9 char * env[] = {"PATH=hehe","LOGANNAME=heihei",NULL}; 10 execle("/usr/bin/env","env",NULL,env); 11 printf("after execle!\n"); 12 return 0; 13 }
终端上执行a.out:
bash --> fork --> exec(a.out)
system:跳出进程执行shell命令,执行完成后返回当前调用进程
int system(const char *command);
参数:shell命令
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 5 6 int main(int argc, const char *argv[]) 7 { 8 printf("before system!\n"); 9 system("ls -l"); 10 printf("after system!\n"); 11 return 0; 12 }
交互进程:与终端产生交互
前台进程(输入输出都存在): 后台运行过程中输入fg切换为前台运行
后台进程(只有输出): 后台运行a.out: (1) ./a.out & (2)ctrl+z暂停进程 用bg回复进程到后台运行
守护进程:与终端无关 ,从开始运行,一直到主动杀死或者操作系统结束才会终止
1.创建子进程,父进程退出,子进程交给init接管
2.设置当前进程为会话组组长
setsid:
pid_t setsid(void);
3.改变了工作路径
chdir
int chdir(const char *path);
4.更改文件权限掩码
umask(0)
mode_t umask(mode_t mask);
5.关闭文件描述符
getdtablesize: 返回当前进程能打开的最大的文件个数 1024
int getdtablesize(void);
利用for结合close循环关闭文件描述符
注:有输出时需要依靠fflush函数实现
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 7 8 int main(int argc, const char *argv[]) 9 { 10 pid_t pid; 11 12 13 pid = fork(); 14 if(pid < 0) 15 { 16 17 } 18 else if(pid == 0) 19 { 20 //设置会话组组长 21 setsid(); 22 23 //更改工作路径 24 chdir("/home/xyx"); 25 26 //更改文件权限掩码 27 umask(0); 28 29 30 //关闭所有打开的文件描述符 31 int size_fd; 32 size_fd = getdtablesize(); 33 int i; 34 for(i = 0;i < size_fd;i++) 35 { 36 close(i); 37 } 38 printf("11111\n"); 39 sleep(50); 40 } 41 else 42 { 43 exit(0); 44 } 45 46 47 48 return 0; 49 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <time.h> 7 8 9 int main(int argc, const char *argv[]) 10 { 11 pid_t pid; 12 13 14 pid = fork(); 15 if(pid < 0) 16 { 17 18 } 19 else if(pid == 0) 20 { 21 //设置会话组组长 22 setsid(); 23 24 //更改工作路径 25 chdir("/home/xyx"); 26 27 //更改文件权限掩码 28 umask(0); 29 30 31 //关闭所有打开的文件描述符 32 int size_fd; 33 size_fd = getdtablesize(); 34 int i; 35 for(i = 0;i < size_fd;i++) 36 { 37 close(i); 38 } 39 40 FILE *fp; 41 fp = fopen("/home/xyx/log.txt","a"); 42 if(fp == NULL) 43 { 44 perror("fail to fopen"); 45 exit(1); 46 } 47 48 time_t t; 49 50 while(1) 51 { 52 t = time(NULL); 53 fprintf(fp,"%s\n",ctime(&t)); 54 fflush(fp); 55 sleep(1); 56 } 57 } 58 else 59 { 60 exit(0); 61 } 62 63 64 65 return 0; 66 }