【Linux】进程控制(上)
进程创建
fork函数:从已存在进程中创建一个新进程,新进程为子进程,原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程返回0,父进程返回子进程id,出错返回-1.
执行到fork代码后,内核做:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度
写时拷贝:父子的代码通常为共享,在不进行写入时,数据也是共享的,当任意一方试图写入,就会以写时拷贝的方式拷贝一份副本。
进程终止
进程退出场景
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常中止
注意:代码异常终止时,本质是代码没有跑完,进程的退出码无意义。
程序出异常时,异常会被系统转换成信号并发送给进程,从而让进程直接退出。
进程常见退出方式
正常终止(echo $?可查看最近一个进程的退出码):
1.从main返回
2.调用exit
3.调用_exit
异常退出:
ctrl+c , 信号终止。
_exit函数
#include <unistd.h>
void _exit(int status);
参数:status定义了进程的终止状态,父进程通过wait来获取该值。
exit函数
#include <unistd.h>
void exit(int status);
exit最后也会调用_exit,但在调用之前,还做了其他工作:
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit
由以上图可知:缓冲区一定不在内核中。
printf一定是先把数据写入缓冲区,合适的时候再进行刷新。
退出码:可用return n或exit(n)返回不同的数字,表示不同的出错原因。
进程等待
必要性:
1.僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄漏的问题。
2.我们需要知道父进程派给子进程的任务完成的如何,获取子进程退出信息。
进程等待的方法:
1.wait方法
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
返回值:成功返回被等待进程pid,失败返回-1.
参数:输出型参数,获取子进程退出状态,不关心则可以设置成NULL。
2.waitpid方法
pid_t waitpid(pid_t pid,int* status,int options);
头文件同上
返回值:
1.当正常返回时为子进程的ID.
2.若设置了选项WNOHANG,且调用中waitpid发现没有已退出的子进程可收集,则返回0.
3.若调用中出错,则返回-1,这时errno会被设置成相应的值指示错误所在。
参数:
pid:pid=-1,等待任一个子进程。与wait等效。pid>0,等待其进程ID与pid相等的子进程。
status:WIFEXITED(status):子进程正常退出时为真。WEXITSTATUS(status):若子进程正常退出,则提取其退出码。
options:0:阻塞等待WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不等待。若正常结束,则返回该子进程ID。
1.若子进程已经退出,调用wait/waitpid时,会立即返回并释放资源,获得子进程退出信息。
2.子进程存在且正常运行时,进程可能阻塞。
3.若不存在子进程,则立即出错返回。
status
只考虑它32bit位的低16位。
1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <sys/types.h>5 #include <sys/wait.h>6 7 #define N 108 9 void RunChild()10 {11 int cnt=5;12 while(cnt--)13 {14 printf("i am a child process,pid:%d,ppid:%d\n",getpid(),ge tppid());15 sleep(1);16 }17 }18 int main()19 {20 for(int i=0;i<N;i++)21 {22 pid_t id=fork();23 if(id==0)24 {25 RunChild();26 exit(i); 27 }28 printf("creat child process:%d success\n",id);29 }30 sleep(10);for(int i=0;i<N;i++)32 {33 // pid_t id=wait(NULL);34 int status=0;35 pid_t id=waitpid(-1,&status,0);//阻塞等待36 if(id>0)37 {38 printf("wait %d success,exit code:%d\n",id,WEXITSTATUS (status));39 }40 }41 sleep(5);42 return 0;43 }
非阻塞轮询:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define N 10
#define TASK_NUM 10typedef void(*task_t)();//定义task_t为函数指针类型。
task_t tasks[TASK_NUM];//定义tasks为函数指针数组。
void task1()
{printf("这是一个执行打印日志的任务, pid: %d\n", getpid());
}void task2()
{printf("这是一个执行检测网络健康状态的一个任务, pid: %d\n", getpid());
}void task3()
{printf("这是一个进行绘制图形界面的任务, pid: %d\n", getpid());
}
int AddTask(task_t t);
void InitTask()
{for(int i=0;i<TASK_NUM;i++){tasks[i]=NULL;//初始化数组内的每一个函数指针都为空。}AddTask(task1);AddTask(task2);AddTask(task3);
}
int AddTask(task_t t)
{int pos=0;for(;pos<TASK_NUM;pos++){if(!tasks[pos])break;//找到数组内元素为空的位置准备赋值。}if(pos==TASK_NUM){return -1;//数组已满则无法继续添加。}tasks[pos]=t;return 0;
}
void ExecuteTask()
{for(int i=0;i<TASK_NUM;i++){if(!tasks[i]){continue;}tasks[i]();//若任务为空,则进入下一次循环,不为空,则调用。}
}
int main()
{pid_t id=fork();if(id<0){perror("fork");return 1;}else if(id==0){int cnt=5;while(cnt--){printf("I am child,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);sleep(1);}exit(11);}else{int status=0;InitTask();while(1){//轮询pid_t ret=waitpid(id,&status,WNOHANG);if(ret>0){if(WIFEXITED(status)){printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));}else{printf("进程出异常了!\n");}}else if(ret<0){//调用这个函数的过程中出错。printf("wait failed!\n");break;}else{ExecuteTask();usleep(500000);}}}return 12;
}