苏嵌实训——day14

一、IO

1.1 fseek

头文件: #include <stdio.h>
原型:int fseek(FILE *stream, long offset, int whence);
功能:读写指针的偏移
参数:
    stream:目标文件流指针
    offset:如何偏移,偏移多少
        如果为负数,代表向前偏移,如果偏移出了文件的开头,会返回报错。
        如果该数为正数,代表向后偏移,如果偏移除了文件的末尾,会扩大文件,用'\0'来填,那么此类文件称为空洞文件
        注意:如果偏移后没有对其进行任何写入操作,内核认为该偏移无效,不会扩大文件大小
     whence:基准位置  ------》根据哪一个位置进行偏移
     SEEK_SET:根据文件开头进行偏移
     SEEK_CUR:根据用户当前位置进行偏移
     SEEK_END:根据文件末尾进行偏移 
     
返回值:
    成功返回0
    失败返回-1
#include <stdio.h>


char ch = 0;
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("./1.txt","r+");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    //文件中的数据为helloworld


    //从文件的末尾向后偏移----空洞文件
    fseek(fp,2000,SEEK_END);
    fputc('a',fp);


    ch = fgetc(fp);
    printf("ch = %c\n",ch);
    fseek(fp,1,SEEK_CUR);   //从当前位置向后偏移一个字节
    ch = fgetc(fp);
    printf("ch = %c\n",ch);
    fseek(fp,-1,SEEK_END);
    ch = fgetc(fp);
    printf("ch = %c\n",ch);
    return 0;
}

1.2 sprintf

头文件: #include <stdio.h>
原型:int sprintf(char *str, const char *format, ...);
功能:向一个固定的地址存放字符串(一般用于字符串的拼接)
参数:
     str:要存放格式化完成的字符串的地址
     format:格式化字符串
     ...:可变参数(一般放置变量)
返回值:
    成功返回输出的字节个数
    失败返回负数
#include <stdio.h>
int main(int argc, char const *argv[])
{
    char name[20] = "zhangsan";
    int age = 18;
    char sex = 'w';
    char phone[12] = "12345678900";
    char buf[123] = {0};
    sprintf(buf,"name:%s--age:%d--sex:%c--phone:%s",name,age,sex,phone);
    printf("buf = %s\n",buf);
    return 0;
}

1.3 snprintf

头文件: #include <stdio.h>
原型:int snprintf(char *str, size_t size, const char *format, ...);
功能:按照固定的大小去格式化字符串输出到字符地址中
参数:
     str:要存放格式化完成的字符串的地址
     size:大小(规定要写入str这片地址中的字节大小)
     format:格式化字符串
     ...:可变参数(一般放置变量)
返回值:
    成功返回输出的字节个数
    失败返回负数

1.4 fprintf

头文件: #include <stdio.h>
原型:int fprintf(FILE *stream, const char *format, ...);
功能:格式化输出字符串到文件中(一般用于书写日志文件)
参数:
     stream:目标文件流指针
     format:格式化字符串{固定的字符串和占位符}
     ...:可变参数(一般放置变量)
返回值:
    成功返回输出的字节个数
    失败返回负数
#include <stdio.h>


int main(int argc, char const *argv[])
{
    char name[20] = "zhangsan";
    int age = 18;
    char sex = 'w';
    char phone[12] = "12345678900";
    FILE *fp = fopen("./1.txt","w");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    fprintf(fp,"name:%s--age:%d--sex:%c--phone:%s",name,age,sex,phone);
    return 0;
}

二、缓冲区

预定义流
在程序开始之前,创建三个文件描述符,分别为0,1,2对应的标准输入,标准输出,标准错误输出,同时也在其基础上封装了三个预定义流指针
标准输入:stdin --->键盘文件
标准输出:stdout --->终端文件
标准错误输出:stderr --->终端文件
#include <stdio.h>
int main(int argc, char const *argv[])
{
    char buf[123] ={0};
   // fgets(buf,123,stdin);   //从标准输入流中读取数据
    //printf("buf = %s\n",buf);
    //标准输出
   // fprintf(stdout,"helloworld%s","123123");
   
    //标准错误输出
    fprintf(stderr,"helloworld%s","123123");
    while(1);
    return 0;
}

2.1 缓冲区

缓冲区是什么?标准IO在文件IO的基础上封装的一片存放数据的地址(一般用来存放不着急得数据),等到缓冲区这个地址得数据存满,或者说程序员手动刷新缓冲区,空间中得数据会被调用文件IO操作。

全缓冲:一般对文件得操作,缓冲区大小为4096个字节。(页)
刷新缓冲区得条件:
1.缓冲区满刷新缓冲区
2.程序结束刷新缓冲区
3.程序员手动刷新缓冲区

fflush

头文件:#include <stdio.h>
原型:int fflush(FILE *stream);
功能:刷新缓冲区
参数:目标文件流指针
返回值:
    成功返回:0
    失败返回:EOF
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    FILE *fp = fopen("1.txt","w");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
#if 0
    while(1)
    {
        fprintf(fp,"helloworld");
        usleep(10000);
    }
#endif
    fprintf(fp,"helloworld");
    fflush(fp);
    while(1);
    return 0;
}

2.2 行缓冲:

只有两个行缓冲,标准输入,标准输出,行缓冲得大小为1024个字节
刷新行缓冲得条件:
1.缓冲区遇到\n则刷新缓冲区
2.缓冲区满则刷新缓冲区
3.当标准输入和标准输出一方要使用缓冲区时,正在使用得一方需要让出缓冲区,给另一方使用
4.fflush刷新缓冲区
5.程序结束

#include <stdio.h>
int main(int argc, char const *argv[])
{
    int i = 0 ;
    //验证缓冲区大小
    for(i = 0; i < 1025;i++)
    {
        printf("1");  //每次向准备输出输入一个字节
    }
    //验证\n刷新缓冲区
    printf("hello world\n");

    char buf[123] = {0};
    printf("helloworld1");
    scanf("%s",buf);
    while(1);
    return 0;
}

2.3 无缓冲

一般为标准错误输出,一般用于比较着急得数据,所以不会进入缓冲区,直接调用文件IO
一般使用方式:fprintf(stderr,“hello world”);
后续报错信息:使用这种方式
文件IO和标准IO得区别
文件IO属于系统调用,由操作系统提供,速度快,但是频繁调用文件IO会降低内核得工作效率,并且移植性较差。
标准IO是由标准c库所提供,是在文件IO得基础上封装出来得API接口,移植性高并且在文件IO得基础上封装了一片缓冲区,降低了文件IO得调用次数,提高了内核得效率。
所以说我们得根据情况来使用者这两种IO模型。

三、进程

任务:目标结果
程序:是为了完成任务,编写得一段代码,是一个静态得

3.1 进程的概念

进程是程序为了完成任务执行得一次过程,是一个动态的,进程被称为资源分配的最小单位,因为每一个进程在启动初期,都会申请一个0-4G的虚拟空间。
这个空间分为两个部分,0-3G用户空间,3-4G是内核空间,0-3G是进程之间独有的空间,互不影响。3-4G属于多进程共享的空间(后续于进程间通讯使用),因为进程用户空间相互独立,互不影响,所以安全性较高。还会申请一个PCB进程控制块,是一个结构体,tast_struct里面存储了所有的进程资源
如:PC程序计数器,堆栈,文件描述符,进程的状态,进程号等。
操作系统启动时会自动出创建三个进程:
0:负责引导系统启动,也会创建一个1号进程 --》init进程
1:负责初始化硬件,回收资源
2:负责资源的分配,系统的调度
进程之间存在这个一种竞态。执行速度是不一定的,所以父子进程结束的快慢也是不一定的。

3.2 进程的调度机制:

在这里插入图片描述

3.3 进程的状态

在这里插入图片描述

3.4 进程的标志

进程号(PID):linux分配的进程的编号,每个进程都不一样,方便管理
进程在结束的时候,会释放进程号的所有权,其它进程等待它释放一段时间后分配,并不会结束后立马区分配。

3.5 进程相关的命令:

1.pstree

以树的形式显示所有的进程
如果加上-p参数会显示进程号

2.ps -ef

主要查看父子进程关系
PID 进程ID PPID 父进程ID

3. ps aux

主要查看进程的状态
在这里插入图片描述

进程的状态:
1.R:运行态
2.S: 休眠态
3.I:空闲态
4.T:停止态
5.Z:僵尸态

1. <:优先级高进程
2. N:优先级低
3. l:该进程中包含线程
4. +:前台进程
5. s:会话首进程
4. ps -ajx
主要用于查看家族关系

PGID:进程组ID
SID:会话ID

5. top -htop

动态查看进程信息:主要查看进程CPU占用率

6. jobs

查看用户后台进程列表
ctrl + z :会将前台运行的进程暂停保存到后台
fg:会将后台暂停的进程恢复到前台运行
fg + %序列号:将指定的后台暂停程序恢复到前台运行

bg:会将后台暂停程序在后台运行
bg + %序列号:制动哪一个进程
可执行程序名 + &:将进程运行在后台

3.6 创建进程:fork

头文件:#include <sys/types.h>
       #include <unistd.h>

原型:pid_t fork(void);
功能:创建一个子进程
参数:无
返回值:
    成功:返回给父进程子进程的ID号,返回给子进程 0
    失败返回-1;
    fork:失败的条件只有一个,内存不足。

在这里插入图片描述
在这里插入图片描述

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>       
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
    int fd = open("./1.txt",O_RDWR|O_CREAT,0666);
    if(-1 == fd)
    {
        perror("open");
        return -1;
    }
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        printf("我是子进程\n");
        write(fd,"hello world",11);
    }
    else if(pid > 0)
    {
        printf("我是父进程,我的子进程ID号为%d\n",pid);
        char buf[123] = {0};
        sleep(1);
        lseek(fd,0,SEEK_SET);
        //close(fd);
        //fd = open("./1.txt",O_RDONLY);
        read(fd,buf,sizeof(buf));
        printf("buf = %s\n",buf);
    }
    return 0;
}

3.7 写时拷贝

fork函数创建子进程时要复制父进程的资源,但是子进程并不一定会用到这些资源,
所以说:
采用一种方式:当创建完子进程后,子进程先共享父进程的资源,如果双方有一方去修改内容,修改之前先复制一份到子进程中,这就叫做写时拷贝技术。

3.8 文件共享

如果fork之间打开了一些文件,获取了一些文件描述符,那么fork之后父进程和子进程公用这些fork之前获取的文件描述符。那么在操作过程中,有可能造成读写指针相互影响。
12345 6789

3.9 获取进程ID接口

3.9.1 getpid

头文件:#include <sys/types.h>
       #include <unistd.h>


原型:pid_t getpid(void);
功能:获取自己的进程ID
参数:无
返回值:
    成功:返回自己的进程ID
    无失败

3.9.2 getppid

头文件:#include <sys/types.h>
       #include <unistd.h>


原型:pid_t getppid(void);
功能:获取父进程的进程ID
参数:无
返回值:
    成功:返回父进程ID
    无失败
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>


int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(0 == pid)
    {
        printf("我是子进程\n");
        printf("我是子进程,我的进程号为:%d\n",getpid());
        printf("我是子进程,我的父进程号为:%d\n",getppid());
    }
    else if(pid > 0)
    {
        sleep(1);
        printf("我是父进程\n");
        printf("我是父进程,我的子进程号为:%d\n",pid);
        printf("我是父进程,我的进程号为:%d\n",getpid());
        printf("我是父进程,我的父进程号为:%d\n",getppid());        
    }
    return 0;
}

3.10 三个结束进程的函数

exit
头文件:#include <stdlib.h>

原型:void exit(int status);
功能:结束一个进程,先释放缓冲区
参数:status:结束进程时的状态,同return 正常结束用0,非正常结束用-1
返回值:
     无
_exit
头文件:#include <unistd.h>

原型:void _exit(int status);
功能:结束一个进程,不会释放缓冲区,直接结束
参数:status:结束进程时的状态,同return 正常结束用0,非正常结束用-1
返回值:
     无
atexit
头文件:#include <stdlib.h>

原型:int atexit(void (*function)(void));
功能:注册一个进程结束后的运行函数
    (当进程结束时会调用function这个函数)
参数:指向返回值为void 类型参数为void的函数指针
返回值:
     成功返回0
     失败返回非0值
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void fun(void)
{
    printf("hello world!\n");
}
int main(int argc, char const *argv[])
{
    //开始注册函数
    atexit(fun);
    sleep(3);
    return 0;
}

3.11 孤儿进程:

父进程优先于子进程结束,子进程失去父亲后,子进程会认1号进程是自己的父进程。那么1号进程负责回收和管理子进程。如果说很多子进程都人1号进程为他的父进程,1号进程的负担会很大,所以我们在编写代码的时候,尽量让父进程回收完子进程资源之后再结束。孤儿进程是没有危害的。

3.12 僵尸进程

子进程优先于父进程结束,子进程会认为该父进程会回收自己的资源,但是父进程没有回收子进程资源的功能,或者说父进程一直在忙于自己的事情,未曾去回收子进程资源,子进程资源就得不到回收,但是子进程任务已经结束了,所以说子进程就变成了僵尸进程,僵尸是有危害。注意:以后在写
fork时都需要回收子进程资源。

#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[])
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        exit(0);    //子进程结束,父进程未结束,子进程就变成了僵尸进程
    }
    else if(pid > 0)
    {
        while(1)
        {
            sleep(1);
        }
    }
    return 0;
}

3.12.1 处理僵尸进程

wait
头文件:#include <sys/wait.h>
原型:pid_t wait(int *stat_loc);
功能:阻塞等待回收任意一个子进程资源
参数:stat_loc:进程结束的状态(一般不考虑进程结束的状态直接写NULL)
返回值:
     成功会返回接受到的进程的ID号
     失败返回-1
注意:如果父进程调用wait来回收资源,那么会阻塞等待,会降低父进程的工作效率
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>       
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        exit(0);    //子进程结束,父进程未结束,子进程就变成了僵尸进程
    }
    else if(pid > 0)
    {
        printf("父进程运行中...\n");
        sleep(5);
        printf("接受成功,接受到子进程的ID号为%d\n",wait(NULL));
        sleep(5);
    }
    return 0;
}
waitpid
头文件:#include <sys/types.h>
       #include <sys/wait.h>
原型:pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收子进程资源,可以不阻塞回收也可以指定回收哪一个
参数:
    pid:
    pid == -1:回收任意一个子进程,如果采用阻塞方式与wait一样
    pid == 0: 回收同进程组中的任意一个子进程
    pid < -1;回收同进程组中,等于pid绝对值的子进程
        注意: -:代表同组,对pid进行取绝对值,|pid| == 正数
    
    wstatus:回收到的子进程结束时返回的状态
    options:操作方式
        0:阻塞回收
        WNOHANG:非阻塞方式回收(如果去回收,没有紫禁城结束,立马返回,如果说已经有子进程结束,
        立马回收)              
返回值:
     成功会返回接受到的进程的ID号
     失败返回-1
注意:需要频繁的去调用函数去查看子进程结束与否
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>       
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        sleep(5);
        exit(0);    //子进程结束,父进程未结束,子进程就变成了僵尸进程
    }
    else if(pid > 0)
    {
        pid_t pid1;
        while(1)
        {
            printf("父进程运行中...\n");
            if(0 < (pid1 = waitpid(-1,NULL,WNOHANG)))
            {
                printf("会受到子进程资源,子进程的ID号为: %d\n",pid1);
            }
            sleep(1);
        }
    }
    return 0;
}

3.13 守护进程

是一种特殊的进程机制,默默地对我的工作进行服务的进程。是一个后台进程。init进程就是一个守护进程。后台进程:只允许向终端写入数据,不允许从终端读取数据。如果一旦对终端输入进行获取,那么就会立即终止后台进程。要脱离终端的管理,脱离回话的管理。守护进程一般用于:服务器,http,tftp,ftp。
附加:ctrl + c给前台所有的进程发一个终止信号
创建守护进程是有固定步骤的:

  1. 变成孤儿进程 kill:给进程发一个信号
  2. 创建一个新的会话,变成会首进程,setsid
头文件:#include <sys/types.h>
       #include <unistd.h>
原型: pid_t setsid(void);
功能:创建一个新的会话,并且创建者称为会话的首进程
参数:无
返回值:
     成功会返回一个新的会话ID号
     失败返回-1
  1. 修改默认工作目录文件 chdir
    头文件:#include <unistd.h>
    原型: int chdir(const char *path);
    功能:修改工作路径
    参数:路径
    返回值:
    成功会返回0
    失败返回-1
  2. 给与最高文件权限 umask
  3. 关闭文件描述符 close(0);注意:如果说也不会对终端进行操作,请关闭所有的文件描述符
  4. 开始做守护事件,如日志文件的写入。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>       
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        //子进程
        //创建一个会话,自己成为自己的主人
        if(-1 == setsid())
        {
            perror("setsid");
            return -1;
        }
        //修改工作目录
        if(-1 == chdir("/"))
        {
            perror("chdir");
            return -1;
        }
        umask(0);
        close(0);
        close(1);
        close(2);
        FILE * fp = fopen("./1.txt","w+");
        while(1)
        {
            printf("11112\n");
            //写日志文件
            fprintf(fp,"helloworld\n");
            fflush(fp);
            sleep(1);
        }
    }
    //不去管父进程,父进程会自动结束
    return 0;
}

练习:使用两个进程共同去完成复制一个文件,将一个文件拷贝到另一个文件中

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


int main(int argc, char const *argv[])
{
    //使用文件IO打开文件,获取文件大小
    int fd = open(argv[1],O_RDONLY);
    if(-1 == fd)
    {
        perror("open");
        return -1;
    }
    int fd1 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(-1 == fd1)
    {
        perror("open1");
        return -1;
    }
    //获取文件大小,利用偏移量
    int len = lseek(fd,0,SEEK_END);
    printf("len = %d\n",len);
    //需要一个从开头,一个从中间开始获取
    len = len / 2;
    pid_t pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return -1;
    }
    if(pid == 0)
    {
        lseek(fd,len,SEEK_SET);
        lseek(fd1,len,SEEK_SET);
        char buf[123] = {0};  //接收读到的输入,写入数据时使用
        ssize_t ret  =0;
        while((ret = read(fd,buf,123)))
        {
            if(-1 == write(fd1,buf,ret))
            {
                perror("write");
                return -1;
            }
        }
        close(fd);
        close(fd1);
    }
    else if(pid > 0)
    {
        //父进程 ----》从头开始
        lseek(fd,0,SEEK_SET);
        lseek(fd1,0,SEEK_SET);
        char buf[123] = {0};
        ssize_t ret = 0;
        while(len)
        {
            if(len >= 123)
            {
                ret = read(fd,buf,123);
            }
            else
            {
                ret = read(fd,buf,len);
            }
            write(fd1,buf,ret);
            len = len - ret;
        }
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周末不下雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值