系统相关
首页 > 系统相关> > 2 linux多进程开发-进程基础

2 linux多进程开发-进程基础

作者:互联网

进程状态 进程创建 父子进程 进程退出 孤儿进程、僵尸进程 wait | waitpid

   

1. 进程概述

   

进程是正在运行的程序的实例。

 

2. 进程状态转换

ps aux / ajx

a: 显示终端上所有进程,包括其他用户的进程

u:显示进程的详细信息

x:显示没有控制终端的进程

j:列出与作业控制相关的信息

   

   

 

   

   

 

   

 

   

 

3. 进程创建

fork.c文件

fork函数的作用:用于创建子进程。

/*

#include <sys/types.h>

#include <unistd.h>

   

pid_t fork(void);

函数的作用:用于创建子进程。

返回值:

fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。

1在父进程中返回创建的子进程的ID,

2在子进程中返回0

如何区分父进程和子进程:通过fork的返回值。

在父进程中返回-1,表示创建子进程失败,并且设置errno

   

父子进程之间的关系:

区别:

1.fork()函数的返回值不同

父进程中: >0 返回的子进程的ID

子进程中: =0

2.pcb中的一些数据

当前的进程的id pid

当前的进程的父进程的id ppid

信号集

   

共同点:

某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作

- 用户区的数据

- 文件描述符表

 

父子进程对变量是不是共享的?

- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。

- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。

 

*/

   

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

   

int main() {

   

int num = 10;

   

// 创建子进程

pid_t pid = fork();

   

// 判断是父进程还是子进程

if(pid > 0) {

// printf("pid : %d\n", pid);

// 如果大于0,返回的是创建的子进程的进程号,当前是父进程

printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());

   

printf("parent num : %d\n", num);

num += 10;

printf("parent num += 10 : %d\n", num);

   

   

} else if(pid == 0) {

// 当前是子进程

printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());

 

printf("child num : %d\n", num);

num += 100;

printf("child num += 100 : %d\n", num);

}

   

// for循环

for(int i = 0; i < 3; i++) {

printf("i : %d , pid : %d\n", i , getpid());

sleep(1);

}

   

return 0;

}

   

/*

实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。

写时拷贝是一种可以推迟甚至避免拷贝数据的技术。

内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。

只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。

也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。

注意:fork之后父子进程共享文件,

fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

*/

   

   

 

4. 父子进程虚拟地址空间情况

 

   

   

   

 

5. GDB多进程调试

   

set follow fork mode [parent(默认) | child]

   

set detach onset detach on fork [on | off]

默认为on ,表示调试当前进程的时候,其它的进程继续运行,

如果为 off ,调试当前进程的时候,其它进程被 GDB 挂起。

   

查看调试的进程:

info inferiors

切换当前调试的进程:

inferior id

使进程脱离GDB 调试:

detach inferiors id

   

hello.c文件

#include <stdio.h>

#include <unistd.h>

   

int main() {

printf("begin\n");

   

if(fork() > 0) {

printf("我是父进程:pid = %d, ppid = %d\n", getpid(), getppid());

   

int i;

for(i = 0; i < 10; i++) {

printf("i = %d\n", i);

sleep(1);

}

} else {

printf("我是子进程:pid = %d, ppid = %d\n", getpid(), getppid());

 

int j;

for(j = 0; j < 10; j++) {

printf("j = %d\n", j);

sleep(1);

}

}

return 0;

}

   

   

 

6. exec函数族

   

exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据

段和堆栈等都已经被新的内容取代,只留下进程ID 等一些表面上的信息仍保持原样,

颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵

魂。

只有调用失败了,它们才会返回-1,从原程序的调用点接着往下执行。

   

◼ int execl(const char *path, const char *arg, .../* (char *) NULL */);

◼ int execlp(const char *file, const char *arg, ... /* (char *) NULL */);

◼ int execle(const char *path, const char *arg, .../*, (char *) NULL, char *const envp[] */);

◼ int execv(const char *path, char *const argv[]);

◼ int execvp(const char *file, char *const argv[]);

◼ int execvpe(const char *file, char *const argv[], char *const envp[]);

◼ int execve(const char *filename, char *const argv[], char *const envp[]);

   

l(list) 参数地址列表,以空指针结尾

v(vector) 存有各参数地址的指针数组的地址

p(path) 按PATH 环境变量指定的目录搜索可执行文件

e(environment) 存有环境变量字符串地址的指针数组的地址

   

execl.c文件

/*

#include <unistd.h>

int execl(const char *path, const char *arg, ...);

- 参数:

- path:需要指定的执行的文件的路径或者名称

a.out /home/nowcoder/a.out 推荐使用绝对路径

./a.out hello world

   

- arg:是执行可执行文件所需要的参数列表

第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称

从第二个参数开始往后,就是程序执行所需要的的参数列表。

参数最后需要以NULL结束(哨兵)

   

- 返回值:

只有当调用失败,才会有返回值,返回-1,并且设置errno

如果调用成功,没有返回值。

   

*/

#include <unistd.h>

#include <stdio.h>

   

int main() {

   

   

// 创建一个子进程,在子进程中执行exec函数族中的函数

pid_t pid = fork();

   

if(pid > 0) {

// 父进程

printf("i am parent process, pid : %d\n",getpid());

sleep(1);

}else if(pid == 0) {

// 子进程

// execl("hello","hello",NULL);

   

execl("/bin/ps", "ps", "aux", NULL);

perror("execl");

printf("i am child process, pid : %d\n", getpid());

   

}

   

for(int i = 0; i < 3; i++) {

printf("i = %d, pid = %d\n", i, getpid());

}

   

return 0;

}

   

execlp.c文件

会到环境变量中查找指定的可执行文件,execl就不会查找,要输入可执行文件的完整路径

/*

#include <unistd.h>

int execlp(const char *file, const char *arg, ... );

- 会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。

- 参数:

- <a href="file://需要执行的可执行文件的文件名">file:需要执行的可执行文件的文件名</a>

a.out

ps

   

- arg:是执行可执行文件所需要的参数列表

第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称

从第二个参数开始往后,就是程序执行所需要的的参数列表。

参数最后需要以NULL结束(哨兵)

   

- 返回值:

只有当调用失败,才会有返回值,返回-1,并且设置errno

如果调用成功,没有返回值。

   

   

int execv(const char *path, char *const argv[]);

argv是需要的参数的一个字符串数组

char * argv[] = {"ps", "aux", NULL};

execv("/bin/ps", argv);

   

int execve(const char *filename, char *const argv[], char *const envp[]);

char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"};

   

   

*/

#include <unistd.h>

#include <stdio.h>

   

int main() {

   

   

// 创建一个子进程,在子进程中执行exec函数族中的函数

pid_t pid = fork();

   

if(pid > 0) {

// 父进程

printf("i am parent process, pid : %d\n",getpid());

sleep(1);

}else if(pid == 0) {

// 子进程

execlp("ps", "ps", "aux", NULL);

   

printf("i am child process, pid : %d\n", getpid());

   

}

   

for(int i = 0; i < 3; i++) {

printf("i = %d, pid = %d\n", i, getpid());

}

   

return 0;

}

   

   

 

7. 进程控制 (进程退出、孤儿进程与僵尸进程、进程回收)

   

   

exit.c文件

 

/*

#include <stdlib.h>

void exit(int status);

   

#include <unistd.h>

void _exit(int status);

   

status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。

*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

   

int main() {

   

printf("hello\n");

printf("world");

   

// exit(0);

_exit(0);

 

return 0;

}

区别:

如果使用exit(0),上面代码 第一行显示hello,第二行显示word

如果使用_exit(0),上面代码 只会显示一行hello

因为第一行有"\n",强制刷新了缓存区,使得正常输出;而第二行的内容还在缓存区,直接被系统库函数_exit(0)结束了

(看上面的图右边 两者区别:exit()还会调用退出处理函数,刷新IO缓存、关闭文件描述符)

   

   

orphan.c文件

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

   

int main() {

// 创建子进程

pid_t pid = fork();

   

// 判断是父进程还是子进程

if(pid > 0) {

printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());

} else if(pid == 0) {

sleep(1);

// 当前是子进程

printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());

}

   

// for循环

for(int i = 0; i < 3; i++) {

printf("i : %d , pid : %d\n", i , getpid());

}

return 0;

}

   

   

zombie.c文件

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

   

int main() {

// 创建子进程

pid_t pid = fork();

   

// 判断是父进程还是子进程

if(pid > 0) {

while(1) {

printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());

sleep(1);

}

} else if(pid == 0) {

// 当前是子进程

printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());

}

   

// for循环

for(int i = 0; i < 3; i++) {

printf("i : %d , pid : %d\n", i , getpid());

}

return 0;

}

   

   

wait.c文件

功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源。

   

调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)

如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.

/*

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *wstatus);

功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源。

参数:int *wstatus

进程退出时的状态信息,传入的是一个int类型的地址,传出参数。

返回值:

- 成功:返回被回收的子进程的id

- 失败:-1 (所有的子进程都结束,调用函数失败)

   

调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)

如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.

   

*/

#include <sys/types.h>

#include <sys/wait.h>

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

   

   

int main() {

   

// 有一个父进程,创建5个子进程(兄弟)

pid_t pid;

   

// 创建5个子进程

for(int i = 0; i < 5; i++) {

pid = fork();

if(pid == 0) {

break;

}

}

   

if(pid > 0) {

// 父进程

while(1) {

printf("parent, pid = %d\n", getpid());

   

// int ret = wait(NULL);

int st;

int ret = wait(&st);

   

if(ret == -1) {

break;

}

   

if(WIFEXITED(st)) {

// 是不是正常退出

printf("退出的状态码:%d\n", WEXITSTATUS(st));

}

if(WIFSIGNALED(st)) {

// 是不是异常终止

printf("被哪个信号干掉了:%d\n", WTERMSIG(st));

}

   

printf("child die, pid = %d\n", ret);

   

sleep(1);

}

   

} else if (pid == 0){

// 子进程

while(1) {

printf("child, pid = %d\n",getpid());

sleep(1);

}

   

exit(0);

}

   

return 0; // exit(0)

}

   

   

WIFEXITED(status) 非 0 ,进程正常退出

   

WEXITSTATUS(status) 如果上宏为真,获取进程退出的状态( exit 的参数)

   

WIFSIGNALED(status) 非 0 ,进程异常终止

   

WTERMSIG(status) 如果上宏为真,获取使进程终止的信号编号

   

WIFSTOPPED(status) 非 0 ,进程处于暂停状态

   

WSTOPSIG(status) 如果上宏为真,获取使进程暂停的信号的编号

   

WIFCONTINUED(status) 非 0 ,进程暂停后已经继续运行

   

   

waitpid.c文件

功能:回收指定进程号的子进程,可以设置是否阻塞。

/*

#include <sys/types.h>

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *wstatus, int options);

功能:回收指定进程号的子进程,可以设置是否阻塞。

参数:

- pid:

pid > 0 : 某个子进程的pid

pid = 0 : 回收当前进程组的所有子进程

pid = -1 : 回收所有的子进程,相当于 wait() (最常用)

pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程

- options:设置阻塞或者非阻塞

0 : 阻塞

WNOHANG : 非阻塞

- 返回值:

> 0 : 返回子进程的id

= 0 : options=WNOHANG, 表示还有子进程活着

= -1 :错误,或者没有子进程了

*/

#include <sys/types.h>

#include <sys/wait.h>

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

   

int main() {

   

// 有一个父进程,创建5个子进程(兄弟)

pid_t pid;

   

// 创建5个子进程

for(int i = 0; i < 5; i++) {

pid = fork();

if(pid == 0) {

break;

}

}

   

if(pid > 0) {

// 父进程

while(1) {

printf("parent, pid = %d\n", getpid());

sleep(1);

   

int st;

// int ret = waitpid(-1, &st, 0);

int ret = waitpid(-1, &st, WNOHANG);

   

if(ret == -1) {

break;

} else if(ret == 0) {

// 说明还有子进程存在

continue;

} else if(ret > 0) {

if(WIFEXITED(st)) {

// 是不是正常退出

printf("退出的状态码:%d\n", WEXITSTATUS(st));

}

if(WIFSIGNALED(st)) {

// 是不是异常终止

printf("被哪个信号干掉了:%d\n", WTERMSIG(st));

}

printf("child die, pid = %d\n", ret);

}

}

} else if (pid == 0){

// 子进程

while(1) {

printf("child, pid = %d\n",getpid());

sleep(1);

}

exit(0);

}

   

return 0;

}

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

   

 

标签:int,printf,pid,char,开发,linux,进程,include
来源: https://www.cnblogs.com/libxing/p/16642630.html