进程基础1

引言

上一次,我们已经学习过了什么是进程和进程与程序的区别,这次我们要学习如何创建,终止,等待进程。

1.进程创建

1.1 fork函数初识

头文件:#include<unistd.h>
pid_t fork(void);
返回值:自进程返回0,父进程返回子进程id,出错返回-1。

当系统调用fork的时候,内核要做:

  • 1.分配新的内存块和内核数据结构给子进程。
  • 2.将父进程部分数据结构(子进程能使用到的)内容拷贝给子进程
  • 3.添加子进程到系统进程列表中(将子进程放入链表中组织起来)
  • 4.由调度器调度(父子进程先后顺序由调度器确定)
注意:在fork之前父进程的操作与子进程无关,fork之后,谁先执行完全由调度器决定。

1.2fork返回值:

  • 子进程返回0。
  • 父进程返回子进程ID。

1.3写时拷贝

注意两点:

  • 1.当fork创建子进程的时候,代码共享,数据功效
  • 2.当父进程/子进程任意一个程序尝试对数据进行修改,操作系统会采用写实拷贝:重新建设该写入进程与物理内存的映射关系,各自写时拷贝一个副本。
  • 好处:不会浪费多余的资源

1.4fork用法:

  • 1.父进程希望复制自己,使子进程和父进程执行不同的代码段
  • 2.一个进程要执行不同的程序。

1.5fork失败的原因:

  • 系统由太多进程
  • 实际用户的进程数超过限制

2. 进程终止

2.1进程退出情况

  • 1.代码运行成功,结果正确
  • 2.代码运行成功,结果错误(与预期不符)
  • 3.代码异常终止(除0,野指针)

2.2 进程常见退出方法

正常退出:

  • return返回
  • exit
  • _exit

2.4_exit函数

头文件:#Include<unistd.h>
void exit(int status);
参数:status定义了进程的终止状态,父进程通过wait来获取该值

2.3exit函数

头文件:#include<unistd.h>
void exit(int status);

让我们实用一下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
    printf("cpp foever\n");   
    exit(10);

    return 0;
}

查看返回值:
在这里插入图片描述

exit和_exit比较:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    printf("cpp foever");
    sleep(1);    
    //_exit(10);
     exit(10);
    return 1000;
}

exit的情况:
在这里插入图片描述
_exit的情况:

在这里插入图片描述
由此我们可以看出exit和_exit是有区别的

区别在于exit要另外执行以下两点:
  • 执行用户定义的清理函数
  • 关闭所有打开的流,所有缓存的数据均被写入。

2.4 return退出

=执行exit,因为调用main的运行时函数会将main的返回值当作exit的参数。

3.进程等待

3.1作用:

  • 1.回收进程的资源,避免内存泄漏。
  • 2.父进程可能需要直到子进程的返回信息。

3.2等待方式:

  • 1.wait方法
头文件:#include<sys/types.h>
	  #include<sys/wait.h>

pid_t wait(int* status);

返回值:
成功返回被等待进程pid(父进程等待子进程,返回子进程pid),失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心可以设置为NULL
  • 2.waitpid方法
头文件:#include<sys/types.h>
	  #include<sys/wait.h>
	  
	  void waitpid(pid_t pid, int* status, int options);

返回值: 
	>0,等待成功
	<0,等待失败
	==0,等待进程未结束
参数:
	pid:
		pid = -1,等待全部子进程。
		pid > 0,等待其进程与pid相等的子进程,输入你要等待的进程
	status:
		输出型参数,获取子进程退出状态
		WIFEXITED:若位正常终止子进程的类型,则为真。
		WEXITSTATUS:若WIFEXITED非0,提取退出码。
	options:
		0:阻塞式。
		!0:非阻塞式
  • 3.使用的情况

    • 1.如果子进程已经退出,调用wait/waitpid,会立即返回,并且释放资源,获得子进程的退出信息。
    • 2.任意时刻调用wait/waitpid,如果子进程存在且再运行,则父进程会阻塞等待
    • 3.如果不存在子进程,则返回错误。
  • 4.获取子进程status

    • 不论wait和waitpid都有一个status参数,该参数是一个输出参数,有操作系统填充
    • 如果传值为NULL,表示不关心子进程退出信息
    • 否则,该参数是子进程的退出信息
    • status要作为为徒看待,如下图(只研究status低16bit位)
      在这里插入图片描述
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>

int main()
{
   pid_t id = fork();
   if(id == 0)
   {
       printf("I am child :.. %d\n", getpid());
       sleep(5);
        exit (13);
   }
   else
   {
       printf("father : pid : %d, ppid : %d\n", getpid(),getppid());
       int status = 0;
       int ret = waitpid(id, &status, 0);
       if(ret < 0)
       {
           printf("wait error, wait ret : %d\n", ret);
       }
       else
       {
           printf("wait success...%d\n", ret);
           printf("wait status   :%d\n", ((status) >> 8) & 0xFF);//显示第8比特位
           printf("exit signal   :%d\n", status & 0x7F);//显示错误信号的位置
       }
   }
   return 0;
}

运行结果
在这里插入图片描述
进程可以通过发送信号的方式人为终止,或者因为进程内部出现错误被终止。

  • 5.等待方式:
  • 阻塞式:条件不发生,父进程一直等待
  • 非阻塞:条件不发生,父进程返回运行其他程序,然后再去验证条件是否达成,如果还是没有,运行上一步,如此循环。
  • 什么是阻塞?
    当某个条件不满足,操作系统将该进程设置为非运行状态,并将该进程(PCB)放入等待队列,当条件满足时,从等待队列中将该进程唤醒(将该进程状态设为R状态),并从等待队列中移除,交由调度器管理。
    阻塞式:
 int ret = waitpid(id + 1, &status, 0);

非阻塞式写法:

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("I am child :.. %d\n", getpid());
        sleep(5);
        exit (13);
    }
    else
    {
        printf("father : pid : %d, ppid : %d\n", getpid(),getppid());
        int status = 0;
        while(1)
        {
            int ret = waitpid(id, &status, WNOHANG);
            if(ret < 0)
            {
                printf("wait error, wait ret : %d\n", ret);
            }
            else if(ret > 0)
            {
                printf("wait success...%d\n", ret);
                printf("wait status   :%d\n", ((status) >> 8) & 0xFF);
                printf("exit signal   :%d\n", status & 0x7F);
                break;
            }
            else
            {
                sleep(1);//做其他事
                printf("parent wait again! \n");//轮询等待
            }
        }
    }
    return 0;
}

4.进程替换

4.1原理:
  • 用fork创造子进程后父子进程执行的是和父进程相同的程序,如果想要执行另外一个程序,子进程需要通过一种exec函数。当进程调用了exec函数之后,进程代码段和数据段完全被新程序替换,从新程序的启动例程开始执行。
  • 调用exec不创建新的进程,所以调用exec前后进程id不变。
4.2 exec函数
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[]);
  • 返回值:exec只有错误的返回值,但是没有正确的返回值,因为其一旦正确,他就会加载新的程序开始执行。

  • 解释:

    • l(list):表示参数采用列表
    • v(vector):参数用数组
    • p(path) : 有P自动搜索环境变量PATH
    • e(env):表示自己维护环境变量
      为了理解函数的正确使用方式,让我们看看下面的代码:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值