【Linux】进程控制

12 篇文章 1 订阅

进程创建

fork()函数创建一个子进程

#include <unistd.h>
pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程id,出错返回-1。
进程调用fork函数执行时,控制转移到内核中的fork代码,内核执行以下内容:
1. 分配新的内存块和内核数据结构给子进程。
2. 将父进程部分数据结构内容拷贝至子进程。
3. 添加子进程到系统进程列表当中。
4. fork返回,开始调度器调度。

fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
当系统中进程太多时,fork可能会调用失败。

vfork()函数创建一个子进程

vfork()用于创建一个子进程,子进程和父进程共享同一个虚拟地址空间,而fork()创建的子进程具有自己的虚拟地址空间。
vfork()保证子进程先运行,在调用exec或者exit()之后父进程再运行,若没有exit()子进程,则父进程阻塞等待,不会运行。
因为子进程与父进程共享同一虚拟地址空间,所以子进程可以改变父进程变量的值。

下面通过代码验证上述结论。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 int m=100;
  5 int main()
  6 {    
  7     printf("%d\n",m);
  8     int pid=vfork();
  9     if(pid<0)
 10     {
 11         printf("error\n");
 12         return -1;
 13     }
 14     else if(pid==0)
 15     {
 16         printf("child:%d\n",m);
 17         m=200;
 18  
 19     }
 20     else
 21     {
 22         printf("parent:%d\n",m);
 23     }
 24     return 0;
 25 }

程序运行结果为:
100
100
200
因为子进程和父进程共享程序地址空间,所以该程序运行时,先输出m=100,之后调用vfork,子进程先运行,父进程阻塞等待,子进程将全局变量m改为200,之后子进程return退出。父进程再运行,此时m已经被改为200,输出m=200,父进程运行完毕,程序退出。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 int main()
  5 {
  6     int m=100;
  7     printf("%d\n",m);
  8     int pid=vfork();
  9     if(pid<0)
 10     {
 11         printf("error\n");
 12         return -1;
 13     }
 14     else if(pid==0)
 15     {
 16         printf("child:%d\n",m);
 17         m=200;
 18  
 19     }
 20     else
 21     {
 22         printf("parent:%d\n",m);
 23     }
 24     return 0;
 25 }

这个程序与上面的有所不同,这个程序中,变量m不再是全局的。该程序运行时,先运行子进程,m值被改为200,再运行第24行,return 0之后m的空间被释放,所以再运行父进程时m值为随机值。

进程终止

进程退出有三种场景:
1. 代码运行完毕,结果正确。
2. 代码运行完毕,结果不正确。
3. 代码异常终止。

进程退出的方法:
正常退出:可以使用命令 echo $? 查看进程退出码,进程退出码一个字节,最大值为255。
1. return:只有在main函数中执行return才会退出,return n 相当于调用exit(n)。
2. exit()
3. _exit()

exit()和_exit()的区别:
实际上exit也是调用了_exit退出函数的,只不过在调用_exit之前,exit还进行了一些多余的工作,也正是因为这样,相比起来exit就没有那么接近底层的系统调用,更应该说是包装过的标准C库函数。_exit包含在头文件unistd.h中,exit包含在头文件stdlib.h中。
exit()函数会逐步释放所有的资源,执行用户自定义的清理函数。关闭所有打开的文件描述符,而且会刷新输入输出流的缓冲区。最后调用_exit()。
_exit()函数相对exit函数就比较粗暴一点,它会直接释放资源,不刷新缓冲区。
1. 让调用的进程马上终止。
2.关闭所有由这个进程打开的文件描述符。
3.调用进程的所有子进程都被初始化init进程收养,调用进程将发送SIGCHLD给他的父进程(这都是因为他即将要退出了,当然要安顿好自己的孩子和告别父母啦)。

通过代码来查看exit和_exit的区别
 

int main()
{
 printf("hello\n");
 printf("hello");
 exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello
hello[root@localhost linux]#
int main()
{
 printf("hello\n");
 printf("hello");
 _exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello
[root@localhost linux]#

我们再回到函数定义来看一下exit和_exit
void _exit(int status);
void exit(int status);
status参数为进程退出状态码,可以通过父进程等待来获取,虽然该退出码是int类型,但是只有低8位可以被使用,当exit(-1)时,进程退出码为255。可以通过echo $?查看。

进程等待

为什么需要进程等待?
我们知道,子进程退出,父进程如果对其退出状态没有读取,子进程会变为僵尸进程,占用资源。换句话说,进程等待就是为了回收子进程资源,获取子进程退出信息,避免产生僵尸进程。

wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status)

等待任意子进程的退出 如果没有子进程退出就一直阻塞等待,不返回,直到子进程退出。
status:用于获取子进程的退出状态,如果不关心置NULL
返回值:成功返回被等待进程pid,失败返回-1

waitpid
 

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
    当正常返回的时候waitpid返回收集到的子进程的进程ID;
    如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
 pid:
     pid=-1,等待任一个子进程。与wait等效。
     pid>0.等待其进程ID与pid相等的子进程。
 status:
     用于获取子进程退出状态码,不关心可以置空。
     WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
     WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
 options:
     WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。

获取子进程status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16位):

当子进程正常终止时,status低16位中高8位存放的是进程退出码,低8位为0。
当子进程被信号终止时,status低16位中高8位为0,低8位存放的是终止信号的信号值。其中有一位core dump标志,可以分析进程被异常终止的原因等。

int main()
{
 pid_t pid;
 pid = fork();
 if(pid < 0)
 {
     return 1;
 } 
 else if( pid == 0 )
 {   //child
     printf("child is run, pid is : %d\n",getpid());
     sleep(5);
     exit(257);
 } 
 else
 {
     int status = 0;
     pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
     printf("this is test for waitpid\n");
     if( WIFEXITED(status) && ret == pid )
     {
         printf("wait child 5s success, child return code is:%d.\n",WEXITSTATUS(status));
     }
     else
     {
         printf("wait child failed, return.\n");
         return 1;
     }
 }
 return 0;
}

运行结果:
[root@localhost linux]# ./a.out
child is run, pid is : 45110
this is test for waitpid
wait child 5s success, child return code is :1.

上述代码中,程序调用fork创建子进程之后,父进程调用waitpid阻塞等待子进程退出,并用变量status来获取子进程退出码。子进程调用sleep函数5秒之后调用exit(257)退出。退出码为257低8位,为1。退出后父进程waitpid等待成功,返回子进程id,ret为子进程id。因为status为正常终止返回的状态, WIFEXITED(status)为真,进入if语句,调用WEXITSTATUS(status)输出进程退出码。

进程程序替换

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec函数并不创建新的进程,所以调用exec前后进程id并未改变。

    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg, ..., char * const envp[]);

    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execve(const char *path, char *const argv[],char * const envp[]);

只有execve()是系统调用,其他五个函数最终都调用execve()。

path:需要替换的具体程序名,需要全路径
file:需要替换的程序名,只需要名字即可,无需路径
arg,..., :参数列表 需要在末尾加NULL
argv[] :参数数组 数组最后一个元素为NULL

envp[]:自己组装的环境变量

带p:无需带路径,只需要告诉操作系统要替换的程序名
不带p:需要带路径名,告诉操作系统要替换的程序具体在哪
带v:参数格式为数组
不带v:参数格式为列表
带e:需要自己组装环境变量,末尾加NULL 不会继承父进程的环境变量
不带e:继承父进程的环境变量

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值