当前位置: 首页 > news >正文

聚合猫网站建设网站备案及管理的授权书

聚合猫网站建设,网站备案及管理的授权书,做头条信息流要网站吗,电商网站设计目的1. 进程相关概念 面试中关于进程#xff0c;应该会问的的几个问题#xff1a; 1.1. 什么是程序#xff0c;什么是进程#xff0c;有什么区别#xff1f; 程序是静态的概念#xff0c;比如#xff1a; 磁盘中生成的a.out文件#xff0c;就叫做#xff1a;程序进程是…1. 进程相关概念 面试中关于进程应该会问的的几个问题 1.1. 什么是程序什么是进程有什么区别 程序是静态的概念比如 磁盘中生成的a.out文件就叫做程序进程是程序的一次运行活动通俗点意思是程序跑起来了系统中就多了一个进程程序是静态的概念进程是动态的概念 简单的来说没有跑起来的文件叫做程序是静态概念例如调用gcc test.c -o test 生成的这个test文件它就是一个程序当这个程序跑起来就是进程动态概念。进程是程序的一次执行也就是说每执行一次程序它就会生成一个新的进程。 1.2. 如何查看系统中有哪些进程 a.使用ps指令查看 ps -aux //查看当前运行的进程 ps指令显示的进程不够完整ps -aux显示完整但是篇幅太长不方便我们查看 由于当前的程序时非常多的所以我们要使用grep指令进行筛选例如查看当前进程中带有 “init” 字样的进程 ps -aux|grep init文件名 b.使用top指令查看类似windows任务管理器 1.3. 什么是进程的标识符 进程标识符process identifier又略称为进程ID或者PID是大多数操作系统的内核用于唯一标识进程的一个数值。这一数值可以作为许多函数调用的参数以使调整进程优先级、杀死进程之类的进程控制行为成为可能。 在各 PID 中较为特别的是 0 号 PID 和 1 号 PID。PID 为 0 者为交换进程swapper属于内核进程负责分页任务PID 为 1 者则常为 init 进程主要负责启动与关闭系统。值得一提的是1 号 PID 本来并非是特意为 init 进程预留的而 init 进程之所以拥有这一 PID则是因为 init 即是内核创建的第一个进程。不过现今的许多 UNIX/类 UNIX 系统内核也有以进程形式存在的其他组成部分而在这种情况下1 号 PID 则仍为 init 进程保有以与之前系统保持一致。 每个进程都有一个非负整数表示的唯一ID,叫做pid类似身份证 Pid0:称为交换进程swapper作用—进程调度类似windows任务管理器在某一个时刻哪个程序CPU占用多少内存占用多少去管理Pid1init进程作用—系统初始化程序初始化界面比如wx打开是一个地球QQ打开是一个企鹅 1.3.1. 获取进程标识符函数gitpid函数原型和头文件 /*Linux下 man 2 gitpid查看手册 */ #include sys/types.h #include unistd.hpid_t getpid(void); getpid函数作用获取自身的进程标识符 pid_t getppid(void); getppid函数作用 获取父进程的进程标识符 pid_t 获取到的进程标识符 1.3.2. 获取自身的进程标识符案例 代码设计 /*Linux下 man 2 gitpid查看手册 */ #include stdio.h #include sys/types.h #include unistd.hint main() {/* pid_t pid;声明一个变量 pid它的类型是 pid_t这是用于表示进程ID的类型。pid getpid();调用 getpid() 函数来获取当前进程的进程ID并将其存储在 pid 变量中。 */pid_t pid;pid getpid();printf(my pid is %d\n,pid);while(1);return 0; } 实现结果 1.4. 什么是父进程什么是子进程 进程A创建了进程B 那么A叫做父进程B叫做子进程父子进程是相对的概念理解为人类中的父子关系。 1.5. C程序的存储空间是如何分配的 参考《UNIX环境高级编程》的第七章进程环境 7.6节C程序的存储空间布局 正文段又叫做代码段这是有CPU执行的机器指令部分。通常正文段是可以共享的并且是只读的 初始化数据段通常将此段作为数据段它包含了程序中需要明确的赋初值的变量比如函数外的声明int cnt 10; 非初始化数据段通常此数据段称为bss段(block start symbol)在程序开始执行之前内核将此段中的数据初始化为0或空指针。比如函数外声明int arr[100]; 堆通常在堆中进行动态存储分配由于历史上的惯例堆位于非初始化数据段和栈之间 栈自动变量以及每次函数调用所需保存的信息都存放在此段中。调用函数其返回地址也保存在栈中。递归函数每调用一次自身就是用一个新的栈帧这样一个函数调用中的变量集就不会影响另一个函数调用函数的变量 2. 创建进程函数fork的使用 2.1. 进程创建函数fork函数原型和头文件 /*Linux下 man 2 fork查看手册 */#include unistd.hpid_t fork(void);无参数 pid_t 是一个宏定义其实质是int 被定义在sys/types.h中fork函数调用成功返回两次 返回值为0 代表当前进程是子进程 返回值非负数 代表当前进程为父进程调用失败返回-1 代码实现 /*Linux下 man 2 fork查看手册fork */ #include stdio.h #include sys/types.h #include unistd.hint main() {pid_t pid;pid getpid();fork();printf(my pid is %d,fork pid is %d\n,pid,getpid());return 0; } 先从上到下正常运行一遍程序然后fork 创建一个子进程在运行一编 这段代码会在 fork() 之后创建一个子进程。由于 fork() 后的代码会在父进程和子进程中各执行一次 因此 printf 语句会被执行两次一次是在父进程中一次是在子进程中。 打印输出中my pid is 的值在父子进程中是相同的而 fork pid is 的值在父子进程中是不同的。 在 fork() 之后程序会创建一个子进程。fork() 函数会返回两次一次在父进程中返回子进程的 PID一次在子进程中返回 0。具体到你的代码在调用 fork() 之前你已经使用 getpid() 获取并存储了当前进程的 PID 到 pid 变量中。这个 pid 变量在 fork() 之后会在父进程和子进程中分别存在并保持相同的值因为子进程是父进程的一个副本。 所以在 printf(my pid is %d,fork pid is %d\n,pid,getpid()); 中pid 变量始终保持父进程的 PID而 getpid() 则返回当前进程的 PID。对于父进程pid 和 getpid() 输出的是同一个值对于子进程pid 仍然是父进程的 PID而 getpid() 则返回子进程的 PID。 综上所述在 fork 之后 pid 变量不变仍然是父进程的 PID因为 pid 是在 fork 之前被设置的并被 fork 复制给了子进程。 2.2 函数说明 一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程child process。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。 子进程是父进程的副本它将获得父进程数据空间、堆、栈等资源的副本。注意子进程持有的是上述存储空间的“副本”这意味着父子进程间不共享这些存储空间。 UNIX将复制父进程的地址空间内容给子进程因此子进程有了独立的地址空间。在不同的UNIX系统下我们无法确定fork之后是子进程先运行还是父进程先运行这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。 由于在复制时复制了父进程的堆栈段所以两个进程都停留在fork函数中等待返回。因此fork函数会返回两次一次是在父进程中返回另一次是在子进程中返回这两次的返回值是不一样的。 在fork函数执行完毕后如果创建新进程成功则出现两个进程一个是子进程一个是父进程。在子进程中fork函数返回0在父进程中fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。 调用fork之后数据、堆、栈有两份代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回箭头表示各自的执行处。当父子进程有一个想要修改数据或者堆栈时两个进程真正分裂。 引用一位网友的话来解释Pid的值为什么在父子进程中不同。“其实就相当于链表进程形成了链表父进程的Pid指向子进程的进程id因为子进程没有子进程所以其Pid为0。” 2.3 编程实现创建子进程 并且分别获取子进程和父进程的PID号 根据父进程和子进程的pid不同的特点我们可以在创建进程之前获取一次进程pid这是父进程的pid创建进程之后再一次获取进程pid并通过判断两次pid是否相同判断哪个是父进程pid哪个是子进程pid #include sys/types.h #include unistd.h #include stdio.hint main() {pid_t pid;pid_t pid2;pid getpid(); //获取fork之前进程PIDprintf(fork之前PID %d\n,pid);fork(); //创建一个子进程pid2 getpid(); //获取fork之后进程PIDprintf(fork之后PID %d\n,pid2);if(pid pid2){ //如果pid pid2代表是父进程printf(父进程PID %d\n,getpid());}else{ //如果pid ! pid2代表是子进程printf(子进程PID,子进程PID %d\n,getpid());}return 0; } 代码实现 2.4 根据fork函数的返回值也可以判断父子进程 #include stdio.h #include sys/types.h #include unistd.hint main() {pid_t pid;pid_t pid2;pid_t retpid;pid getpid();printf(before forl : pid %d\n,pid); retpid fork();pid2 getpid();printf(after fork : pid %d\n,pid2);if(retpid 0){printf(父进程PID,retpid %d,父进程PID %d\n,retpid,getpid());}else if(retpid 0){printf(子进程PID,retpid %d,子进程PID %d\n,retpid,getpid());}return 0; }代码实现 fork之前只有一个父进程在运行父进程的PID是17804然后调用fork函数创建了一个子进程, fork调用成功后返回两次两次返回唯一的区别是子进程返回0值父进程返回子进程PID是17804. 2.5 程序的存储空间是如何分配的 fork()父子进程的代码和数据的复制问题 进程数据代码数据 父进程创建子进程时代码共享(因为代码在内存中一般为只读)数据私有(写时拷贝)这也就解释了上面的fork()为什么会有两个不同的返回值。 之前Linux系统采用完全拷贝将父进程的内存地址和内容都重新拷贝一份 现在Linux系统采用写实拷贝只有再对某一变量运用时才执行拷贝 写实拷贝 当子进程刚刚被创建时子进程和父进程的数据和代码是共享的即父子进程的代码和数据通过页表映射到物理内存的同一块空间。 只有当父进程或子进程需要修改数据时才将父进程的数据在内存当中拷贝一份然后再进行修改。这种在需要进行数据修改时再进行拷贝的技术称为写时拷贝技术。 #include sys/types.h #include unistd.h #include stdio.hint main() {pid_t pid;int data 10;printf(父进程PID %d\n,getpid()); //获取父进程PIDpid fork(); //创建一个子进程if(pid 0){ //返回值如果是非负数代表是父进程printf(PID 0代表是父进程返回值 %d,父进程PID %d\n,pid,getpid()); //父进程的返回值是子进程的PID}else if(pid 0){printf(PID 0代表是子进程返回值 %d,子进程PID %d\n,pid,getpid()); //子进程的返回值是0data data 100;}printf(data %d\n,data);return 0; } 可以看到data的值在子进程中改变时data data 100;是通过重新赋值了父进程的数据段修改的父进程的data值没有改变 2.6 fork创建子进程的目的 一个父进程希望复制自己使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时父进程调用fork使子进程处理此请求。父进程则继续等待下一个服务请求到达。 #include sys/types.h #include unistd.h #include stdio.hint main() {pid_t pid;int data ;while(1){printf(please input a data\n);scanf(%d,data);if(data 1){pid fork();if(pid 0){printf(父进程PID %d \n,getpid());}else if(pid 0){while(1){//如果pid ! pid2代表是子进程printf(子进程PID,子进程PID %d\n,getpid());sleep(3);}}}else{printf(wait ,do no thie\n);}}return 0; } 2.一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下子进程从fork返回后立即调用exec。 3. vfork函数创建进程 3.1. 进程创建函数vfork函数原型和头文件 #include sys/types.h #include unistd.hpid_t vfork(void);无参数 pid_t 是一个宏定义其实质是int 被定义在sys/types.h中fork函数调用成功返回两次 返回值为0 代表当前进程是子进程 返回值非负数 代表当前进程为父进程 调用失败返回-1vfork - 创建子进程并阻塞父进程 既然vfork函数也可以创建进程与fork的区别是什么 vfork函数与fork函数的关键区别一 3.2. vfork保证子进程先运行当子进程调用exit退出后父进程才执行 首先我们在fork的时候 int main() {pid_t pid;pid fork();if(pid 0){while(1){printf(this is father print , pid %d \n,getpid());sleep(1);}}else if(pid 0){while(1){printf(this is chilid print, pid %d\n,getpid());sleep(1);}} return 0; } 3.2.1. 代码解析 调用 fork() 创建子进程: pid_t pid; pid fork(); fork() 会创建一个新的子进程返回两次一次在父进程中返回子进程的 PID一个正整数一次在子进程中返回 0。如果 fork() 返回负数则表示创建子进程失败代码中未处理此情况。 区分父进程和子进程: 父进程pid 0: if(pid 0) {while(1){printf(this is father print, pid %d \n, getpid());sleep(1);} } 在父进程中fork() 返回子进程的 PID正数。进入这个 if 块后父进程会进入一个无限循环每秒打印一次自己的 PID并输出 this is father print, pid ...。 子进程pid 0: else if(pid 0) {while(1){printf(this is child print, pid %d\n, getpid());sleep(1);} } 在子进程中fork() 返回 0。进入这个 else if 块后子进程会进入一个无限循环每秒打印一次自己的 PID并输出 this is child print, pid ...”。 3.2.2. 程序的执行效果 并行运行: 父进程和子进程是两个独立的进程它们的执行是并行的。由于进程的调度由操作系统管理因此输出会交替出现。输出交替显示: 终端中会看到父进程和子进程的输出不断交替或混合显示具体顺序取决于系统调度。 3.2.3. 示例输出 当你运行这个程序时终端可能会显示如下内容 3.2.4. 注意事项 无限循环: 由于两个进程都进入了无限循环while(1)程序将不会自动退出必须手动终止例如通过 Ctrl C。终端输出冲突: 因为父进程和子进程同时向同一个终端输出输出可能会出现交错或混合的情况。这是正常的因为两个进程是在并行执行。系统资源消耗: 长时间运行该程序会占用系统资源如 CPU 和内存所以在测试或调试时注意及时停止程序。 首先我们在fork的时候父进程和子进程同时运行 然后我们在vfork的时候 int main() {pid_t pid;pid vfork();if(pid 0){while(1){printf(this is father print , pid %d \n,getpid());sleep(1);}}else if(pid 0){ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);}} return 0; } vfork函数调用之后只运行子进程 我们在vfork的时候当子进程不退出的时候父进程无法运行 当改成让子进程执行三次退出后 #include sys/types.h #include unistd.h #include stdio.hint main() {pid_t pid;int cnt 0;pid vfork();if(pid 0){while(1){printf(this is father print , pid %d \n,getpid());sleep(1);}}else if(pid 0){ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){printf(子进程退出\n);break;}}} return 0; } 当使用vfork 函数之后 运行else if语句中的父进程子进程运行3次之后使用break函数结束循环进入到if语句中运行父进程。 我们可以看见当子进程正常退出之后父进程才执行。 3.3. vfork函数与fork函数的关键区别二 vfork直接使用父进程存储空间与父进程共享数据段不拷贝。 int main() {pid_t pid;int cnt 0;pid vfork();if(pid 0){while(1){printf(this is father print , pid %d \n,getpid());printf( printf在父进程cnt %d\n,cnt);sleep(1);}}else if(pid 0){while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){printf(子进程退出\n);exit(0);}}}return 0; } 可以发现当子进程改变cnt的值之后父进程的cnt也在改变因为改变的同一个cnt。 4. 进程退出 4.1. 进程退出的三种情况 代码运行完毕结果正确 exit() 、 _exit() 、_Exit()。 代码运行完毕结果不正确代码异常终止进程崩溃 4.2. 进程退出码 main函数是间接性被操作系统所调用的。当main函数调用结束后就应该给操作系统返回相应的退出信息而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回我们一般以0表示代码成功执行完毕以非0表示代码执行过程中出现错误这就是为什么我们都在main函数的最后返回0的原因。 当我们的代码运行起来就变成了进程当进程结束后main函数的返回值实际上就是该进程的进程退出码可以使用echo $?命令查看最近一次进程退出的退出码信息。 比如 代码正常运行结束后可以用echo $?命令查看退出码是0 使用echo $? 命令查看之后是 0属于正常退出 当代码被强行结束ctrlcecho $?命令查看退出码是130 使用echo $? 命令查看之后是 130属于强制退出 4.3. 进程正常退出 从man函数返回即调用return函数调用exit标准C语言库调用_exit或者 _Exit属于系统调用进程最后一个线程返回最后一个线程调用pthread_exit最后一个线程对取消cancellation请求做出响应 5. 父进程等待子进程退出 5.1. 为什么父进程要等待子进程退出 父进程等待子进程退出并收集子进程退出状态如果子进程退出状态不被收集那么子进程会变成僵尸进程。僵尸进程一个进程使用fork创建子进程如果子进程退出而父进程并没有调用wait或waitpid获取子进程 的状态信息那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。 在之前我们使用vfork创建子进程的时候子进程运行结束之后在运行父进程子进程退出时没有被父进程收集其退出的状态因此子进程最终会 变成“僵尸进程”。 int main() {pid_t pid;int cnt 0;pid vfork();if(pid 0){while(1){printf(this is father print , pid %d \n,getpid());printf( printf在父进程cnt %d\n,cnt);sleep(1);}}else if(pid 0){ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){printf(子进程退出\n);exit(0);}}} return 0; } 当我们使用fork创建子进程的时候子父进程同时运行子进程运行三次之后退出父进程没有收集其退出的状态其子进程也会成为”僵尸进程“。 int main() {pid_t pid;int cnt 0;pid fork();if(pid 0){while(1){printf(this is father print , pid %d \n,getpid());printf( printf在父进程cnt %d\n,cnt);sleep(1);}}else if(pid 0){ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){printf(进程退出\n);exit(0);}}}return 0; } 5.2. 进程等待相关函数wait原型和头文件 /*Linux下man 2 wait查看手册 */ #include sys/types.h #include sys/wait.hpid_t wait(int *wstatus);pit_t 函数返回值等待成功返回被等待进程的PID等待失败则返回-1 int *wstatus 输出型参数获取子进程的退出状态传入的是整型指针不关心可设置为NULL。 5.2.1. 父进程调用wait函数可以回收子进程终止信息。该函数有三个功能 阻塞等待子进程退出wait 会一直阻塞父进程直到有一个子进程终止。这意味着父进程会暂停执行直到至少有一个子进程结束。回收子进程残留资源当子进程终止后它的进程资源会暂时保留在系统中成为“僵尸进程”。调用 wait 可以回收这些资源避免系统资源浪费。获取子进程结束状态通过 wait 函数的参数 status父进程可以获取子进程的退出状态这个状态能够提供子进程是如何终止的信息。 wait一旦被调用就会一直阻塞在这里直到有一个子进程退出出现为止。调用成功则清理掉的子进程ID失败则返回-1表示没有子进程。使用wait函数传出参数status来保存进程的退出状态正常终止→退出值异常终止→终止信号。 借助宏函数来进一步判断进程终止的具体原因。 5.3. 使用wait函数实现父进程等待子进程退出 使用wait(NULL)函数等待子进程结束子进程被清除然后进入一个无限循环不断打印自身的 PID 和变量 cnt 的值。 int main() {pid_t pid;int cnt 0;pid vfork();if(pid 0){wait(NULL);while(1){printf(this is father print , pid %d \n,getpid());printf( printf在父进程cnt %d\n,cnt);sleep(1);}}else if(pid 0){ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){printf(子进程退出\n);exit(0);}}} return 0; } 5.4. 检查wait和waitpid所返回的终止状态的宏 正常结束 WIFEXITED(status): 如果返回值为非 0表示子进程是正常结束的例如通过调用 exit() 或从主函数返回。 WEXITSTATUS(status): 当 WIFEXITED(status) 为真时可以使用此宏获取子进程的退出状态。 这个退出状态是子进程调用 exit() 时传递的参数或主函数返回的值。 异常终止 WIFSIGNALED(status): 如果返回值为非 0表示子进程因信号而异常终止例如未捕获的信号如 SIGKILL、SIGSEGV 等。 WTERMSIG(status): 当 WIFSIGNALED(status) 为真时可以使用此宏获取导致子进程终止的信 号编号。 暂停状态 WIFSTOPPED(status): 如果返回值为非 0表示子进程处于暂停状态例如子进程收到 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU 信号。 WSTOPSIG(status): 当 WIFSTOPPED(status) 为真时可以使用此宏获取导致子进程暂停的信号 编号。 继续运行 WIFCONTINUED(status): 如果返回值为真表示子进程在暂停后已经继续运行例如收到 SIGCONT 信号。 5.4.1. 通过 wait(status) 阻塞等待子进程退出并通过 WEXITSTATUS(status) 获取子进程的退出状态码。 #include sys/types.h #include unistd.h #include stdio.h #include stdlib.hint main() {pid_t pid;int cnt 0;int status 10;pid fork();if(pid 0) //父进程{wait(status);printf(child quit ,child status %d\n,WEXITSTATUS(status));while(1){printf(this is father print , pid %d \n,getpid());printf( printf在父进程cnt %d\n,cnt);sleep(1);}}else if(pid 0) // 子进程{ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){ //当子进程执行三次后退出,退出之后执行父进程printf(子进程退出\n);exit(3);}}} return 0; } 子进程进程ID13139执行完后被清除没有变成僵尸进程。 如果需要子进程退出时的状态可以在exit函数写入一个状态值例如向父进程返回一个3可以写成 exit(3)父进程则需要用到宏函数WEXITSTATUS(status)获取状态值。 这里返回值为3表示子进程是正常结束的WIFEXITED(status) 为真时使用WEXITSTATUSstatus 获取子进程的退出状态码。 5.5. 进程等待函数waitpid函数原型和头文件 #include sys/types.h #include sys/wait.hpid_t waitpid(pid_t pid, int *wstatus, int options); //等待指定子进程或任意子进程pit_t 函数返回值 1.等待成功返回被等待进程的pid 2.如果设置了选项WNOHANG而调用中waitpid发现没有已退出的子进程可收集则返回0 3.如果调用中出错则返回-1这时errno会被设置成相应的值以指示错误所在pid_t pid 1.pid -1 等待任一子进程此种情况下wait和waitpid等效 2.pid 0 等待进程ID与pid相等的子进程 3.pid 0 等待组ID等于调用进程组ID的任一子进程 4.pid -1 等待组ID等于pid绝对值的任一子进程int *wstatus 输出型参数获取子进程的退出状态不关心可设置为NULL int options 提供了一些额外的选项来控制waitpid 常量 说明 WCONTINUED 若实现支持作业控制那么由pid 指定的任一子进程在停止后已经继续 但其状态尚未报告则返回其状态(POSIX.1的XSI扩展) WNOHANG 若由pid指定的子进程并不是立即可用的则 waitpid不阻塞此时其返回值为 0 WUNTRACED 若某实现支持作业控制而由 pid 指定的任一子进程已处于停止状态并且其状态自停止以来还未报告过则返回其状态。 WIESTOPPED宏确定返回值是否对应于一个停止的子进程 wait和waitpid的区别 wait 使调用者阻塞子进程不结束就一直不会运行父进程waitpid 有一个选项可以使调用者不阻塞 5.5.1. 通过waitpid()函数来检查子进程的退出状态与之前的版本相比这次你使用了WNOHANG选项让waitpid() 以非阻塞模式运行 int main() {pid_t pid;int cnt 0;int status 10;pid fork();if(pid 0){waitpid(pid,status,WNOHANG);// wait(status);printf(child quit ,child status %d\n,WEXITSTATUS(status));while(1){printf(this is father print , pid %d \n,getpid());printf( printf在父进程cnt %d\n,cnt);sleep(1);}}else if(pid 0){ while(1){printf(PID 0 this is chilid print, pid %d\n,getpid());sleep(1);cnt;if(cnt 3){printf(子进程退出\n);exit(3);}}} return 0; } waitpid(pid, status, WNOHANG) 使其父进程与子进程同时就行不存在阻塞模式但当子进程结束之后输入僵尸进程 5.6. 孤儿进程 父进程如果不等待子进程的退出在子进程之前就“结束”了自己的生命此时子进程叫孤儿进程。Linux 避免系统存在过多的孤儿进程init 进程收留孤儿进程变成孤儿进程的父进程。 #include sys/types.h #include unistd.h #include stdio.h #include stdlib.hint main() {pid_t retpid;int cnt 0;retpid fork(); //创建一个子进程if(retpid 0){ //当PID0代表是子进程while(1){printf(子进程PID %d,父进程PID %d\n,getpid(),getppid());cnt;if(cnt 3){ //当子进程执行三次后退出,退出之后执行父进程printf(子进程退出\n);exit(0);}sleep(1);}}else if(retpid 0){ //当PID是一个非负整数代表是父进程 printf(父进程PID %d\n,getpid()); }return 0; } 当父进程运行结束之后子进程就变成了孤儿进程需要注意的是不是所有的init都是1我这里就是1087大家也有可能是其他的这是因为 父进程退出后子进程被系统接管。在你的环境中子进程的父进程并没有成为 PID 1而是 1087。这可能是由于系统中某些进程层次结构例如容器化环境、不同的 init 系统或者进程管理策略的结果。这并不是异常行为而是符合系统进程管理的设计只要子进程的父进程指向一个长期运行的进程如 init 或类似进程子进程就不会成为僵尸进程。 可以在系统上运行 ps -p 1087 -o comm 来查看该进程的名称。 我运行这个命令之后出现了systemd在现代 Linux 系统中systemd 是一个常见的初始化系统和服务管理器它通常会接管孤儿进程。 这也就解释了我的init不是1而且1087了 6. 进程程序替换exec族函数 6.1. exec族函数作用和功能 exec族函数作用用fork函数创建新进程后经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时该进程被完全替换为新程序。因为调用exec函数并不创建新进程所以前后进程的ID并没有改变。 exec族函数功能在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件也可以是任何Linux下可执行的脚本文件 6.2. exec族函数原型和头文件 #include unistd.hextern char **environ;int execl(const char *pathname, const char *arg, .../* (char *) NULL */); int execlp(const char *file, const char *arg, .../* (char *) NULL */); int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */); int execv(const char *pathname, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[],char *const envp[]); 函数返回值exec函数族的函数执行成功后不会返回调用失败时会设置errno并返回-1然后从原程序的调用点接着往下执行pathname要执行的程序路径。可以是绝对路径或者是相对路径。在execv、execve、execl和execle这4个函数中使用带路径名的文件名作为参数file要执行的程序名称。如果该参数中包含“/”字符则视为路径名直接执行否则视为单独的文件名系统将根据PATH环境变量指定的路径顺序搜索指定的文件arg可执行程序所带的参数第一个参数为可执行文件名字没有带路径且arg必须以NULL结束argv命令行参数的矢量数组envp带有该参数的exec函数可以在调用时指定一个环境变量数组。其他不带该参数的exec函数则使用调用进程的环境变量. . .命令行参数列表。调用相应程序时有多少命令行参数就需要有多少个输入参数项。注意在使用此类函数时在所有命令行参数的最后应该增加一个空的参数项(NULL)表明命令行参数结束 exec族函数参数极难记忆和分辨函数名中的字符会给我们一些帮助 l : 使用参数列表p使用文件名并从PATH环境进行寻找可执行文件v应先构造一个指向各参数的指针数组然后将该数组的地址作为这些函数的参数。e多了envp[]数组使用新的环境变量代替调用进程的环境变量 6.3. exec族函数execl函数应用一 #include stdio.h #include unistd.hint main() {printf(execl之前\n); // 文件路径 执行的参数if(execl(./echoarg,echoarg,abc,NULL) -1){printf(execl失败\n);perror(why:); //如果wxecl函数调用失败调用perror函数输出出错信息}printf(execl之后\n);return 0; } //文件echoarg.c #include stdio.hint main(int argc,char *argv[]) {int i 0;for(i 0; i argc; i){printf(argv[%d]: %s\n,i,argv[i]); }return 0; } 在当前路径下运行execl.c文件通过execl函数运行当前路径下的echoarg文件cp文件将参数echoarg,abc传入到程序中。 代码总结 主程序(main.c)打印一条消息后尝试使用 execl 替换当前进程为另一个程序(echoarg)。execl 的第一个参数是要执行的程序路径后面的参数是传递给新程序的命令行参数。如果 execl 成功当前进程被替换后续代码不再执行如果失败会输出错误信息。被执行的程序(echoarg.c)打印出所有接收到的命令行参数包括程序自身的名称和传递的参数。 注意 我们先用gcc编译echoarg.c生成可执行文件echoarg并放在当前路径目录下。文件echoarg的作用是打印命令行参数。然后再编译execl.c并执行execl可执行文件。用execl 找到并执行echoarg将当前进程main替换掉所以”execl之后” 没有在终端被打印出来。 6.3.1. exec族函数execl函数应用二 我们直接在Linux下输入date指令会显示出系统的时间但是我们想使用代码来获取系统的时间所以使用execl函数实现 获取系统时间—首先我们得知道date的路径 代码编译 int main() {printf(execl之前\n);// 文件路径 执行的参数if(execl(/bin/date,date,NULL,NULL) -1){printf(execl失败\n);perror(why:); //如果wxecl函数调用失败调用perror函数输出出错信息}printf(execl之后\n);return 0; }6.4. exec族函数execlp函数应用 带p的一类exac函数包括execlp、execvp、execvpe如果参数file中包含/则就将其视为路径名否则就按 PATH环境变量在它所指定的各目录中搜寻可执行文件。举个例子PATH/bin:/usr/bin 我们会发现我们每一次运行另一个程序都需要用whereis来查找路径如果不写路径会怎样 int main() {printf(execl之前\n); // 文件路径 执行的参数if(execl(ps,ps,NULL,NULL) -1){printf(execl失败\n);perror(why); //如果wxecl函数调用失败调用perror函数输出出错信息}printf(after execl\n);return 0; }execl会调用失败因为在当前路径下招不到ps的路径。 我们使用execlp函数就可以解决这个问题. int main() {printf(execl之前\n); // 文件路径 执行的参数if(execlp(ps,ps,NULL,NULL) -1){printf(execl失败\n);perror(why); //如果wxecl函数调用失败调用perror函数输出出错信息}printf(after execl\n);return 0; } 使用execlp函数来获取系统时间就可以不用加绝对路径了因为execlp函数能通过环境变量PATH查找可执行文件ps 6.4.1. execlp函数应用2 int main() {printf(execl之前\n); // 文件路径 执行的参数if(execlp(data,data,NULL,NULL) -1){printf(execl失败\n);perror(why); //如果wxecl函数调用失败调用perror函数输出出错信息}printf(after execl\n);return 0; } 使用execlp函数来获取系统时间就可以不用加绝对路径了因为execlp函数能通过环境变量PATH查找可执行文件date 6.5. 配置PATH环境变量 echo $PATH //输出当前环境变量指令 如果需要修改环境变量可以在后面进行追加比如想将/etc/apache2/bin添加为环境变量可以这样写 export PATH$PATH:/etc/apache2/bin //自己配置环境变量用export指令 我们在运行程序的时候在程序名之前就不需要加./了也可以运行其他路径下的可执行程序 6.6. exec族函数execv函数应用 带v不带l的一类exac函数包括execv、execvp、execve应先构造一个指向各参数的指针数组然后将该数组的地址作为这些函数的参数。 如char *arg[]这种形式且arg最后一个元素必须是NULL例如char *arg[] {“ls”,”-l”,NULL}; int main() {char *argv[] {date,NULL,NULL}; //把所有的参数全部都放到数组中printf(execl之前\n); // 文件路径 执行的参数if(execv(/bin/date,argv) -1){//如果函数返回值为-1代表调用execv函数失败,反之执行date指令printf(execl失败\n);perror(why); //调用perror函数输出出错信息}printf(after execl\n);return 0; } 6.7. exec族函数execvp函数应用 int main() {char *argv[] {date,NULL,NULL};printf(execl之前\n); // 文件路径 执行的参数if(execvp(date,argv) -1){ //如果函数返回值为-1代表调用execvp函数失败,反之执行date指令printf(execl失败\n);perror(why); //调用perror函数输出出错信息}printf(after execl\n);return 0; } 6.8. exec族函数配合fork函数应用 当用户输入1时创建子进程修改配置文件的字段值当前配置文件LENG9通过 exec族函数配合fork函数修改成5 /*file_peizhi.c*/ #include sys/types.h #include sys/stat.h #include fcntl.h #include stdio.h #include unistd.h #include unistd.h #include stdlib.h #include string.hint main(int argc, char **argv) {int fdSrc;char *readBuf NULL;if(argc ! 2){ //判断C文件参数是不是有两个如果不是程序退出printf(param error\n);exit(-1);}fdSrc open(argv[1],O_RDWR); //打开配置文件int size lseek(fdSrc,0, SEEK_END); //计算配置文件有多少个字节lseek(fdSrc, 0, SEEK_SET); //让配置文件光标回到头readBuf (char *)malloc(sizeof(char) * size 8); //动态开辟readBuf的内存空间int n_read read(fdSrc,readBuf,size); //把配置文件的size个字节的内容读取到readBuf里面//char *strstr(const char *haystack, const char *needle);char *p strstr(readBuf,LENG); //字符串查找函数返回值为要查找的字符串的第一个字符的指 针第一个参数为待查找的原始字符串第二个参数为要查找的内容p p strlen(LENG); //偏移LENG的长度偏移到数据位置*p 5; //更改数据位置的值lseek(fdSrc, 0, SEEK_SET); //让配置文件光标回到头int n_write write(fdSrc,readBuf,strlen(readBuf)); //把读出的内容重新写入配置文件close(fdSrc); //关闭配置文件return 0; } int main() {pid_t pid;int data;printf(父进程PID %d\n,getpid()); //获取父进程PIDwhile(1){printf(请输入一个数据\n); //等待用户输入,当用户输入为1时父进程创建子进程在子进程中处理请求。scanf(%d,data);if(data 1){pid fork(); //创建一个子进程if(pid 0){ //返回值如果是非负数代表是父进程wait(NULL); //父进程等待子进程退出}else if(pid 0){while(1){if(execl(./file_peizhi,file_peizhi,comfig.txt,NULL) -1){ //执行已经写好的配置文件printf(execl失败\n);perror(why);}} }}else{printf(什么都不做\n);}}return 0; }可以看到成功把mfig.txt中的LENG9改成LENG5。 7. system函数 system是一个C/C的函数Linux操作系统下system 函数主要是执行shell 命令 7.1. system函数原型和头文件 #include stdlib.hint system(const char *command);int 函数返回值 1. 成功则返回进程的状态值 2. 当 sh 不能执行时。返回127 3. 其他原因失败返回-1 函数说明system函数会调用fork函数产生子进程由子进程来调用/bin/sh-c string来执行参数string字符 串所代表的命令此命令执行完后随即返回原调用的进程。在调用system函数期间SIGCHLD 信号会被暂时搁置SIGINT和SIGQUIT 信号则会被忽略。 不同于exec族函数system执行完之后还会执行原来的程序 7.2. system函数应用 #include stdio.h #include unistd.h #include stdlib.hint main() {printf(execl之前\n);if(system(date) -1){ //如果函数返回值为-1代表调用system函数失败,反之获取系统时间printf(execl失败\n);perror(why); //调用perror函数输出出错信息}printf(execl之后\n);return 0; }int main() {printf(execl之前\n);if(system(./file_peizhi comfig.txt) -1){ //如果函数返回值为-1代表调用system函数失败,反之获取系统时间printf(execl失败\n);perror(why); //调用perror函数输出出错信息}printf(execl之后\n);return 0; } 代码解释: system() 函数用于在程序中执行一个命令行命令。这行代码尝试执行 ./file_peizhi comfig.txt即运行当前目录下的 file_peizhi 程序并传递 comfig.txt 作为参数。如果 system() 调用成功它会返回命令执行的状态码通常为 0 表示成功非零表示错误但如果 system() 本身调用失败例如无法创建子进程或 system() 被禁用它会返回 -1。错误处理如果 system() 返回 -1程序会打印 execl失败\n 并使用 perror(why) 输出系统调用失败的原因。这可以帮助调试为何 system() 没有成功执行命令。 8. popen函数 popen函数允许一个程序将另外一个程序作为新进程来启动并可以传递数据或者通过它接受数据。其内部实现为调用 fork 产生一个子进程执行一个 shell 以运行命令来开启一个进程这个进程必须由 pclose 函数关闭。 popen函数与system函数在应用中的好处是可以获取运行的输出结果 调用system函数后指令的执行结果会在shell上打印出来如果想要把执行的结果保存到一个数组里system函数没有这个功能所以引入popen函数。 声明 #include stdio.hFILE *popen(const char *command, const char *type);int pclose(FILE *stream); //关闭文件流 参数 command 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 /bin/sh 并使用 -c 标志shell 将执行这个命令。 type 指定了管道的类型它可以是 “r”读取模式或 “w”写入模式。如果 type 是 “r”则文件指针连接到 command 的标准输出如果 type 是 “w”则文件指针连接到 command 的标准输入。 返回值 如果调用成功popen 返回一个 FILE 指针该指针可以用于读取或写入数据取决于 type 参数的值。如果调用失败popen 返回 NULL具体错误可以通过检查 errno 来确定。 总的来说popen 函数提供了一个比 system 更加灵活的方式来与子进程进行交互因为它允许你直接读取或写入子进程的输入和输出。这使得 popen 在需要处理命令输出或向命令提供输入的情况下非常有用。 popen函数和open函数一样都会返回一个文件流而且最后都需要把这个文件流关闭防止文件损坏。 8.1. popen函数原型和头文件 #include stdio.hFILE *popen(const char *command, const char *type);int pclose(FILE *stream);FILE * 返回值是一个文件指针函数执行成功返回文件指针否则返回NULL可用来存储执行后的结果const char *command 是一个指向以NULL结束的shell命令字符串指针shell将执行的命令 const char *type 1. r文件指针连接到 command 的标准输出 2. w文件指针连接到 command 的标准输入 由于popen是以创建管道的方式创建进程连接到子进程的标准输出设备或标准输入设备 因此其带有管道的一些特性同一时刻只能定义为写或者读。 8.2. popen函数应用 #include stdio.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.hint main() {FILE* fp;int fd;char readBuf[1024] {0};//FILE *popen(const char *command, const char *type);int size sizeof(readBuf) / sizeof(readBuf[0]); //计算数组的大小fp popen(ps,r); //运行ps指令//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);int n_read fread(readBuf,1,size,fp); //从fp文件流里面每次读1bit读size次到readBuf里面printf(read ret %d,readBuf \n%s\n,n_read,readBuf); //read ret 读取的字节数然后输出readBuf的内容fd open(./file,O_RDWR|O_CREAT,0600); //可读可写方式打开file文件如果没有则创建它//ssize_t write(int fd, const void *buf, size_t count);int n_write write(fd,readBuf,size); //把readBud中的内容写size个字节到file文件中printf(通过write函数向file文件写入了%d个字节的数据\n,n_write);pclose(fp); //关闭文件流close(fd); //关闭file文件return 0; } 8.2.1. popen函数应用二nhnh #include stdio.h #include stdlib.hint main() {FILE* fp;char readBuf[1024] {0};int size sizeof(readBuf) / sizeof(readBuf[0]); // 计算数组的大小// 先运行 file_peizhi 修改 comfig.txtsystem(./file_peizhi comfig.txt);// 使用 popen 打开管道来读取修改后的 comfig.txt 内容fp popen(cat comfig.txt, r); // r 表示读取模式if (fp NULL) {perror(popen 失败);return 1;}// 从 fp 中读取数据到 readBufint n_read fread(readBuf, 1, size, fp); // 每次读取 1 个字节共读取 size 次// 检查 fread 返回值if (n_read 0) {printf(没有读取到数据。请检查 comfig.txt 是否有内容。\n);} else {printf(读取到的内容 (字节数 %d):\n%s\n, n_read, readBuf);}// 关闭 popen 打开的管道if (pclose(fp) -1) {perror(pclose 失败);}return 0; } 运行结果 点个赞吧
http://www.w-s-a.com/news/537152/

相关文章:

  • 网站备案证明在自己电脑上做网站
  • 沈阳旅游团购网站建设怎么制作网站搜索窗口
  • 做化学合成的网站有哪些枣庄住房和城乡建设局网站
  • 天猫优惠券网站怎么做的网络连接
  • 保定网站建设多少钱公司网页网站建设+ppt模板下载
  • 用户上传商品网站用什么做建设跳转公积金网站
  • 买程序的网站上海市网站建设公司
  • 南通网站建设排名公司哪家好wordpress网站图片迁移
  • 河南省汝州文明建设门户网站博客网站建设源码
  • 单位建设网站的请示手机移动端网站案例
  • 国内做网站的企业网站结构有哪些类型
  • 南通网站建设制作公司苏州好的网站公司名称
  • 咸阳做网站开发公司哪家好珠海公司制作网站
  • 深圳网站建设好不好医疗网站前置审批
  • 做ic什么网站好安溪网站建设
  • 网站建设 慕课企业文化标语经典
  • 做短视频的网站都有哪些简约 时尚 高端 网站建设
  • 浦口区网站建设售后服务建设一个网站多少钱
  • 做个小网站大概多少钱广州h5网站
  • 360免费建站视频wordpress标签显示图片
  • 创建简易个人网站国外做网站被动收入
  • 轻定制网站建设网页培训哪个机构好
  • 青岛海诚互联做网站好吗计算机软件开发培训机构
  • 德钦网站建设如何在网站上做用工登记
  • 创意品牌网站云服务
  • 个人备案网站可以做商城展示如何制作网页二维码
  • 网站建设php教程视频百度seo 站长工具
  • 外包小程序两个相同的网站对做优化有帮助
  • 网站备案主体修改wordpress 导航图片
  • 怎么建设网站数据库用vs代码做网站