目录

进程创建

fork()函数

fork返回值

fork写时拷贝

fork失败原因

fork用法

进程退出

退出场景

常见的退出方法

正常退出

异常退出

_exit()系统调用

exit()函数

_exit()和exit()的区别

return

进程等待

进程等待的必要性

进程等待的方法

wait()

waitpid()

参数int* status

进程程序替换

进程替换的原理

替换函数exec族函数

1. execv(参数格式是数组)

2.execl(参数格式是列表)

3.execvp / execlp(不带替换程序的路径)

4.execle / execve(需要自己设置环境变量)


什么是进程, 在另一篇博客中,戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103213364

进程创建

Linux 中我们可以说一个进程就是一个PCB, 即 一个task_struct, 那么创建进程也就是创建PCB, 即是创建task_struct

Linux 中说到进程创建,  就不得不提到 fork()函数. fork()在Lnux下是非常重要的一个函数 .

fork()函数

从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程

fork()在函数内部会调用clone这个系统调用接口

pid_t  fork ()

头文件: unistd.h

fork返回值

fork函数返回值 : (返回值类型为pid_t, 实际等同于int)

  • 子进程在执行fork()时返回 0
  • 父进程在执行fork()时, fork()创建子进程, 返回子进程的PID (PID是一个大于0的整数)
  • 父进程在用fork()创建子进程失败时返回 -1

因为fork运行有多种结果, 所以往往fork之后要根据fork的返回值进行分流(例如用 if 写多个分支), 来看个例子 .

testfork.c 如下

#include<stdio.h>
#include<unistd.h>
int main(){pid_t pid = fork();if(pid == -1){perror("fork error");}else if(pid == 0){printf("子进程\n");}else{printf("父进程\n");}return 0;
}

编译执行如下, 可以看到, 当父进程用fork() 创建子进程成功后, 返回了其子进程的pid, 然后继续执行, 直到执行打印语句后子进程才执行, 如下 :

但并不是父进程创建了子进程, 父进程就一定会先执行完,才执行子进程, 也可能是父进程执行到一半, 甚至刚调用fork()创建完子进程后, 就立即转而执行子进程. 这取决于CPU的调度. 比如说下面这段代码 .

#include<stdio.h>
#include<unistd.h>
int main(){pid_t pid = fork();if(pid == -1){perror("fork error");}else if(pid == 0){printf("子进程执行\n子进程pid:%d\n", getpid());}else{printf("父进程开始执行\n");sleep(5);printf("父进程执行\n父进程pid:%d\n", getpid());printf("父进程运行结束\n");}return 0;
}

可以看到, 父进程执行到一半开始执行子进程了, 就此次运行结果分析, 由于父进程中sleep()函数, 致使父进程进入睡眠状态

(sleeping)(这种睡眠是可中断的, 当sleep()执行完, 就会中断睡眠, 进入就绪状态(或者说进入运行队列), 等待分配时间片), 子进程

当被创建后, 一直处于就绪状态(一直处于运行队列中), 等待分配时间片, 当父进程睡眠时, 子进程拿到了时间片, 子进程执行 . 当子

进程执行完, 父进程拿到时间片后, 父进程继续运行 .

所以就有, fork创建子进程之前, 父进程独立运行, 创建子进程之后, 谁先运行取决于调度器的调度

进程调用fork,  内核会做出以下操作

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程 ( 此时已经创建了子进程的PCB即Linux下的task_struct )
  • 添加子进程到系统进程列表当中 ( 即添加子进程PCB)
  • fork返回,调度器开始调度

fork写时拷贝

fork 创建子进程采取分时拷贝的策略 .

即,  父进程创建子进程时,但是并没有直接给子进程开辟内存,拷贝数据,而是跟父进程映射到同一位置,但是如果父进

程或子进程有一方想要修改内存中的数据时,那么对于改变的这块内存,需要重新给子进程开辟内存,并且更新页表信息.

这样做, 提高创建子进程的性能, 并且能节省内存 . 如下图 :

图1. 父子进程共享代码与数据                                                          图2. 父子进程代码共享, 数据独立

图里涉及到了虚拟内存与分页式内存管理的内容, 简单说一下, 分页式内存管理可以将一段程序加载到不连续的物理空间上,但是

从虚拟地址空间来看依旧是连续的, 用以解决内存使用率低的问题 .

mm_struct结构体也叫内存描述符,其中记录虚拟内存各个段的起始地址, 结束地址, 通过这种方式描述了进程的虚拟地址空间,

每一个进程都会有唯一的mm_struct结构体, mm_struct记录在task_struct中.

页表: 页表中存储的是虚拟地址和物理地址的映射关系, 即页号到物理块的地址映射. 通过虚拟地址得到页号与页内地址(或者叫页内偏移),  在页表中通过页号找到物理块号.  然后,  物理地址 = 物理块号 x 页面大小 + 页内偏移  就得到了物理地址

来段代码感受一下

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){pid_t pid = fork();int data = 0;if(pid == -1){perror("fork erro");}else if(pid == 0){printf("子进程执行\n");data = 10;printf("data = %d\n", data);printf("data地址: %p\n", &data);}else{sleep(2);printf("父进程执行\n");printf("data = %d\n", data);printf("data地址: %p\n", &data);}return 0;
}

代码中, 先让父进程睡上2秒, 这时会执行子进程, 子进程修改了data的值为10, 但子进程结束后, 父进程继续执行打印出的data

还是0, 两个进程所打印的值不同,  但父子进程中data的地址都是一样的.  我们知道数据不同, 则数据一定存储在不同的物理地址

上, 打印的的变量地址依旧相同, 这是因为取地址&得到的并不是物理地址(在所有有关地址的操作中, 我们只能接触到虚拟地址),

而是虚拟地址, 虚拟地址虽然相同, 但是父子进程有着不同的mm_struct, 即有着不同的页表, 这父子进程的data相同的(虚拟)地址

通过不同的页表映射到不同的物理地址上.

运行结果如下图:

fork失败原因

  • 系统进程数达到太多, 达到上限(系统会有一个进程数的限定, 可以修改)
  • 内存不足 (fork创建子进程需要创建新的PCB, 写时拷贝可能还会分配新的数据空间)

fork用法

fork()函数当然不是为了创建子进程而创建子进程, 创建子进程目的有两种:

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段.

    例如: 父进程等待客户端请求,生成子进程来处理请求。

  • 一个进程要执行一个不同的程序 . 例如: 子进程从fork返回后,调用exec函数族(用来做进程替换的一些列函数, 下面再说)替换子进程. 比如, 我们用的Shell就是一个程序,   一般为默认为bash, 我们执行一些非Shell内建命令时, 实际就是一个Shell创建子进程, 再进行进程替换的过程

进程退出

退出场景

  1. 正常符合预期退出
  2. 正常不符合预期退出
  3. 异常退出(执行过程中异常奔溃, 还未执行完)

常见的退出方法

正常退出

  1. main函数返回 ( return )
  2. 调用 exit( int status )函数
  3. 使用 _exit( int status )系统调用接口

可以使用ench $? 来查看进程退出码

异常退出

  1. 向进程发送信号导致进程异常退出(如 Ctrl+c)
  2. 代码错误导致进程运行时奔溃异常退出

说明 : 第二种情况是代码错误导致异常终止没什么说的, 就是代码问题

第一种情况是 Unix / Linux 系统中的信号是系统响应某些状况而产生的事件,是进程间通信的一种方式。信号可以由一个进程发

送给另外进程,也可以由核发送给进程 .

信号处理程序

信号处理程序是进程在接收到信号后, 系统对信号的响应. 根据具体信号的涵义, 相应的默认信号处理程序会采取不同的信号处理方式:

  • 终止进程运行, 并且产生core dump (核心转储文件)(记录一些错误信息, 方便用户查看)
  • 终止进程运行
  • 忽略信号,进程继续执行 .
  • 暂停进程运行 .
  • 如果进程已被暂停,重新调度进程继续执行 .

前两种方式会导致进程异常退出. 实际上,大多数默认信号处理程序都会终止进程的运行。

在进程接收到信号后,如果进程已经绑定自定义的信号处理程序, 进程会在用户态执行自定义的信号处理程序. 如果没有绑定,内

核会执行默认信号程序终止进程运行,  导致进程异常退出 .

例如: kill()函数, 在Shell中执行kill 指令, 在终端用键盘发送信号, 如:Ctrl+c , 都是发送信号来终止进程

_exit()系统调用

void _exit (int status)

头文件 : unistd.h

参数 :status 定义了进程的终止状态,父进程通过wait() 来获取该值(wait() 函数, 用于进程等待, 下面说).

说明 :虽然status是int,占但是仅有低8位可以被父进程所用. 在下面小结进程等待中详细说

功能 :   直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构

exit()函数

void exit (int status)

头文件 : stdlib.h

参数status 与_exit中同理

exit() 底层封装了 _exit 系统调用, 在底层调用_exit之前,  还做了下面的工作

  • 执行用户通过 atexit 或 on_exit定义的清理函数。
  • 关闭所有打开的流,所有的缓存数据均被写入 (即刷新缓冲区)
  • 调用 _exit()

_exit()和exit()的区别

  • 最大的区别是 exit()函数在调用 _exit() 系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件 (即刷新缓冲
    区), 然后将控制权交给内核 . _exit() 则是执行后立即返回给内核,而exit()要先执行一些清除操作,
  • 调用_exit函数时,会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新缓冲区, exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新缓冲区。
  • 补充: exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux的标准函数库中,由于内存中都有一片缓冲区.  每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取.  同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。

举栗子

#include<stdio.h>
#include<stdlib.h>
int main(){printf("hello world!");exit(0);return 0;
}

#include<stdio.h>
#include<unistd.h>
int main(){printf("hello world!");_exit(0);
}

可以看到, 并没有输出hello world! ,  这就_exit()没有刷新缓冲区, 导致在缓冲区的字符串没有打印到显示器就直接刷新, 造成数据丢失 .(注意hello world! 后面不能有\n, 因为\n会刷新输出缓冲区,  影响结果)

return

return是一种更常见的退出进程方法. 执行return n等同于执行exit(n), 因为main中 return n时, 系统会将main的返回值当做 exit()

的参数


进程等待

谁要等待? 等待什么? 为什么要等待 ?

首先要知道进程终止或退出的时候会发生什么,  进程退出时会关闭所有文件描述符, 释放在用户空间分配的内存, 但是PCB却会暂时保留, 里面存着退出状态, 比如一个进程正常退出, PCB里面就放着进程的退出状态(也就是退出码). 如果是异常退出( 前面说到, 肯定是收到信号了), 那么PCB里面存放着导致该进程终止的信号.

然后, 我们知道, 子进程的退出状态讲道理是该由父进程回收( 自己生的孩子, 自己就得负责 ), 也就是说, 父进程必须得等待子进程退出接收子进程的退出状态.   (即, 父进程要等待, 等待子进程退出, 为了拿到子进程的退出状态)

进程等待的必要性

  • 要是父进程先于子进程退出, 那么子进程就成了孤儿进程, 1号进程(孤儿院院长)等待领养, 1号进程要领养, 还得等子进程退出.
  • 要是子进程先于父进程退出, 退出时为了保存退出状态 , 子进程一些资源并没有释放, 如果此时父进程没有关注到子进程的退出(也就父进程是没有拿到子进程的退出状态), 子进程伤心欲绝, 就会变成 "僵尸进程",变成一个连kill -9 都杀不死的进程, 因为谁也杀不死一个已经死去的进程 (只有重新来过, 也就是重启机器才能解诀僵尸进程),  "僵尸进程" 会造成资源泄露, 占用进程数的问题 .

简单来说, 就是父进程需要知道, 派给子进程的任务完成的如何, 结果对还是不对,或者是否正常退出. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息.

说了一大堆, 如何等待 ?

进程等待的方法

  • wait()

头文件 : sys/wait.h

pid_t  wait (int* status)

返回值:  成功返回被等待进程PID,  失败则返回 -1. (成功指的是被子进程正常退出, 失败指的是被子程序异常终止)
参数:  int* status, 是输出型参数,获取子进程退出状态, 将其退出状态存到status所指向的内存空间, 不关心被子进程退出状态则可以设置status为NULL

wait() 是阻塞的, 在wait 返回之前, 阻塞等待子进程退出, 再返回 .

阻塞与非阻塞

  • 阻塞:   为了完成一个功能, 若发起调用, 当前不具备完成条件, 则一直等待达到完成条件, 调用才会结束
  • 非阻塞: 与阻塞相反, 若发起调用, 当前不具备完成条件, 立即返回
  • 最大区别是, 调用是否立即返回
  • waitpid()

pid_ t  waitpid (pid_t pid, int *status, int options

参数:
pid :

  • pid = -1 :等待任一个子进程
  • pid >  0 : 等待其进程ID与pid相等的子进程。

int* status:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

后面详细看status和WIFEXITED, WEXITSTATUS这两个宏

options: options = 1时, waitpid是非阻塞的, options = 其他值时 waitpid() 是阻塞的

  • WNOHANG: 值为1 的宏

返回值

有三种返回值
=-1 : waitpid()出错
= 0 : 没有等到子进程退出
>0 :  等到子进程退出, 返回子进程PID

详细来看:

  • 给 options 传WNOHANG 或 1, 此时waitpid()是非阻塞的,
    1. 如pid此时指定,即pid>0, 已如指定pid的进程在调用waitpid()时退出,  waitpid()返回子进程PID, 如发现指定pid的进程
        还没有退出, 不予等待, 直接返回 0.
    2. 若pid不指定,即pid = -1,若在waitpid()调用之前已有子进程退出(任意子进程都可以), 则返回该子进程的PID,
        若在调用waitpid()之前没有任何子进程退出, 则返回 0
  • 若给options传入非1的值, 如等不到子进程退出, 会一直阻塞
  • 如果调用waitpid()出错,  则返回-1, 这时errno会被设置成相应的值以指示错误所在 .

PS: 当waitpid是waitpid(-1, &status, 非1) 等效 wait (&status)
       不关心子进程退出码时 waitpid(-1, NULL, 非1) 等效 wait (NULL)

参数int* status

前面说到. wait/waitpid都可以获取到退出的子进程的退出状态, 那么获取到的状态在哪儿保存呢 ?

这就需要我们事先申请一块内存空间来保存, 即 int status; 用这个int型变量来保存退出状态, 那么wait/waitpid又如何知道, 要将退

出状态给我们事先申请好的这个变量呢? 传入这个变量的指针, 由操作系统填充.

前面说到, 进程在退出时, 会有正常退出和异常退出(终止), 那么一个int 型变量如何保存这两种不同些情况的的退出状态呢 ?

如下图:

可以看到, 在正常退出时, status这个int型变量, 只有低16位用到了

即, 在正常退出时, 低16位中的高8位存的是子进程得退出码, 后8位全为0,

在异常退出时, 底7位存的是进程的异常退出信号值, 第8位是core dump 标志, 即核心转储文件标志, 为了用户方便查看异常终止

的信息, 此时低16位中的高8位未用

所以, 在wait/waitpid结束后,  要先取出dtatus的低7位看子进程是不是异常终止, 如果不是, 再来看退出码, 如下:

if (!(status & 0x7f)) {printf("子进程退出码为\n", (status >> 8) & 0xff);
}
else {printf("子进程异常终止\n");
}

这样写很麻烦, 而且还容易出错, 所以系统就将 !(status & 0x7f) 和 (status >> 8) & 0xff 封装成了两个宏

即WIFEXITED 和 WEXITSTATUS , 既上面的代码可以写成如下:

if (WIFEXITED(status)) {printf("子进程退出码为\n", WEXITSTATUS(status));
}
else {printf("子进程异常终止\n");
}

举栗子

wait.c

用wait() 模拟父母接孩子的场景, 父母就像wait() 阻塞一样, 不管多久, 都会等着我们, 直到接到我们.

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(){printf("一位爸爸来学校接孩子放学了~\n");pid_t pid = fork();if(pid < 0){perror("fork erro");exit(-1);}else if(pid == 0){sleep(30);exit(30);}int status = -1;wait(&status);if (!(status & 0x7f)) {printf("%d分钟后接上了孩子\n", (status >> 8) & 0xff);                }else {printf("孩子在幼儿园发烧送医院了!\n");            }return 0;
}

在30秒后, 打印出了30分钟后接上了孩子

如果我们在另一个窗口下用kill -9 杀死子进程, 此时子进程就是异常终止了, 运行如下:

waitpid.c

用waitpid模拟出租车司机在车站等待拉乘客的场景, 出租车司机会一直去询问旅客乘不乘车, 当长的时间拉不到人, 就离开去别的地方拉乘客了

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main(){printf("一位出租车司机在车站拉乘客~\n");pid_t pid = fork();if(pid < 0){perror("fork erro");exit(-1);                  }else if(pid == 0){sleep(5);exit(5);                           }int status = -1;while(waitpid(-1, &status, WNOHANG) == 0){printf("没有人打车, 继续等...\n");sleep(1);                           }if(WIFEXITED(status)){printf("这次等了%d分钟才拉到乘客(子进程退出码)\n", WEXITSTATUS(status));printf("有人打车了, 出发~\n");}else{printf("没有等到乘客(子进程异常退出)\n");}return 0;}


进程程序替换

为什么要进行进程替换 ?

前面说到,  fork()创建子进程, fork创建的子进程要么和父进程执行一样的代码, 要么执行不同的代码分支(通过fork的返回值控制),

但这样还是不够灵活. 假如要有很多的功能已经用别的程序实现好了, 那么就不需要在父进程中控制父子进程执行不同的代码分

支, 让子进程在自己的分支中完成这些功能, 而是可以直接拿一个已有的程序替换掉子进程. 使子进程的代码完全变成所替换程序

的代码. 这样就方便了很多, 而且在子进程需要完成较为复杂的功能或是多项功能时, 分支就显得力不从心了.

所以进程往往要调用exec族中的某一个函数来进行程序替换, 让一个进程来执行另一个程序. 当进程调用一种exec函数时,该进程

的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec族中的函数并不会创建新进程,所以调用exec

前后被替换进程的iD并未改变。

进程替换的原理

如上图, 进程替换时, 替换的是PCB映射在内存中的代码和数据. 这样, 该进程PID虽然没有变, 但已经物是人非, 已经不是原来的那

个进程了.

如进行进程程序替换? 前面也说到了, exec族函数

替换函数exec族函数

exec族函数共有六个, 功能都是进程程序替换, 但多个不同的函数接口使得使用更加灵活.

其中exceve()是系统调用接口, 其余5个底层都封装了execve().

函数原型如下 :

int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ... /*,(char *)0, char *const envp[]*/);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execvp(const char *file, char *const argv[]);

头文件: unistd.h

返回值 :

  • 这六个函数返回值相同.
  • 当函数调用失败, 返回 -1.
  • 当调用成功, 即加载新的程序, 替换后的进程启动开始执行,exec族函数不再返回 .
  • 特殊的地方是, 函数调用成功, 不返回

参数:

虽然有六个, 不好记, 但好在有规律. 可以发现, 函数名都是在exce的基础上, 加上l, v, e,  p形成新的函数名, 加哪个字母都有各自的

含义 , 如下所示:

  • l / v 必须有一个, 也只能有一个, 带l参数格式是列表,  带 v 参数格式是 数组
  • p   有p, 会自动搜索环境变量PATH, 则可以不带路径, 只要文件名(但文件必须放在PATH环境变量指定路径).  没有p, 则必须指定文件路径
  • e    有e, 则不使用当前环境变量, 需要自己设置环境变量,  没带e, 则使用当前环境变量, 无需设置环境变量

如下表:

函数名 参数格式 函数是否自带路径(通过PATH) 是否使用当前环境变量
execl 列表 不带, 需要制定文件路径 使用
execlp 列表 带, 但文件必须的放在指定目录 使用
execle 列表 不带, 需要制定文件路径 不使用, 需要自己设置环境变量
execv 数组 不带, 需要制定文件路径 使用
execvp 数组 带, 但文件必须的放在指定目录 使用
execve 数组 不带, 需要制定文件路径 不使用, 需要自己设置环境变量

来看一下这六个函数具体如何使用.

1. execv(参数格式是数组)

#include<stdio.h>
#include<unistd.h>
int main() {char* arg[] = {"ls","-a","-l","/",NULL };//参数数组;//参数数组execv("/bin/ls", arg);printf("hello world!\n");return 0;
}

可以看到进程被ls程序替换后, 只会执行ls的代码, 并不会再输出 hello world!

如下, 还可以通过main函数的参数传入, 我们用Shell调用ls等命令就是这个原理.

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char* argv[]){char* arg[] = {"ls","-a","-l",NULL };//参数数组pid_t pid = fork();if(pid == -1){perror("fork");}else if(pid == 0){printf("替换子进程\n");execv("/bin/ls", argv);}else{sleep(1);printf("替换父进程\n");execv("/bin/ls", arg);}printf("hello world!\n");return 0;
}

2.execl(参数格式是列表)

#include<stdio.h>
#include<unistd.h>
int main(){execl("/bin/ls", "ls", "/", NULL);printf("hello world!\n");return 0;
}

3.execvp / execlp(不带替换程序的路径)

execvp

#include<stdio.h>
#include<unistd.h>
int main(int argc, char* argv[]){execvp("ls", arg);printf("hello world!\n");return 0;
}

 execlp

#include<stdio.h>
#include<unistd.h>
int main(){char* arg[] = {"ls","-a","-l",NULL };//参数数组execlp("ls", "-a", "-l", NULL);printf("hello world!\n");return 0;
}

4.execle / execve(需要自己设置环境变量)

execve

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char* argv[]){char* envp[] = {"PATH=/home/test", NULL};execve("/bin/env", argv, envp);return 0;
}

execle

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){char* envp[] = {"PATH=/home/test", NULL};execle("/bin/env", "", NULL, envp);return 0;
}

学习完进程的创建和替换后, 就可以利用这些知识, 写一个自己的Shell了

具体代码, 持续更新

Linux 进程控制(创建/退出/等待/替换)相关推荐

  1. 《Linux进程概念,进程创建退出等待替换,环境变量等基础操作 ---总结》

    前言 Linux系统的进程概念,进程状态,以及操作创建进程,还有环境变量及相关指令,程序地址空间等一些知识的梳理,将自己的理解总结如下,希望和各位一起进步,一起学习. 以下是本篇文章正文内容. 文章目 ...

  2. 北风网 linux,linux进程控制笔记北风网分享.doc

    linux进程控制笔记北风网分享 linux 进程控制笔记 进程创建 普通函数调用完成后,最多返回 return 一次,但fork/vfork会返回二次,一次返回给父进程,一次返回给子进程 父进程的返 ...

  3. linux系统进程控制实验报告,Linux进程控制实验报告.doc

    里奴性进程控制实验报告 实验名称: Linux进程控制 实验要求:一.编写一个Linux系统C程序,由父亲创建2个子进程,再由子进程各自从控制台接收一串字符串,保存在各自的全局字符串变量中,然后正常结 ...

  4. 进程控制-创建、退出、等待、替换

    目录 进程创建 1.子进程继承 2.写时拷贝 进程退出 echo $? 退出码 进程异常退出的情况模拟: 退出进程的方式 退出码的意义: 进程退出,在系统中发生了什么? 进程等待 为什么要有进程等待呢 ...

  5. Linux——进程控制:创建、终止、等待、替换

    进程创建 fork #include <unistd.h> pid_t fork(void); 操作系统做了什么? 调用fork之后,内核的工作: 分配新的内存块和内核数据结构给子进程 将 ...

  6. linux——进程(创建、终止、等待、替换)

    进程的基本操作 概念 程序运行的一个实例,其占有一定的空间. 查询某一进程当前情况 ps aux | grep 进程名 终止进程 kill -9 pid: //pid指需要终止的进程pid 创建 pi ...

  7. 【Linux】进程控制(创建、终止、等待)

    环境:centos7.6,腾讯云服务器 Linux文章都放在了专栏:[Linux]欢迎支持订阅 相关文章推荐: [Linux]冯.诺依曼体系结构与操作系统 [Linux]进程理解与学习Ⅰ-进程概念 [ ...

  8. 嵌入式Linux系统编程学习之十一Linux进程的创建与控制

    文章目录 一.fork函数 二.进程的终止 三.wait 和 waitpid 函数 四.exec 函数族 五.system 函数 六.popen 函数 总结 一.fork函数 fork 函数原型: # ...

  9. Linux_进程控制(创建进程,等待进程,进程终止)

    文章目录 1.创建进程 1.1 fork()函数初识 1.2 fork()创建进程代码示例 2.等待进程 2.1 进程等待概念 2.2进程等待必要性 2.3 进程等待方法 2.3.1 wait 2.3 ...

最新文章

  1. WebRTC内置debug工具,详细参数解读
  2. C#中equal与==的区别
  3. DeepLearning:手动编辑python实现卷积操作
  4. 【❌❌N种姿势满足你遍历C++ vector⭕⭕】C++ vector 遍历
  5. python归一化处理_详解python实现数据归一化处理的方式:(0,1)标准化
  6. java开发安全问题_Java 安全开发需要了解什么?
  7. Python:获取文件夹内 文件夹 和 文件数量
  8. java中的linked_为Java实现LinkedArray
  9. 连接服务器打印机文件名无效,excel服务器-办公室常识——共享服务器文件报错,共享打印机无法打印的解决办法(解决word、excel不能打印))...
  10. R pdf大小_PDF如何压缩?这两招教您快速压缩PDF!
  11. php新手自学手册,PHP编程新手自学手册
  12. Nature:肠道内微生物合作方式的探究
  13. 37岁的老大叔零基础学python,来点鼓励吧
  14. dns配置异常怎么修复_dns配置异常不能上网如何修复
  15. 一天 第二章 纤腰舞困因谁,知谁系斑骓?
  16. vscode插件(个人正在用的)
  17. MVC框架的学习总结
  18. 基于FaceX-Zoo实现的Face Mask Adding人脸戴口罩
  19. 什么是云计算的基本原理?具体的核心技术有哪些?
  20. 植物大战僵尸java圣诞版,植物大战僵尸2国际版圣诞节版

热门文章

  1. 手机文档被删除怎么恢复,如何恢复
  2. 【爬坑总结】产品提交苹果审核之:苹果IAP内购规则
  3. 豪掷300亿扶持内容创业者 腾讯公布新科技与大内容开放战略
  4. 传奇架设教程入门讲解
  5. 两个视频左右拼接成一个视频,且两个视频音频都保留
  6. 计算机网络_第一、二章知识总结
  7. 暗影精灵4 安装双系统方法:win10 + ubuntu16.04 LTS
  8. Webpack优化总会让你不得不爱
  9. 安卓UI设计、验收规范总结
  10. 使用PS给一个模块加阴影效果