WEBSERVER02

程序和进程

进程是正在运行的程序的实例,进程由用户内存空间和一系列内核数据结构组成

程序可以创建多个进程

单道程序,计算机内存只允许一个程序运行

多道程序 同时存在几道相互独立的程序

时间片,时间片是由操作系统内核调度程序分配给每个进程,内核会给每个进程分配相等的初始时间片,每个进程轮番执行相应的时间

并行同一个时刻,有多条指令在多个处理器上同时执行

并发统一时刻只能一条指令执行,多个进程指令被快速的轮换执行

进程管理块 PCB

进程ID 进程的状态 进程切换时需要保存恢复的cpu寄存器 描述虚拟地址空间的信息 描述控制终端的信息

进程状态转换

就绪态运行态阻塞态

进程相关命令

查看进程

ps aux/ajx

PPID父进程 PID PGID进程组  SID会话

实时显示进程的动态

top

杀死进程

进程创建

系统允许一个进程创建新进程,新进程是子进程,子进程还可以创建新的子进程形成进程树结构模型 

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

       pid_t fork(void);
//成功,返回值返回两次,子进程返回0,父进程返回子进程ID
在父进程中返回-1表示创建失败,设置errno

*/
 #include <sys/types.h>
       #include <unistd.h>
       #include<stdio.h>
int main(){
    //创建子进程
     pid_t pid=fork();
     //判断是父进程还是子进程
     if(pid>0){
        printf ("pid:%d\n",pid);
   printf("i am parent pid : %d,ppid :%d",getpid(),getppid());
        
     }
     else if(pid==0){
         printf("i am child pid : %d,ppid :%d",getpid(),getppid());

     }
     for (int i=0;i<5;i++){
        printf("i:%d\n",i);
        sleep(1);
     }
     return 0;
}
      

fork之后虚拟地址空间会被复制过来pid会变、

fork使用写时拷贝,内核此时并不复制整个进程的地址空间,而是共享

只需要在写入的时候才会复制地址空间,从而使各个拥有自己的地址空间

资源的复制在写入的时候进行,在此之前,只有以只读的方式共享

fork产生的子进程与父进程相同的文件文件描述符指向相同的文件夹,引用计数增加

父子进程的关系

fork函数返回值不同

PCB的一些数据,进程ID PID ,当前进程的父进程ID PPID 信号集

共同点 在某些状态下,子进程还没有被创建出来,还没有执行任何写数据的操作,

-用户区的数据-文件描述符表

父子进程对变量是不是共享

刚开始的时候是一样的如果修改了数据的,不共享了

读时共享(子进程被创建) 写时共享(子进程被创建,两个进程没有做任何写的操作

GDB调试

默认只能跟踪一个进程,默认跟踪父进程

exec函数族

exec函数族的作用时根据指定的文件名找到可执行文件,并用他来取代调用进程的内容,在调用进程内部执行一个可执行文件

执行成功之后不会返回,调用进程的实体已经被新的内容取代,只有调用失败,它们才会返回-1

#include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...
          参数Path需要指定执行的路径或者名称推荐绝对路径
          arg 执行文件所需要的参数列表
          第一个参数没有任何作用,为了方便,一般写的时执行程序的名称
          第二个参数开始,程序执行所需要的参数列表
          NULL结束

          返回值
          调用失败才有返回值-1 设置errno
          成功没有返回值
           */

 #include <unistd.h>
 #include<stdio.h>
 int main(){
    pid_t pid=fork();
    if(pid>0){
    printf("i am parent pid:%d\n",getpid());
    }else if (pid==0){
        execl("hello","hello",NULL);//执行hello的内容
        //execl("/bin/ps", "ps", "aux", NULL);
        perror("execl");
        printf("i am child process, pid : %d\n", getpid());//不会执行
    }
      for (int i=0;i<5;i++){
        printf("i:%d,pid=%d\n",i,getpid());//父进程
        sleep(1);
     }
    return 0;
 }
i am parent pid:40808
i:0,pid=40808
hello, world
i:1,pid=40808
i:2,pid=40808
i:3,pid=40808
i:4,pid=40808

execlp会在环境变量中查找

execv argv是需要的参数的一个shi

char *argv[]={"ps","aux",NULL};

execv("/bin/ps",argv);

进程控制

status进程退出的状态信息,父进程回收子进程资源的时候可以查看

printf后面带\n,可以自己刷新IO缓冲区 

没有/N,数据写在缓冲区里,系统调用_exit,所以不打印;但是exit会刷新,所以打印

孤儿进程

父进程运行结束,子进程还在运行。出现孤儿进程的时候,设置父进程为Init,ppid是1,会循环wait一斤推出的子进程。当一个孤儿进程结束生命周期的时候 ,init会收尾

僵尸进程

每个进程结束之后会释放自己用户区的数据,内核区的PCB没有办法自己释放,需要父进程自己的释放,进程结束之后需要父进程区释放。结束之后父进程尚未回收,PCB存放于内核中,编程僵尸进程,不能被KILL -9杀死。进程号会一直被占用,这是僵尸进程的危害

进程回收


每个进程退出的时候,内核释放该进程所有的资源,父进程可以调用,得到退出状态同时彻底清除wait waitpid功能一样,wait会阻塞,waitpid可以设置不阻塞,waitpid还可以指定等待哪个子进程结束,每次只能清理一个子进程,循环多次清理

管道读写特点

使用管道时,需要注意以下几种特殊的情况(假设阻塞I/O操作

1.所有指向管道写端的文件描述符都关闭了,管道写端引用计数为0,进程从管道的读端读数据,那么管道剩余的数据被读取之后,再次read会返回0,就像读到文件末尾一样

2.如果有指向管道写端的文件描述符没有关闭,管道写端引用计数大于0,而持有管道写也没有往管道写入数据,有时候有进程从管道读取数据,那么管道中剩余的数据被读取之后再次read会阻塞,直到管道有数据可以读了才读取数据并返回

3.如果所有指向管道读端的文件描述符被关闭了,管道读端引用计数大于0 ,这个时候有向管道中写入数据,那么该进程会收到一个信号SIGPIPE,通常会导致进程异常停止

4.如果有指向管道读端的文件描述符没有关闭,管道读端引用计数大于0,而持有管道的进程也没有在管道读数据,这时有进程向管道写入数据,那么在管道被写满的时候再次write直到管道有空位置才能再次写入数据并返回

总结

读管道

管道中有数据,read返回实际读到的字节数

管道无数据:

    写端被全部关闭,read返回0,相当于读到文件的末尾

    写端没有完全关闭,read会阻塞

写管道

管道读端全部被关闭,进程异常终止,进程收到SIGPIPE信号

管道读端没有全部关闭,

      管道已满,write阻塞

      管道没有满,write将数据写入,并返回实际写入的字节数

让读变成非阻塞

/*
    #include <unistd.h>
    int pipe(int pipefd[2]);
        功能:创建一个匿名管道,用来进程间通信。
        参数:int pipefd[2] 这个数组是一个传出参数。
           
           pipefd[0] 对应的是管道的读端
            pipefd[1] 对应的是管道的 写端
        返回值:
            成功 0
            失败 -1

    管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞

    注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
/*
设置管道非阻塞
fcntl(fd[0],F_GETFL);
flags|=O_NONBLOCK;
fcntl(fd[0],F_SETFL,flags);
*/
// 子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

int main() {

    // 在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }

    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        // 父进程
        printf("i am parent process, pid : %d\n", getpid());

        // 关闭写端
        close(pipefd[1]);
        
        // 从管道的读取端读取数据
        char buf[1024] = {0};
        
      int flags=fcntl(pipefd[0],F_GETFL);
      flags|=O_NONBLOCK;
      fcntl(pipefd[0],F_SETFL,flags);
        while(1) {
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("len :%d\n",len);
            printf("parent recv : %s, pid : %d\n", buf, getpid());
            memset(buf,0,1024);
            sleep(1);
        }

    } else if(pid == 0){
        // 子进程
        printf("i am child process, pid : %d\n", getpid());
        // 关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while(1) {
            // 向管道中写入数据
            char * str = "hello,i am child";
            write(pipefd[1], str, strlen(str));
           sleep(5);
        }
        
    }
    return 0;
}


有名管道

提供了一个路径名与之关联,以FIFO文件的形式存在于文件系统,并且其打开方式与打开一个普通文件是一样的。与管道一样,FIFO也有一个写入端和读取段,并且从管道中读取数据的顺序与写入的顺序是一样的,FIFO的名称也由此而来,先入先出

不同点

1.FIFO作为一个特殊问价存在,FIFO中的内容却存放于内存中

2.当使用FIFO的进程退出后,FIFO文件继续保存在文件系统中以便以后使用

3.FIFO有名字,不相关的进程可以通过有名管道进行通信

使用

1.命令创建mkfifo

2.常见的文件I/O函数都可以用于FIFO,如close,read,write,

3.FIFO严格遵循先进先出,对管道及FIFO的读总是从开始处返回数据,对他们的写则是把数据添加到末尾,它们不支持lseek等文件定位操作

注意事项
//先打开写,输出管道不存在,创建管道,直到读端打开,才写入数据
当读端关闭,停止写入数据
读同理
#include <sys/types.h>
 #include <sys/stat.h>
 #include<stdio.h>
 #include<stdlib.h>
#include<string.h>
 #include<unistd.h>
#include <fcntl.h>
 int main ()
 {
    //以只写的方式打开数据
    int fd = open("test",O_RDONLY);
    if(fd == -1){
        perror("open");
        exit(0);
    }
    while(1){
        char buf[1024]={0};
        int len =read(fd,buf,sizeof(buf));
        if(len==0){
            printf("写端断开\n");
                   break;
        }
        printf("recv buf : %s\n",buf);
    }
    close(fd);
     return 0;
 }
#include <sys/types.h>
 #include <sys/stat.h>
 #include<stdio.h>
 #include<stdlib.h>
#include<string.h>
 #include<unistd.h>
#include <fcntl.h>
 int main ()
 {//1.文件是否存在
    int ret=access("test",F_OK);
    if(ret==-1){
       printf("管道不存在创建管道\n") ;
       //创建管道
    
       ret =mkfifo("test",0664);
       if(ret==-1){
    perror("mkfifo");
    exit(0);
} }
    //以只写的方式打开数据
    int fd = open("test",O_WRONLY);
    if(fd == -1){
        perror("open");
        exit(0);
    }
    //写数据
    for(int i=0 ; i<100 ; i++){
        char buf[1024];
        sprintf(buf, "hello,%d\n", i);
        printf("write data :%s\n", buf);
        write(fd,buf,strlen(buf));
        sleep(1);
    }
     close(fd);
     return 0;
   

 }

1.一个只读而打开一个管道的进程会阻塞,直到另一个进程为只写打开管道

2.一个只写而打开一个管道的进程会阻塞,直到另一个进程为只读打开管道

读管道 

管道有数据,read返回实际读到的字节数

管道无数据:

    写端被全部关闭,read返回0,相当于读到文件的末尾

    写端没有完全关闭,read会阻塞

写管道

管道读端全部被关闭,进程异常终止,进程收到SIGPIPE信号

管道读端没有全部关闭,

      管道已满,write阻塞

      管道没有满,write将数据写入,并返回实际写入的字节数

用有名管道实现聊天的功能

进程A1.只写的方式打开fifo1 2.只读的方式打开fifo2 3.循环读写 获取键盘录入fgets数据写fifo1读fifo2

进程B 1.只读的方式打开fifo1 2.只写的方式打开fifo2 3.循环读写 读fifo1 获取 写fifo2

只能A一条B一条

内存映射

将磁盘文件的数据映射到内存当中,通过修改内存就能修改磁盘文件

进程通信

/*
 #include <sys/mman.h>
       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
                  将一个文件或者设备映射到内存当中
                  void *addr:NULL,有内核决定
                length 要映射的数据的长度,这个值不能为0,,建议使用文件的长度
                  stat lseek获取
                prot对申请的内存映射区的操作权限
                  PROT_NONE  没有权限
                  PROT_EXEC  可执行
                  PROT_READ  读
                  PROT_WRITE 写
                  要操作映射内存,必须要有读的权限,
                  读写一起可以按位或
                flags
                    MAP_SHARED 映射区的数据会自动和磁盘文件同步,进程间通信,必须要有这个
                    MAP_PRIVATE 不同步,内存映射区的数据改变了,对原来的文件不会改变,会重新创建一个新的文件copy on write类似fork
                fd文件描述符,文件大小不能为0,open指定的权限不能和Prot参数有冲突 Prot的权限小于等于open
                offset偏移量,一般不用,4K的整数倍
                   0表示不偏移
                返回值:返回内存的首地址
                   如果错误返回-1
          int munmap(void *addr, size_t length);
                释放内存映射
                参数
                addr:要释放的内存首地址
                length:要释放的内存大小,要和mmap的length参数的值一样

        

*/ 
/*
使用内存映射实现进程通信
1,有关系的进程(父子进程
 还没有子进程的时候
 通过唯一的父进程,先创建内存映射区
 有了内存映射区以后,创建子进程
 父子进程共享创建的内存映射区
 2.没有关系的进程
 准备一个文件大小不是0 的磁盘文件
 进程1 通过磁盘文件创建内存映射区
 得到一个操作这块内存的指针
 进程2 通过磁盘文件创建文件映射区
 得到一个操作这块内存的指针
 使用内存映射区通信
 内存映射区通信非阻塞
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main (){
    //1.打开一个文件
    int fd=open("test.txt",O_RDWR);
    int size= lseek(fd,0,SEEK_END);
    //创建内存映射区
   void *ptr= mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
   if(ptr==MAP_FAILED){
    perror("mmap");
    exit(0);
   }
   //创建子进程
   pid_t pid=fork();
   if(pid>0){   char buf[64];
    wait(NULL); 
     strcpy(buf,(char *)ptr);
    printf("read data: %s\n",buf);
    
   }else if(pid==0){
   strcpy((char *)ptr,"nihao a ,son!!!");
   }
  munmap(ptr,size);
    return 0;
}
//输出read data :nihao a ,son!!! Txt文件前面也加上这句话
注意事项

1.如果对mmap的返回值做++操作,munmap是否能成功

可以对其++操作,释放的时候错误,要保存地址

2.如果open的时O_RDONLY,mmap prot是读写

错误返回MAP_FAILED,建议一致

3.如果文件偏移量是1000

off,1024的整数倍,返回错误

4.mmap调用失败

第二个参数length=0;prot只有写权限,open和prot权限不一样

5.open的时候o_create创建一个新文件创建映射区

可以,文件大小不为0;对新的文件进行扩展,Lseek;truncate;
6.Mmap关闭文件描述符,对mmap映射有没有影响

映射区还存在,fd关闭没有影响

7.ptr越界操作有影响吗

越界操作的是非法内存——段错误

复制文件
/*
1.对原始的文件进行内存映射
2.新文件,扩展
3.新文件数据映射到内存
4.内存拷贝
5.释放资源
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main (){
    int fd=open("english.txt",O_RDWR);
    if(fd==-1){
        perror("open");
        exit(0);
    }

    int fd1=open("cpy.txt",O_RDWR|O_CREAT,0664);
     if(fd1==-1){
        perror("open");
        exit(0);
    }
    int size=lseek(fd,0,SEEK_END);
    truncate("cpy.txt",size);

    void *ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    void *ptr1=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
      if(ptr==MAP_FAILED){
    perror("mmap");
    exit(0);}
      if(ptr1==MAP_FAILED){
    perror("mmap");
    exit(0);
   }
   memcpy(ptr1,ptr,size);
   munmap(ptr,size);
    munmap(ptr1,size);
    close(fd);
    close(fd1);
    return 0;
}

信号

事件发生时对进程的通知机制,异步通信,在内核产生

1.对前台进程,用户特殊的终端符号来发送信号

2.硬件发生异常,或者引用无法访问的内存区域

3.系统状态发生变化

4.KILL命令,调用KILL函数

core文件,可以打开,查看错误信息

                           

/*
 #include <sys/types.h>
 #include <signal.h>
       给某个进程pid或者进程组发送某个信号
       Pid>0指定的进程
         -0当前的进程组
         =-1 每一个有权限接受信号的进程
         <-1 pid=某个进程组的id取反
       sig宏值,0表示不发送任何信号
       int kill(pid_t pid, int sig);

          
    #include <signal.h>
       int raise(int sig);
     kill(getppid(),9);父进程、
     kill(getpid(),9)
  
   #include <signal.h>
   功能给当前的进程发送信号
   sig
   返回值成功0 失败非0
       int raise(int sig);

    void abort(void)
    发送SIGABRT信号给当前的进程,杀死当前进程



*/
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <fcntl.h>
 #include <signal.h>
int main ()
{
  pid_t pid=fork();
  if(pid==0){
    int i=0;
    for(i=0;i<5;i++){
        printf("child process\n");
        sleep(1);
    }
  }else if(pid>0){
    printf("parent process\n");
    sleep(2);
    printf("kill child process\n");
    kill(pid,SIGINT);

  }
    return 0;
}
/*
 #include <unistd.h>

    unsigned int alarm(unsigned int seconds);
    设置定时器,函数调用开始倒计时,倒计时为0的时候,函数会给当前进程发送一个信号SIGALALRM
     seconds倒计时的时长,单位秒参数为0 ,定时器无效
       取消一个定时器alarm(0)
     返回值:
       之前有定时器,返回之前的倒计时剩余的时间
       没有定时器,返回0
     SIGALARM:默认终止当前的进程,每个进程都有有且唯一一个定时器
       alarm(10)返回0,
       过了一秒
       alarm(5)返回9

alarm(100)函数时不阻塞
实际时间=内核时间+用户时间+消耗时间
进行文件IO操作的时候比较费时间
定时器和进程状态无关,无论进程什么状态都会计时

*/
 #include <unistd.h>
 #include<stdio.h>
 int main(){
    int seconds=alarm(5);
    printf("seconds :%d\n",seconds);
    sleep(2);
    seconds=alarm(10);
  printf("seconds :%d\n",seconds);
  while(1){

  }
    return 0;
 }
信号集

多个信号组成的集合,sigset_t

PCB有两个重要信号集 阻塞信号集,未决信号集 内核使用位图机制实现,操作系统不允许我们直接对信号集进行位操作,自定义另外一个集合,借助信号集操作函数对PCB的两个信号集进行修改

未决是一种状态,指的时从信号的产生到信号被处理前的一段时间

阻塞是一个开关动作,指的是组织信号被处理,但不是阻止信号产生

信号的阻塞就是让系统暂时保留信号留待以后发送,由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞是暂时的,只是为了防止信号打破敏感的操作

阻塞和未决信号集

1.用户通过键盘ctrl+c,产生2号信号SIGINT(信号被创建

2.信号产生但是没有被处理(未决

    -在内核将所有没有被处理的信号存储在一个 集合中

    SIGINT信号状态被存储在第二个标志位上

    标志位的值是0,说明信号不是未决状态

    这个标志位是1,说明信号处于未决状态

3.这个未决状态的信号,需要被处理,处理之前需要和阻塞信号集比较,阻塞信号集默认不阻塞任何的信号,如果想要阻塞某些信号需要用户调用系统的API,阻塞信号的值是1,就被阻塞,是0的话,可以被处理

/*
对自定义信号集操作
int sigemptyset(sigset_t *set)
  -功能:清空信号集中的数据,信号几种的所有标志位清为0
  -参数:SET,传出参数,需要传出的信号集
  -返回值:成功返回0,失败返回-1
int sigaddset(sigset_t *set,int signum)
 -功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
 -参数:SET,传出参数,需要传出的信号集
 -signum需要设置阻塞的信号
 -返回值:成功返回0,失败返回-1
int sigdelset(sigset_t *set,int signum)
 -功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
 -参数:SET,传出参数,需要传出的信号集
 -signum需要设置不阻塞的信号
 -返回值:成功返回0,失败返回-1
int sigimember(const sigset_t *set,int signum)
判断某个信号是否阻塞
signum需要判断的吸纳后
返回1,signum被阻塞
返回0,signum不阻塞
返回-1,调用失败
*/
#include <stdio.h>
#include <signal.h>
int main(){
    //创建信号集
    sigset_t set;
    sigemptyset(set);
    //sigint
   int  ret=sigimember(&set,SIGINT);
   if(ret==0){
    printf("SIGINT 不阻塞\n");
    else if(ret==1)
     printf("SIGINT 阻塞\n");
   }
   //添加
   sigaddset(&set,SIGINT);
   sigaddset(&set,SIGQUIT);
   ret=sigimember(&set,SIGINT);
   if(ret==0){
    printf("SIGINT 不阻塞\n");
    else if(ret==1)
     printf("SIGINT 阻塞\n");
   }
    sigdelset(&set,SIGQUIT);
    return 0;
}
/*
  #include <signal.h>
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
       功能:就自定义信号集中的数据设置到内核信号集中,设置阻塞,解除阻塞,替换
       参数
       how:如何将内核阻塞信号集进行处理
          SIG_BLOCK:将用户设置的阻塞信号集添加到内核中内核原来的数据不变
            假设内核more阻塞信号集是mask,mask|set
          SIG_UBLOCK:根据用户设置的数据,对内核中的数据进行解除阻塞
            mask &=~set按位与反set
          SIG_SETMASK:覆盖内核中原来的值
        set:已经初始化的用户自定义的信号集
        old_value:保存设置之前的内核中的内核阻塞信号集的状态,可以是NULL
        返回值:
        成功:0
        失败:-1
        设置错误号:EFAULT/EINVAL
       int sigpending (sigset_t *set)
         获取内核中的未决信号集
         set 传出参数,保存内核中未决信号集的信息

         编写一个程序,将所有的常规信号未决状态打印到屏幕
         设置某些信号阻塞
    
*/
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main (){
    //设置2 3 信号阻塞
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);
    sigaddset(&set,SIGQUIT);
   int num=0;
    sigprocmask(SIG_BLOCK,&set,NULL);
    while(1){
        num++;
        sigset_t pendingset;//获取未决
        sigemptyset(&pendingset);
        sigpending(&pendingset);
       //遍历
       for(int i=0;i<31;i++){
        int ret =sigismember(&pendingset,i);
        if(ret==1){
            printf("1");
        }else  if(ret==0){
            printf("0");
        }
        else{
            perror("sigismember");
            exit(0);
        }
       }
       printf("\n");
       sleep(1);
       if(num==10){
        sigprocmask(SIG_UBLOCK,&set,NULL);
       }
       
    }
    return 0;
}
/* #include <signal.h>

       int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
        检查或者设置信号的处理,信号捕捉
        -signum:信号编号
        -act:捕捉信号之后的处理动作
        -oldact:NULL
        返回值
         struct sigaction {
               函数指针,指向的函数就是信号捕捉之后的处理函数
               void     (*sa_handler)(int);
               不常用
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号
               sigset_t   sa_mask;
               使用哪一个信号处理对捕捉的信号进行处理
               这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO,表示使用sigaction 
               int        sa_flags;
               不常用
               void     (*sa_restorer)(void);

*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
void myalarm(int num){
  printf("捕捉到了信号的编号是%d\n",num);
  printf("xxxxxxx\n");
}


int main(){
    //注册信号捕捉
    struct sigaction act;
    act.sa_flags=0;//用sa_handler处理
    act.sa_handler = myalarm;
    sigemptyset(&act.sa_mask);//
    sigaction(SIGALRM,&act,NULL);
    struct itimerval new_value;
    //间隔时间
    new_value.it_interval.tv_sec=2;
    new_value.it_interval.tv_usec=0;
   //定时时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec=3;
    new_value.it_value.tv_usec=0;
    int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
    //直接结束了,三秒之后
    printf("定时器开始了\n");
    if(ret==-1){
        perror("setitimer");
        exit(0);
    }
    while(1);
    return 0;
}

sigaction常用,signal不常用

系统有一个阻塞信号集,在信号捕捉的时候会使用临时的阻塞信号集,处理完之后会恢复到阻塞信号集

多次发送某个信号,只会处理当前一个,其他的信号会阻塞在那

未决信号和阻塞信号集只支持一个标志位

/*
条件
子进程结束,暂停,继续运行
解决僵尸进程

*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
void myfun (int num){
    printf("捕捉到的信号%d\n",num);
    //回收子进程的位置
   while(1){
    int ret=waitpid(-1,NULL,WNOHANG);
    if(ret>0){
        printf("child die,pid =%d\n",ret);
    }else if(ret==0){
        break;
    }else if(ret==-1){
        break;

    }
   }
   
}
int main(){
    //提前设置好阻塞信号集,阻塞sigchld,
    //因为有可能子进程很快结束,父进程还有注册完信号捕捉
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,SIGCHLD);
    sigprocmask(SIG_BLOCK,&set,NULL);
    pid_t pid;
    //创建子进程
    for(int i=0;i<20;i++){
        pid=fork();
        if(pid==0){
            break;
        }
    }
    if(pid>0){
        //捕捉子进程死亡的sigchld
        struct sigaction act;
        act.sa_flags=0;
        act.sa_handler=myfun;
        sigemptyset(&act.sa_mask);
        sigaction(SIGCHLD,&act,NULL);
        sigprocmask(SIG_UNBLOCK,&set,NULL);
        while(1){
            printf("parent process pid:%d\n",getpid());
            sleep(2);

        }} else if(pid==0){
            printf("child process pid :%d\n",getpid());
        }
        return 0;
    }
共享内存

一个写数据,一个读数据

/*
#include <sys/ipc.h>
#include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
    功能创建一个新的共享内存端,或者获取一个既有的共享内存段的标识
    新创建的内存段中的数据都会被初始化为0
    参数
    -Key key_t类型是一个整形,通过这个或者找到创建一个共享内粗
        16进程 非0
    - size 共享内存的大小
    - shmflg属性
        -访问权限
        -附加属性,创建、判断共享内存是不是存在
            创建IPC_CREAT
            判断共享内存是否存在,IPC_EXCL,需要和IPC_CREAT一起使用
               IPC_CREAT|IPC_EXCL|0064
    -返回值失败-1,成功》0,返回共享引用的ID,后面操作只用

 void *shmat(int shmid, const void *shmaddr, int shmflg);
   -功能和当前的进程guanlian
   - shmid共享内存的标识Id
   - shmaddr申请的共享内存的起始地址,NULL内核指定
   - shmflg对共享内存的操作
       SHM_RDONLY必须要有读权限
       读写:0
    - 返回值
       成功返回首地址
       失败(void *)-1


 void *shmat(int shmid, const void *shmaddr, int shmflg);
    -功能:解除当前进程和共享内存的关联
    -参数
     shmaddr首地址
    -返回值,成功0,失败-1
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    -删除共享内存,共享内存要删除才消失,创建共享内存的链接就会消失
    -shmidid
    cmd要做的操作
      IPC_STAT 获取共享内存的当前的状态
      IPC_SET 设置共享内存的状态
      IPC_RMID 标记共享内存被销毁
    -buf 需要设置或者获取的共享内存属性信息
      IPC_STAT buf存储数据
      IPC_SET buf需要初始化数据,设置到内核中
      IPC_RMID 没用NULL
key_t ftok(const char *pathname,int proj_id); 
根据指定的路径名和Int值,生成一个共享内存的Key 
pathname路径
proj_id int这系统调用只使用其中的一个字节
范围0-255 一般指定一个字符'a'
*/
//写数据
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main() {    

    // 1.获取一个共享内存
    int shmid = shmget(100, 0, IPC_CREAT);
    printf("shmid : %d\n", shmid);

    // 2.和当前进程进行关联
    void * ptr = shmat(shmid, NULL, 0);

    // 3.读数据
    printf("%s\n", (char *)ptr);
    
    printf("按任意键继续\n");
    getchar();

    // 4.解除关联
    shmdt(ptr);

    // 5.删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main (){
    //1.获取共享内存
    int shmid=shmget(100,0,IPC_CREAT);
    printf("shmid:%d\n",shmid);
    //关联
    void *ptr=shmat(shmid,NULL,0);
    //3.读数据
   printf("%s\n",(char *)ptr);
   //4.解除关联
 
   printf("按任意键继续\n");
   getchar(); 
    shmdt(ptr);
   //5.删除共享内存
   shmctl(shmid,IPC_RMID,NULL);
    return 0;
}

1.操作系统如何知道共享内存被多少进程关联

共享内存维护了一个结构体streuct shmid_ds,这个结构体有个成员shm_nattach记录了关联的进程个数

2.可以对共享内存进行多次删除shmctl

可以,因为shmctl标记删除共享内存,不是直接删除

什么时候真正删除

当共享内训关联的进程数为0的时候,就被真正删除

当共享内存key为0,不爱书共享内存标记删除了

如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存

3.共享内存和内存映射的区别

-共享内存可以直接创建,内存映射需要哦磁盘文件,匿名映射除外

-共享内存效率更高

-内存

    所有的进程操作的是同一块共享内存

    内存映射,每个进程在自己的虚拟地址空间有一个独立的内存

-数据安全

   进程突然退出

    共享内存还存在,内存映射小时

    运行进程的电脑司机

   数据存在在共享内存没有了,

内存映射区的数据,由于磁盘文件数据旱灾,所以内存映射区的数据还在’

-生命周期

内存映射区,进程退出,内存映射区销毁

共享内存:进程退出,共享捏u才能还在,标记删除(所有的关联进程数为0 ,或者关机

如果一个进程退出,会自动和共享内存取消关联

守护进程

bash 去创建find进程组,wc是find子进程,ppid是父进程,pgid是进程组编号,sid是会话组,加了 &,在后端执行

创建sort,uniq是子进程,默认在前端执行,前端只能有一个进程组,享有控制终端

1.获取进程的组2.获取指定的进程组Id3.设置进程组5.设置会话Id

比如父进程id100,组id100,会话Id是100,子进程id100,不是当前会话id

子进程创建新会话,新会话Id是101,与之前会话不冲突没有控制终端

写数据到

/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写u人到磁盘文件中

*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include<time.h>

void work(int num){
    //捕捉到信号之后,获取系统时间,写入磁盘文件
    time_t tm=time(NULL);
    struct tm* loc=localtime(&tm);
    //char buf[1024];
    //sprintf(buf,"%d-%d-%d %d:%d:%d\n",loc->tm_year,loc.tm_mon,loc->tm_mday,loc->tm_hour,loc->tm_min,loc->tm_sec);
    char *str =asctime(loc);
    int fd=open("time.txt",O_RDWR|O_CREAT,0664);
    write(fd,str,strlen(str));
    close(fd);
}

int main (){
    pid_t pid=fork();
    if(pid>0){
        exit(0);
    }
   setsid();
   umask(022);
   chdir("/home");
   int fd=open("/dev/null",O_RDWR);
   dup2(fd,STDIN_FILENO);
   dup2(fd,STDOUT_FILENO);
   dup2(fd,STDERR_FILENO);

   struct sigaction act;
   act.sa_flags=0;
   act.sa_handler=work;//捕捉信号
   sigemptyset(&act.sa_mask);
    //业务逻辑
   sigaction(SIGALRM,&act,NULL);
   struct itimerval val;
   val.it_value.tv_sec=2;
   val.it_value.tv_usec=0;
   val.it_interval.tv_sec=2;
   val.it_interval.tv_usec=0;
   
  setitimer(ITIMER_REAL,&val,NULL);

   while(1){
    sleep(10);

}

    return 0;

}

/dev/null,会丢弃

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值