父子进程的关系
- 子进程是父进程的副本。
- 子进程获得父进程数据段,堆,栈,正文段共享。
在fork之后,一般情况哪个会先运行,是不确定的。如果非要确定那个要先运行,需要IPC机制。
区别:
- fork的返回值
- pid不同
进程的创建
- 创建之后,父子进程各自拥有4g独立的内存空间
- 各自拥有自己的相关的程序的各个段数据段,所以,各自之间对数据的改变,不会相互影响。
- 子进程会继承父进程已打开的文件描述符,若fork之前打开文件,父子进程操作同一个文件,相互间有影响。fork之后打开文件,父子进程操作同一个文件,但因为各自拥有自己的 "文件表项",所以,各自按照自己的逻辑改变文件。
进程的执行
exec函数族
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
新进程,所以调用exec前后该进程的id并未改变。有六种以exec开头的函数,统称exec函数:
exec函数族用于执行一个文件,用新进程的镜像替换调用进程的镜像。主要包括以下几种函数:
1)int execl (const char *path, const char *arg, ... /* (char *) NULL */);
2)int execlp (const char *file, const char *arg, ... /* (char *) NULL */);3)int execle (const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
4)int execv (const char *path, char *const argv[]);
5)int execvp (const char *file, char *const argv[]);
6)int execvpe (const char *file, char *const argv[], char *const envp[]);
区别:
(1)前4个使用路径名作为参数,后面两个使用文件名做参数。当filename中,含有/时视为路径名,否则就按PATH变量,在指定目录下查找可执行文件。
(2)相关的参数表传递
- l表示list,v表示vector
- execl,execlp,execle,需要将参数一个一个列出,并以NULL结尾。
- execv,execvp,execve,需要构造一个参数指针数组,然后将数组的地址传入。
(3)以e结尾的函数,可以传入一个指向环境字符串的指针数组的指针。其他未指定环境变量,使用父进程继承过来的。execve 是真正的系统调用
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错
则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
(1)execl 函数和 execv 函数
参数传递方式:
1)带 l
的函数(如execl
)参数逐个列举,以NULL
结尾。
2)带 v
的函数(如execv
)参数组织成指针数组的形式。
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("---begin---\n");
// execl("/bin/ls","ls","-l",".",NULL);
char * const arg[] ={"ls","-l",".",NULL};
execv("/bin/ls",arg);
execl("/home/linux/linux_prog/01-prog/mycp","mycp","1.c","2.c",NULL);
printf("---end---\n");
return 0;
}
(2)execvp 函数和execle函数
文件寻找方式:
1)带p
的函数(如execvp
)表示可执行文件的寻找方式是从系统的环境变量PATH
中的路径下面去找。
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
printf("---begin---\n");
//if (execlp("mycp","mycp","1.c","2.c",NULL) < 0)
char *const arg[] = {"mycp","1.c","2.c",NULL};
if (execvp("mycp",arg) < 0)
{
perror("execl fail");
return -1;
}
printf("---end---\n");
return 0;
}
2)带e
的函数(如execle
)表示可以给要执行的新程序传递需要的环境变量。
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(int argc, const char *argv[])
{
char *menv[] = {"USER=linux","PASSWD=123456",NULL};
//execle("./myenv","myenv",NULL,environ);
//execle("./myenv","myenv",NULL,menv);
execle("./myenv","myenv",NULL,menv);
return 0;
}
在fork函数中的应用
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int execvpe(const char *file, char *const argv[],
char *const envp[]);
int main(int argc, const char *argv[])
{
char *env[5] = {NULL};
char username[20];
char passwd[20];
while (1)
{
printf("Input username and passwd:");
fgets(username,sizeof(username),stdin);
username[strlen(username)-1] = '\0';
fgets(passwd,sizeof(passwd),stdin);
passwd[strlen(passwd)-1] = '\0';
env[0] = username;
env[1] = passwd;
pid_t pid = fork();
if (pid < 0)
{
perror("fork fail");
return -1;
}
if (pid == 0)
{
char *const arg[] = {"check",NULL};
if (execvpe("check",arg,env) < 0)
{
perror("execvpe fail");
return -1;
}
}
}
return 0;
}
进程的终止
八种情况:
(1)正常结束:
- main 中 return
- exit() //库函数 。c库函数,会执行io库的清理工作,关闭所有 的流,以及所有打开的文件注册清理函数(atexit)。
- _exit,_Exit 会关闭所有的已经打开的文件,不执行清理函数。 //系统调用
- 主线程退出
- 主线程调用pthread_exit。
(2)异常终止:
- abort()
- signal kill pid
- 最后一个线程被pthread_cancle开辟堆区空间
进程的退出
(1)孤儿进程:
子进程还在,但父进程已经结束。此时,避免子进程将来没有人收尸,由init进程来收养子进程。init(1) --- child(xxx)
(2)僵尸进程:
父进程还在,子进程先结束了。父进程没有做收尸操作。此时,子进程进入僵尸态。(wait/waitpid --- 查看子进程的退出状态)进程执行结束但空间未被回收变成僵尸进程。
(1)exit 库函数
void exit(int status);
- 退出状态,终止的进程会通知父进程,自己使如何终止的。
- 如果是正常结束(终止),则由exit传入的参数。
- 如果是异常终止,则有内核通知异常终止原因的状态。
- 任何情况下,父进程都能使用wait,waitpid获得这个状态,以及资源的回收。
(1)功能:让进程退出,并刷新缓存区
(2)参数:status:进程退出的状态
(3)返回值:缺省
(2)_exit 系统调用
void _exit(int status);
(1)功能:让进程退出,不刷新缓存区
(2)参数:status:进程退出状态
(3)返回值: 缺省
(3)atexit 回调函数
int atexit(void (*function)(void));
(1)功能:注册进程退出前执行的函数
(2)参数:function:函数指针,指向void返回值void参数的函数指针
(3)返回值: 成功返回0,失败返回非0
当程序调用exit或者由main函数执行return时,所有用atexit。 注册的退出函数,将会由注册时顺序倒序被调用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int *q = NULL;
void cleanup1(void)
{
free(q);
printf("----cleanup 1---\n");
}
void cleanup2(void)
{
printf("----cleanup 2---\n");
}
int main(int argc, const char *argv[])
{
atexit(cleanup1);
atexit(cleanup2);
int *p = malloc(4);
q = p;
*p = 4;
printf("*p = %d\n",*p);
printf("hello world!\n");
//_exit(0);
//_exit(0);
return 0;
}
注意:
-
exit函数调用时,会调atexit函数 。_exit函数调用时,不会调到atexit
- atexit 程序正常结束。(1).main 返回 //exit 。(2).exit()
- atexit函数 可以多次注册
- 最后"退出清理函数"的调用顺序,与注册顺序相反。
进程空间的回收
wait/waitpid函数
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
(1)功能:该函数可以阻塞等待任意子进程退出并回收该进程的状态。一般用于父进程回收子进程状态。
(2)参数:status 进程退出时候的状态如果不关心其退出状态一般用NULL表示。如果要回收进程退出状态,则用WEXITSTATUS回收。
(3)返回值:成功 回收的子进程pid。 失败 -1;
标签:const,函数,int,char,线程,进程,NULL From: https://blog.csdn.net/qq_69639971/article/details/141171218