IO进程 学习笔记

……接上文

fork创建子进程的特点

  1. fork创建一个子进程,父进程返回子进程的pid,子进程中返回0。
  2. fork创建的子进程几乎拷贝了父进程所有的内容(三个段:堆栈、数据、代码),fork之前的代码被复制并不会被执行,fork之后的代码被复制并执行。
  3. fork创建进程一旦成功,进程之间的空间就相会独立。各自分配0-4G的虚拟内存空间。
  4. fork创建进程之前打开的文件可以通过复制拿到同一个文件描述符 操作同一个文件(同一个文件指针)
  5. 如果父进程退出,子进程没有退出,子进程会变成孤儿进程被init进程收养。变成后台进程。

如果子进程退出,父进程没有退出。父进程没有回收子进程的资源,子进程就会变成僵尸进程,应该避免僵尸进程的产生。

defunct :僵尸进程的标志。

进程函数

fork和vfork(知道)

1.fork(): 子进程拷贝父进程的数据段,代码段

vfork(): 子进程与父进程共享数据段

2.fork(): 父子进程执行次序不确定

vfork(): 保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行

总结:fork: 更通用,适用于需要创建一个完全独立的子进程的场景,vfork: 更适用于子进程立即执行 exec() 覆盖其自身的场景,因为它避免了不必要的地址空间复制,提高了性能。

退出进程(exit)

void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
不返回。
exit(0);
void _exit(int status);
功能:结束进程,不会刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
    通常0表示正常结束;
    其他的数值表示出现了错误,进程非正常结束
    
两者的区别
exit(0);
刷新缓存,关闭所有打开的文件指针
_exit(0);
立即终止进程,但是不会进行上述的操作

exit(0)和return  0;区别
exit 是一个函数,结束进程
return 是一个关键字,结束函数

回收进程资源(wait)

pid_t wait(int *status); 
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
        失败:-1
        
示例代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{

    pid_t pid = fork();
    if (pid < 0)
    {
        perror("creat err");
        return -1;
    }
    else if (pid == 0)
    {
        printf("in child\n");
        sleep(5);
        printf("子进程结束\n");
    }
    else
    {
        printf("in parent\n");
        pid_t cpid = wait(NULL);
        printf("%d\n",cpid);
        printf("子进程资源已回收\n");
    }
    return 0;
}

pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
    pid:>0     指定子进程进程号
         =-1   任意子进程
         =0    等待其组ID等于调用进程的组ID的任一子进程
         <-1   等待其组ID等于pid的绝对值的任一子进程
    status:子进程退出状态
    options:0:阻塞
              WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
      当使用选项WNOHANG且没有子进程结束时:0
      出错:-1
      

wait(NULL) = waitpid(-1,NULL,0) 阻塞回收任意子进程
waitpid(-1,NULL,WNOHANG):不阻塞回收任一子进程
waitpid(pid,NULL,0)阻塞回收指定的子进程

获取进程号

pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("creat err");
        return -1;
    }
    else if (pid == 0)
    {
        printf("in child\n");
        printf("chpid = %d\n",getpid());
        printf("chppid = %d\n",getppid());
    }
    else
    {
        printf("in parent\n");
        printf("prpid = %d\n",getpid());
        printf("prppid = %d\n",getppid());
    }
    return 0;
}

守护进程

特点

守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。

创建按步骤

  1. 创建子进程,父进程退出

让子进程变成孤儿进程,成为后台进程;fork()

  1. 在子进程中创建新会话

让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()

  1. 改变进程运行路径为根目录

原因进程运行的路径不能被删除或卸载;chdir("/")

  1. 重设文件权限掩码

增大进程创建文件时权限,提高灵活性;umask(0)

  1. 关闭文件描述符

将不需要的文件关闭;close()

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

int main(int argc, char const *argv[])
{
    // 1.创建子进程
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    if (pid == 0)
    {
        //2.在子进程中创建会话
        setsid();
        //3. 改变运行路径
        chdir("/");
        //4. 重设文件权限掩码
        umask(0);
        //5.关闭文件描述符
        for (int i = 0; i < 3; i++)
        {
            close(i);
        }
        while (1);
    }
    else
    {
        exit(0);
    }
    return 0;
}

练习

创建一个守护进程,循环间隔1s向文件中写入一串hello。

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

int main(int argc, char const *argv[])
{
    // 1.创建子进程
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    if (pid == 0)
    {
        // 2.在子进程中创建会话
        setsid();
        // 3. 改变运行路径
        chdir("/");
        // 4. 重设文件权限掩码
        umask(0);
        // 5.关闭文件描述符
        for (int i = 0; i < 3; i++)
        {
            close(i);
        }
        // 打开一个文件
        int fd = open("./a.txt", O_RDWR | O_CREAT | O_APPEND, 0777);
        if (fd < 0)
        {
            perror("open err");
            return -1;
        }
        while (1)
        {
            write(fd,"hello\n",6);
            sleep(1);
        }
    }
    else
    {
        exit(0);
    }
    return 0;
}

exec函数族

作用:在当前进程中执行一个新的进程

1. int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
使用可执行文件的绝对路径或相对路径 path 来替换当前进程的映像。参数通过变长参数列表传递,必须以 NULL 结尾。
2. int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
类似 execl,但 file 只需要是可执行文件的名称,函数会根据环境变量 PATH 查找可执行文件的路径。                       
3. int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
与 execl 类似,但允许传递一个自定义的环境变量列表 envp。这个环境会代替当前进程的环境变量。
4. int execv(const char *path, char *const argv[]);
使用路径 path 和参数数组 argv[] 替换当前进程。argv[0] 是程序名,argv[] 数组必须以 NULL 结束。
5. int execvp(const char *file, char *const argv[]);
类似 execv,但会根据 PATH 环境变量查找可执行文件。
6. int execvpe(const char *file, char *const argv[],char *const envp[]);
与 execvp 类似,但允许使用自定义的环境变量 envp。

线程

  1. 概念

线程是一个轻量级的进程,为了提高系统的性能引入线程,线程和进程都参与统一的调度。

  1. 进程和线程的区别

共性:

都为操作系统提供了并发执行能力。

不同点:

  1. 调度和资源上:线程是系统调度的最小单位,进程是资源分配的最小单位。
  2. 地址空间上:同一个进程创建多个线程共享进程资源,进程的地址空间相互独立。
  3. 通信方面:线程的通信相对简单,只需要通过全局变量就能实现。但是需要考虑临界资源(临界资源包括同一个文件,全局变量等)访问的问题,进程间的通信相对复杂,需要借助进程间的通信机制(借助3g-4g的的内核空间)
  4. 安全性方面:线程的安全性相对较差,当进程结束时会导致所有线程退出,进程相对于安全。

线程资源

共享的资源:可执行的指令,静态的数据,进程中打开的文件描述符,信号处理函数。当前的工作目录,用户的ID,用户的组ID

私有资源:线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈、错误号 (errno)、信号

掩码和优先级、执行状态和属性

线程标识:

主线程的 TID 和 PID 是相同的,每个子线程有自己独立的 TID,但它们都共享相同的 PID

线程的函数接口

创建线程(pthread_create)

头文件
#include <pthread.h>
int pthread_create(pthread_t  *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建一个线程
参数:1.pthread_t *thread:线程标识,成功创建线程后,pthread_create 会将新线程的 ID 写入 thread 指向的内存位置。
      2.const pthread_attr_t *attr:线程属性, NULL:代表设置默认属性
      3. void *(*start_routine):函数名,代表线程函数,指向一个函数的指针,这个函数就是线程的执行体(也就是线程的入口函数)。该函数必须符合 void *(*start_routine)(void *) 的原型,即接受一个 void * 类型的参数,并返回一个 void * 类型的值。
 4.void *arg:传递给 start_routine 的参数。arg 是一个通用的指针,可以传递任何类型的数据(通常是一个结构体的指针,以便传递多个参数)。如果不需要传递参数,可以传递 NULL
 
返回值:成功返回0
        失败返回错误码


#include <stdio.h>
#include <pthread.h>

void *mythread(void *arg)
{
    printf("子线程执行结束\n");
}
int main(int argc, char const *argv[])
{
    pthread_t tid;
    //创建一个线程
    if(pthread_create(&tid,NULL,mythread,NULL))
    {
        printf("create failed\n");
        return -1;
    }
    printf("主线程执行中。。。\n");
    sleep(2);
    printf("主线程执行结束\n");
    return 0;
}

                                                                                                                                    未完待续……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值