一、虚拟内存与物理映射

Linux多进程编程(上)_父进程

Linux多进程编程(上)_#include_02


二、PCB

PCB进程控制块:
进程id
文件描述符表
进程状态:初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码
信号相关信息资源。
用户id和组id

三、fork、getpid、getppid

Linux多进程编程(上)_父进程_03

//子进程创建成功返回0,父进程返回子进程pid
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	printf("before fork-1-\n");
	printf("before fork-2-\n");
	printf("before fork-3-\n");
	printf("before fork-4-\n");

	pid_t pid = fork();
	if (pid == -1) {
		perror("fork error");
		exit(1);
	}
	else if (pid == 0) {
		printf("---child is created\n");
	}
	else if (pid > 0)
	{
		printf("---parent process: my child is %d\n", pid);
	}

	printf("-----------------end of file\n");

	return 0;
}

//运行结果如下

Linux多进程编程(上)_子进程_04

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	printf("before fork-1-\n");
	printf("before fork-2-\n");
	printf("before fork-3-\n");
	printf("before fork-4-\n");

	pid_t pid = fork();
	if (pid == -1) {
		perror("fork error");
		exit(1);
	}
	else if (pid == 0) {
		printf("---child is created, pid = %d, parent-pid:%d\n", getpid(), getppid());
	}
	else if (pid > 0)
	{
		printf("---parent process: my child is %d, my pid:%d, my parent pid:%d\n", pid, getpid(), getppid());
	}

	printf("-----------------end of file\n");

	return 0;
}

//运行结果如下
//7876是bash进程

Linux多进程编程(上)_父进程_05


四、循环创建多个子进程

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	for (i = 0; i < 5; i++) {
		pid_t pid = fork();
		if (pid == -1) {
			perror("fork error");
			exit(1);
		}
		else if (pid == 0) {
			printf("---child is created, pid = %d, parent-pid:%d\n", getpid(), getppid());
		}
		else if (pid > 0)
		{
			printf("---parent process: my child is %d, my pid:%d, my parent pid:%d\n", pid, getpid(), getppid());
		}
	}

	return 0;
}

//i=2子进程如下

Linux多进程编程(上)_父进程_06

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		printf("I'm parent\n");
	}
	else {
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//出现下面这种情况是因为bash进程优先抢到CPU,子进程没抢到

Linux多进程编程(上)_#include_07

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		sleep(5);
		printf("I'm parent\n");
	}
	else {
		sleep(i);
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//这样保证了子进程按顺序执行,父进程也是放到最后一个

Linux多进程编程(上)_父进程_08

fork函数:
父进程返回子进程pid。子进程返回0.

父子进程相同:
刚fork后。data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式

父子进程不同:
进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集

父子进程共享:
读时共享、写时复制。-----------  全局变量。
1.文件描述符 2.mmap映射区。

五、父子进程gdb调试

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		sleep(5);
		printf("I'm parent\n");
	}
	else {
		sleep(i);
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//这样保证了子进程按顺序执行,父进程也是放到最后一个
//调试子进程

gdb test(上述代码文件名)

list 1

l

b 12(在for循环处创建断点)

r(运行)

n(next)

set follow-fork-mode child(切换到子进程)

n

n

Linux多进程编程(上)_linux_09

//调试父进程

gdb test

l 1

l

b 12

r

set follow-fork-mode parent

n

n

n

n

n

//多个n创建5个子进程

Linux多进程编程(上)_父进程_10


六、exec

Linux多进程编程(上)_linux_11

exec所干的事就是将exec函数里的内容覆盖子进程原来fork的内容。

七、execlp和execl

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

int main(int argc, char *argv[]){
    pid_t pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(1);
    }
    else if(pid == 0){        //子进程
        execlp("ls", "ls", "-l", "-h", NULL);    //第一个逗号之后的ls是argv[0]
        execlp("date", "date", NULL);
        execl("./a.out", "./a.out", NULL);    //执行相对路径下的a.out文件
        perror("exec error");                 //如果execl或execlp出错就会执行以下内容
        exit(1);                              //因为execl或execlp执行成功没有返回值,不返回
    }
    else if(pid > 0){
        sleep(1);
        printf("I'm parent: %d\n", getpid());
    }
    
    return 0;
}

//子进程会做出ls、date命令和执行a.out文件,下图只展示一个功能

Linux多进程编程(上)_父进程_12


八、孤儿进程和僵尸进程

//孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程被init进程领养。

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid;
    pid = fork();

    if(pid == 0){
        while(1){
            printf("I'm child, my parent pid = %d\n", getppid());
            sleep(1);
        }
    }
    else if(pid > 0){
        printf("I'm parent, my pid = %d\n", getpid());
        sleep(9);
        printf("-----------parent is going to die------------\n");
    }
    else{
        perror("fork");
        return 1;
    }

    return 0;
}

//如下图,当父进程执行完退出后子进程变为孤儿进程,他的父进程变为pid为1721 init父进程
//用两次ps ajx命令可以查看子进程前后父进程pid的变化

Linux多进程编程(上)_父进程_13

//僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸进程。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid;
    pid = fork();
    
    if(pid == 0){
        printf("child, my parent = %d, go to sleep 10s\n", getppid());
        sleep(10);
        printf("----child die----\n");
    }
    else if(pid > 0){
        while(1){
            printf("I'm parent, pid = %d, myson = %d\n", getpid(), pid);
            sleep(1);
        }
    }
    else{
        perror("fork");
        return 1;
    }

    return 0;
}

//当子进程结束后,子进程变成僵尸进程,只需将父进程kill就解决这个问题
//kill -9 3464(父进程pid)

Linux多进程编程(上)_父进程_14


九、wait回收子进程

wait函数:回收子进程退出资源
作用1:阻塞等待子进程退出
作用2:清理子进程残留在内核的pcb资源
作用3:通过传出参数,得到子进程结束状态
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid, wpid;
    int status;
    
    pid = fork();
    if(pid == 0){
        printf("child, my id = %d, go to sleep 10s\n", getppid());
        sleep(10);
        printf("---------child die---------\n");
    }
    else if(pid > 0){
        wpid = wait(&status);        //status是个传出参数,将子进程状态传出来
        if(wpid == -1){
            perror("wait error");
            exit(1);
        }
        printf("---------parent wait finish: %d\n", wpid);
    }
    else{
        perror("fork");
        return 1;
    }

    return 0;
}

Linux多进程编程(上)_#include_15


十、子进程退出和终止

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main(void){
    pid_t pid, wpid;
    int status;

    pid = fork();
    if(pid == 0){
        printf("---child, my id = %d, go to sleep 10s\n", getpid());
        sleep(10);
        printf("---child die---\n");
        return 73;
    }
    else if(pid > 0){
        //wpid = wait(NULL)        //不关心子进程结束原因
        wpid = wait(&status);    //如果子进程未终止,父进程阻塞(等待)在这个函数上
        if (wpid == -1){
            perror("wait error");
            exit(1);
        }
        if (WIFEXITED(status)){    //为真,说明子进程正常终止
            printf("child exit with %d\n", WEXITSTATUS(status));
        }
        if (WIFSIGNALED(status)){    //为真,说明子进程是被信号终止
            printf("child kill with signal %d\n", WTERMSIG(status));
        }

        printf("parent wait finish: %d\n", wpid);
    }
    else{
        perror("fork");
        return 1;
    }
    
    return 0;
}

//下图第一个和第三个案例是被信号(9,11)终止,第二个是正常超时退出(退出值73)

Linux多进程编程(上)_#include_16


十一、waitpid回收子进程

waitpid函数:
pid_t waitpid(pid_t pid, int *status, int options)

参数:
pid:指定回收的子进程pid, > 0:待回收的子进程pid, -1:任意子进程, 0:同组的子进程。
status:(传出)回收进程的状态。
options:WNOHANG指定回收方式为非阻塞。

返回值:
> 0:表示成功回收的子进程pid
0:函数调用时,参数3指定了 WNOHANG,并且,没有子进程结束。
-1:失败。errno
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid, wpid;

	for (i = 0; i < 5; i++) {
		if (fork() == 0) {
			break;
		}
	}

	if (i == 5) {
		//sleep(5);
        wait(NULL);    //一次wait/waitpit函数调用,只能回收一个子进程
        wpid = waitpid(-1, NULL, WNOHANG);    //-1回收任意子进程,相当于wait
        if(wpid == -1){
            perror("waitpid error");
            exit(1);
        }
		printf("I'm parent, wait a child finish: %d\n", wpid);
	}
	else {
		sleep(i);
		printf("I'm %dth child\n", i + 1);
	}

	return 0;
}

//由于父进程没有sleep,父进程先运行,但此时没有子进程运行,所以没有回收子进程
//WNOHANG表示非阻塞,没回收就直接返回0

Linux多进程编程(上)_#include_17

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid, wpid, tmpid;

	for (i = 0; i < 5; i++) {
        pid = fork();
		if (pid == 0) {    //是子进程,退出
			break;
		}
        if(i == 2){        //没有被break就是父进程,pid是子进程的pid
            tmpid = pid;
            printf("pid = %d\n", tmpid);
        }
	}

	if (i == 5) {    //父进程
		//sleep(5);
        //wpid = waitpid(tmpid, NULL, WNOHANG);    //指定一个进程回收,不阻塞
        wpid = waitpid(tmpid, NULL, 0);            //指定一个进程回收,阻塞
        
        if(wpid == -1){
            perror("waitpid error");
            exit(1);
        }
		printf("I'm parent, wait a child finish: %d\n", wpid);
	}
	else {        //子进程
		sleep(i);
		printf("I'm %dth child, pid = %d\n", i + 1, getpid());
	}

	return 0;
}

//有两种指定回收子进程方式,第一种是sleep+WNOHANG非阻塞,第二种是用参数0来阻塞
//第一张图是sleep的,等子进程全都退出父进程再回收
//第二张图是阻塞的,等待第三个子进程执行完立马回收

Linux多进程编程(上)_#include_18

Linux多进程编程(上)_linux_19


十二、waitpid回收多个子进程

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<pthread.h>

int main(int argc, char* argv[]) {
	int i;
	pid_t pid, wpid;

	for (i = 0; i < 5; i++) {
        pid = fork();
		if (pid == 0) {    //是子进程,退出
			break;
		}
	}

	if (i == 5) {    //父进程
        //第二个参数NULL表示不关心子进程状态,0表示阻塞
        /*while((wpid = waitpid(-1, NULL, 0))){ 
		    printf("wait child:%d\n", wpid);
        }*/
        
        //非阻塞方式回收
        while((wpid = waitpid(-1, NULL, WNOHANG)) != -1){
            if(wpid > 0){
                printf("wait child:%d\n", wpid); 
            }
            else if(wpid == 0){
                sleep(1);
                continue;
            }
        }

        printf("I'm parent, wait a child finish: %d\n", wpid);
	}
	else {        //子进程
		sleep(i);
		printf("I'm %dth child, pid = %d\n", i + 1, getpid());
	}

	return 0;
}

十三、进程间通信常见方式

英文简称:IPC,InterProcess Communication
四种进程间通信方式:
1.管道(使用最简单,得有血缘关系,如父子关系)
2.信号(开销最小)
3.共享映射区(可应用于无血缘关系的进程间)
4.本地套接字(最稳定,较复杂,一般用于网络)

Linux多进程编程(上)_linux_20


十四、管道通信特性

管道:
实现原理:内核借助环形队列机制,使用内核缓冲区实现。

特质:
1.伪文件(文件占用磁盘空间,伪文件占用内存空间)
2.管道中的数据只能一次读取。
3.数据在管道中,只能单向流动。

局限性:
1.自己写,不能自己读。
2.数据不可以反复读。
3.半双工通信。
4.血缘关系进程间可用。

十五、管道函数pipe

pipe函数:创建,并打开管道。
int pipe(int fd[2]);

参数:
fd[0]:读端。
fd[1]:写端。

返回值:
成功:0
失败:-1 errno
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int ret;
    int fd[2];
    pid_t pid;

    char *str ="hello pipe\n";
    char buf[1024];

    ret = pipe(fd);
    if (ret ==-1){
        sys_err("pipe error");
    }

    pid = fork();
    if (pid> 0){
        close(fd[0]);    // 关闭读端
        write(fd[1], str, strlen(str));
        sleep(1);        //保证父进程不先退出
        close(fd[1]);
    }
    else if (pid== 0){
        close(fd[1]);    //子进程关闭写端
        ret = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);    //输出到屏幕
        close(fd[0];
    }

    return 0;
}

Linux多进程编程(上)_子进程_21


十六、管道读写行为

读管道:
1.管道中有数据,read返回实际读到的字节数。
2.管道中无数据:
    (1)管道写端被全部关闭,read返回0(类似读到文件结尾)。
    (2)写端没有全部被关闭,read阻塞等待(不久的将来可能有数据到达)

写管道:
1.管道读端全部被关闭,进程异常终止(SIGPIPE信号导致的)
2.管道读端没有全部关闭:
    (1)管道已满,write阻塞等待。
    (2)管道未满,write将数据写入,并返回实际写入的字节数。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int ret;
    int fd[2];
    pid_t pid;

    char *str ="hello pipe\n";
    char buf[1024];

    ret = pipe(fd);
    if (ret ==-1){
        sys_err("pipe error");
    }

    pid = fork();
    if (pid> 0){
        close(fd[0]);    // 关闭读端
        sleep(3);
        write(fd[1], str, strlen(str));
        close(fd[1]);
    }
    else if (pid== 0){
        close(fd[1]);    //子进程关闭写端
        ret = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);    //输出到屏幕
        close(fd[0];
    }

    return 0;
}

//等待3s父进程写数据
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[]){
    int ret;
    int fd[2];
    pid_t pid;

    char *str ="hello pipe\n";
    char buf[1024];

    ret = pipe(fd);
    if (ret ==-1){
        sys_err("pipe error");
    }

    pid = fork();
    if (pid> 0){
        close(fd[0]);    // 关闭读端
        sleep(3);
        //write(fd[1], str, strlen(str));
        close(fd[1]);
    }
    else if (pid== 0){
        close(fd[1]);    //子进程关闭写端
        ret = read(fd[0], buf, sizeof(buf));
        printf("child read ret = %d\n", ret);
        write(STDOUT_FILENO, buf, ret);    //输出到屏幕
        close(fd[0];
    }

    return 0;
}

//父进程未写入数据,返回0

Linux多进程编程(上)_#include_22


十七、用管道实现ls | wc -l

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

int main(int argc, char* argv[]) {
    int fd[2];
    int ret;
    pid_t pid;

    ret = pipe(fd);
    if (ret == -1) {
        sys_err("pipe error");
    }

    pid = fork();
    if (pid == -1) {
        sys_err("fork error");
    }
    else if (pid > 0) {
        //父进程来读,这样就不会先于子进程执行,因为会阻塞等待子进程写入
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL); //执行成功就不回来了
        sys_err("execlp wc error");
    }
    else if (pid == 0) {
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execlp("ls", "ls", NULL);
        sys_err("execlp ls error");
    }

    return 0;
}

//这个程序可以输出当前目录文件个数(ls | wc -l)
//注意 | 是管道符

十八、兄弟进程实现ls | wc -l

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

int main(int argc, char* argv[]) {
    int fd[2];
    int ret, i;
    pid_t pid;

    ret = pipe(fd);
    if (ret == -1) {
        sys_err("pipe error");
    }

    for (int i = 0; i < 2; i++) {
        pid = fork();
        if (pid == -1) {
            sys_err("fork error");
        }

        if (pid == 0) {
            break;
        }
    }

    if (i == 2) {
        //父子进程都公用一个管道,必须保证管道单向流动,关闭父进程读写端
        close(fd[0]);
        close(fd[1]);
        wait(NULL);
        wait(NULL);
    }
    else if (i == 0) {  //兄长进程
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execlp("ls", "ls", NULL);
        sys_err("execlp ls error");
    }
    else if (i == 1) {  //小弟进程
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("wc", "wc", "-l", NULL);
        sys_err("execlp wc error");
    }

    return 0;
}

十九、管道的一写端多读端

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
#include<stdlib.h>

int main(void) {
	pid_t pid;
	int fd[2], i, n;
	char buf[1024];
	int ret = pipe(fd);

	if (ret == -1) {
		perror("pipe error");
		exit(1);
	}

	for (i = 0; i < 2; i++) {
		if ((pid = fork()) == 0) {
			break;
		}
		else if (pid == -1) {
			perror("pipe error");
			exit(1);
		}
	}

	if (i == 0) {
		close(fd[0]);
		write(fd[1], "1.hello\n", strlen("1.hello\n"));
	}
	else if (i == 1) {
		close(fd[0]);
		write(fd[1], "2.world\n", strlen("2.world\n"));
	}
	else {
		close(fd[1]);	//父进程关闭写端,留读端读取数据
		sleep(1);		//保证两个子进程都写完再回收
		n = read(fd[0], buf, 1024);	//从管道中读数据
		write(STDOUT_FILENO, buf, n);
		for (i = 0; i < 2; i++) {
			wait(NULL);	//两个儿子wait两次
		}	
	}

	return 0;
}

//这个程序父进程读,两个子进程写,由于按循环顺序i==0的子进程概率先执行,所以打印hello world
//如果想保证顺序,两个子进程用sleep来控制先后

Linux多进程编程(上)_父进程_23


二十、命名管道fifo创建

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<errno.h>
#include<pthread.h>

void sys_err(const char* str) {
	perror(str);
	exit(1);
}

int main(int argc, char *argv[]) {
	int ret = mkfifo("myfifo", 0644);	//创建管道myfifo		rw-rw-r--
	if (ret == -1) {
		sys_err("mkfifo error");
	}

	return 0;
}