进程创建
进程:内核的相关管理数据结构(task_struct+mm_struct+页表)+代码(<-共享)和数据(<-写时拷贝)
fork函数初识
#include <unistd.h>pid_t fork(void);返回值:自进程中返回 0 ,父进程返回子进程 id ,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程将父进程部分数据结构内容拷贝至子进程添加子进程到系统进程列表当中fork 返回,开始调度器调度
int main( void ){pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 )perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;}运行结果:[root@localhost linux]# ./a.outBefore: pid is 43676After:pid is 43676, fork return 43677After:pid is 43677, fork return 0
fork函数返回值
写时拷贝
fork常规用法
fork调用失败的原因
进程终止
终止是在做什么?
释放曾经的代码和数据所占据的空间
释放内核数据结构
进程退出场景
进程常见退出方法
echo $?
echo:内建命令,打印的都是bash内部的变量数据
$?:父进程获取到的最近一个子进程退出的退出码
退出码为0:成功
退出码为非0:表示失败,不同的非0值,一方面表示失败,另一方面表示失败的原因
退出码可以使用默认,也可以自定义
_exit函数(系统调用)
#include <unistd.h>void _exit(int status);参数: status 定义了进程的终止状态,父进程通过 wait 来获取该值
exit函数 (库函数)
#include <stdlib.h>void exit(int status);
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
int main(){printf("hello");exit(0);}运行结果 :[root@localhost linux]# ./a.outhello[root@localhost linux]#int main(){printf("hello");_exit(0);}运行结果 :[root@localhost linux]# ./a.out[root@localhost linux]#
return退出
exit和_exit异同
相同:在代码的任意位置调用exit(_exit),都表示进程退出
异:exit在进程退出时候,会充刷缓存区,而_exit不会
进程等待
进程等待必要性
之前讲过,子进程退出,父进程如果不管不顾,就可能造成 ‘ 僵尸进程 ’ 的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就刀枪不入, “ 杀人不眨眼 ” 的 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法
wait方法
#include<sys/types.h>#include<sys/wait.h>pid_t wait(int*status);返回值:等待成功返回被等待进程(子进程) pid ,失败返回 -1 。参数:输出型参数,获取子进程退出状态 , 不关心则可以设置成为 NULL
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);返回值:当正常返回的时候waitpid 返回收集到的子进程的进程 ID ;如果设置了选项WNOHANG, 而调用中 waitpid 发现没有已退出的子进程可收集 , 则返回 0 ;如果调用中出错, 则返回 -1, 这时 errno 会被设置成相应的值以指示错误所在;参数:pid :Pid=-1,等待任一个子进程。与 wait 等效。Pid>0.等待其进程 ID 与 pid 相等的子进程。status(获取子进程的退出信息):WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若 WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)options(非阻塞等待):WNOHANG: 若 pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的ID 。宏函数:WIFEXITED、WEXITSTATUS宏:WNOHANG
获取子进程status
测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void ){
pid_t pid;
if ( (pid=fork()) == -1 )
perror("fork"),exit(1);
if ( pid == 0 ){
sleep(20);
exit(10);
} else {
int st;
int ret = wait(&st);
if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
printf("child exit code:%d\n", (st>>8)&0XFF);
} else if( ret > 0 ) { // 异常退出
printf("sig code : %d\n", st&0X7F );
}
}
}
测试结果:
[root@localhost linux]# ./a.out #等20秒退出
child exit code:10
[root@localhost linux]# ./a.out #在其他终端kill掉
sig code : 9
具体代码实现
进程的阻塞等待方式:
int main()
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
}
else if (pid == 0) { //child
printf("child is run, pid is : %d\n", getpid());
sleep(5);
exit(257);
}
else {
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
}
else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
运行结果:
[root@localhost linux] # . / a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is : 1.
进程的非阻塞等待方式:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
}
else if (pid == 0) { //child
printf("child is run, pid is : %d\n", getpid());
sleep(5);
exit(1);
}
else {
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if (ret == 0) {
printf("child is running\n");
}
sleep(1);
} while (ret == 0);
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
}
else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
进程程序替换
替换原理
替换函数
#include <unistd.h>`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 execve(const char *path, char *const argv[], char *const envp[]);(系统调用)
函数解释
即:让子进程执行一个全新的程序,会让代码跟数据都产生写时拷贝,保证进程的独立性!!!
命名理解
l(list) : 表示参数采用列表v(vector) : 参数用数组p(path) : 有 p 自动搜索环境变量 PATHe(env) : 表示自己维护环境变量
例:
#include <unistd.h>
int main()
{
char* const argv[] = { "ps", "-ef", NULL };
char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL };
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}