1、创建进程的函数为fork
- 子进程会在当前进程的基础上创建
- 创建的子程与当前进程都会执行进程后的代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
pid_t pid = getpid();
printf("pid:%u\n" ,pid);
// 从此处开始,后面的代码会被执行2次
pid_t fork_pid = fork();
if (fork_pid == -1){
perror("fork error:");
exit(1);
}else if (fork_pid == 0){
// 为0是返回给子线程,表明线程创建成功
// 当前操作在子线程中执行
printf("子进程:pid:%u, ppid:%u\n", getpid(), getppid());
}else{
// 父线程中返回的是子线程id号
printf("父进程:pid:%u, ppid:%u\n", getpid(), getppid());
}
printf("fork:%u\n", fork_pid);
return 0;
}
输出结果为:
pid:30963
父进程:pid:30963, ppid:11330
fork:30964
子进程:pid:30964, ppid:30963
fork:0
2、循环创建进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
printf("父进程id:%u\n", getpid());
int i;
for(i = 0;i < 5; i++){
pid_t pid = fork();
if (pid == -1){
perror("fork error:");
}else if(pid == 0){
// 子进程跳出循环,以免再次创建进程,指数级增长
printf("第%d个子进程,id:%u\n", i+1, getpid());
break;
}else{
sleep(1);
}
}
return 0;
}
输出结果为:
父进程id:19575
第1个子进程,id:19576
第2个子进程,id:19577
第3个子进程,id:19581
第4个子进程,id:19585
第5个子进程,id:19589
3、进程共享
- 不同进程之间,内存空间相互独立,互不影响
- fork时:
- 相同点:全局变量、数据段、代码段、栈、堆、环境变量、用户ID、主目录、进程工作目录等;
- 不同点:进程id、fork返回值、父进程id、进程运行时间、闹钟(定时器)、未决信号集等;
- fork后:
- 读时共享,写时复制的原则
- 先执行子进程还是父进程是不确定的,取决于内核所使用的调度算法;
- 共享文件描述符
- 共享mmap建立的映射区
4、gdb
- set follow-fork-mode
- parent —> 跟踪父进程
- child —> 跟踪子进程
5、exec函数簇
extern char **environ;
// 第一个参数为函数路径,接下来的参数为程序的转入参数从argv[0]开始
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
// execle中的e指的是environ,环境变量表
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
// 相对execl函数,execlp加载了环境变量中的PATH,可通过环境变量寻找执行目标
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
// execv中的v指的是argv,
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvP(const char *file, const char *search_path, char *const argv[]);
- 该函数的作用在于使子进程执行指定的程序,而非沿着父进程继续往下重复执行
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(){ // 创建一个子进程 pid_t pid = fork(); if (pid == -1){ perror("fork error:"); exit(1); }else if(pid == 0){ // 子进程执行ls命令以及ls命令需要的输入入参数 //execl("/bin/ls", "ls", "-l", "-a", NULL); //execlp("ls", "ls", "-l", "-a", NULL); char* argv[] = {"ls", "-l", "-a", NULL}; execv("/bin/ls", argv); }else{ // 父进程 sleep(1); printf("pid:%u\n", getpid()); } return 0; }
执行结果:
total 24 drwxrwxr-x 2 parallels parallels 4096 Jan 6 11:33 . drwxrwxr-x 8 parallels parallels 4096 Jan 6 11:13 .. -rwxrwxr-x 1 parallels parallels 8560 Jan 6 11:33 a.out -rw-rw-r-- 1 parallels parallels 333 Jan 6 11:33 exec.c pid:11479
- fork、exec与dup2的综合使用
- 要求实现ls结果输出到文件中,终端可直接使用ls -l > out.txt方式完成
- dup2是dupto的意思,文件描述符的拷贝
- 文件描述符表前三个0、1、2分别表示stdin、stdout、stderr
- 如果需要将输出结果转移到文件,可以通过dup2将stdout指向某个特定的fd
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main(){ pid_t pid = fork(); if (pid == -1){ perror("fork error"); exit(1); }else if(pid > 0){ sleep(1); }else{ // 子进程 int fd = open("file.txt", O_CREAT | O_RDWR, 0664); // 将文件描述符stdout,dupto fd,即将原本打印在终端的信息写入fd int ret = dup2(fd, STDOUT_FILENO); if (ret == -1){ perror("dup2"); exit(1); } // 注意:该函数如果没有出错则无返回值,即意味着后续代码不会执行!!! execlp("ls", "ls", "-l", "-a", NULL); // 如果execlp执行成功,不会来到这里 close(fd); } return 0; }
6、孤独进程
- 父进程先于子进程结束,子进程将处于无人管辖的状态,此时子进程会被系统收入“孤独院”,此后子进程的父进程为孤儿院进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
// 创建一个新进程
pid_t pid = fork();
if (pid == -1){
perror("fork error:");
exit(1);
}else if (pid == 0){
// 子进程
int i;
for (i = 0; i < 5; i++){
// 父进程结束后,父进程默认为init进程,一般为1,所以有孤儿进程都会被挂在此
printf("I am child, pid=%u, ppid=%u\n", getpid(), getppid());
sleep(1);
}
}else{
// 父进程
sleep(2);
printf("I am father, pid=%u, I will died\n", getpid());
}
return 0;
}
执行结果
I am child, pid=2570, ppid=2569
I am child, pid=2570, ppid=2569
I am father, pid=2569, I will died
I am child, pid=2570, ppid=1
parallels@ubuntu:~/Linux/process$ I am child, pid=2570, ppid=1
I am child, pid=2570, ppid=1
7、僵尸进程
- 子进程结束后,父进程没有对子进程进行相关处理,此时子进程即为僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(){
pid_t pid = fork();
if (pid == -1){
perror("fork error");
exit(1);
}else if (pid == 0){
// 子进程
sleep(2);
printf("I am child, pid=%u, ppid=%u, I will died...\n", getpid(), getppid());
}else{
// 父进程
int i;
for (i = 0; i < 10; i++){
printf("I am father ,pid=%u\n", getpid());
sleep(1);
}
}
return 0;
}
执行结果
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am child, pid=4430, ppid=4429, I will died...
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
I am father ,pid=4429
在子进程结束后,父进程结束前,查看进程
parallels@ubuntu:~$ ps -ax | grep a.out
4429 pts/0 S+ 0:00 ./a.out
4430 pts/0 Z+ 0:00 [a.out] <defunct>
4452 pts/1 S+ 0:00 grep --color=auto a.out
8、wait函数
- pid_t wait(int *wstatus);
- 阻塞等待子进程
- 回收子进程资源
- 获取子进程结束状态
- WIFEXITED—>WEXITSTATUS( ) // 获取子进程退出状态
- WIFSIGNALED —> WTERMSIG( ) // 获取子进程终止信号编号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t pid = fork();
if (pid == -1){
perror("fork error");
exit(1);
}else if (pid == 0){
// 子进程
sleep(2);
printf("I am child, pid=%u, ppid=%u, I will died...\n", getpid(), getppid());
}else{
// 父进程
int wstatus;// 函数传入值
int ret = wait(&wstatus);
if (ret == -1){
perror("wait error");
exit(1);
}
// WIFEXITED()判断是否正常退出
if (WIFEXITED(wstatus)){
printf("exited with %d\n", WEXITSTATUS(wstatus));
}
// WIFSIGNALED()判断是否信号退出(异常退出)
if (WIFSIGNALED(wstatus)){
printf("signaled with %d\n", WTERMSIG(wstatus));
}
// WIFSTOPPED()判断是否中止
if (WIFSTOPPED(wstatus)){
WSTOPSIG(wstatus);// 停止信号值
}
// WIFCONTINUED()判断是否继续
if (WIFCONTINUED(wstatus)){
}
int i;
for (i = 0; i < 10; i++){
printf("I am father ,pid=%u\n", getpid());
sleep(1);
}
}
return 0;
}
正常退出结果:
I am child, pid=9474, ppid=9473, I will died...
exited with 0
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
I am father ,pid=9473
异常退出结果:
signaled with 9
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
I am father ,pid=10031
9、pid_t waitpid(pid_t pid, int wstatus, int option)
- pid > 0 —> 指定进程id回收
- pid = -1 —> 回收任意子进程,等同于wait( )
- pid = 0 —> 回收本组任意子进程,默认父子进程同组
- pid < -1 —> 回收该进程的任意子进程
- option = 0 –> 阻塞回收
- option = WNOHANG —> 非阻塞回收
- 返回值
- 成功:pid
- 失败:-1
- option=WNOHANG并且子进程尚未结束,返回0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main(){
int i;
pid_t pid;
for(i = 0; i < 5; i++){
pid = fork();
if (pid == -1){
perror("fork error:");
exit(1);
}else if (pid == 0){
// 第1个子进程比父进程慢3秒,后续每个子进程都比前一个慢1秒
sleep(i+3);
break;
}
}
if (i < 5){
// 子进程
printf("I am child pid = %u\n", getpid());
}else{
// 父进程
printf("I am father pid = %u\n", getpid());
// waitpid(-1, NULL, 0)相当于wait(NULL)
// 第一个参数为pid,-1:回收任意子线程、>0:回收指定线程
// 第三个参数设为WNOHANG则为非阻塞立即执行,如果没有子线程或子线程正在运行返回0
// 循环执行waitpid(-1, NULL, 0);则会循环取子线程
pid_t pid = waitpid(-1, NULL, 0);
printf("%d\n", pid);
//wait(NULL);
}
return 0;
}