• C语言基础-进程相关


    进程的概念

    程序

    • 存放在磁盘上的 指令和数据的有序集合(文件)
    • 静态的

    进程

    • 执行一个程序所分配的资源总称
    • 进程是程序一次执行过程
    • 动态的,包括创建、调度、执行和消亡

     

    进程控制块(pcd)

    • 进程标识PID
    • 进程用户
    • 进程状态、优先级
    • 文件描述符表

    进程类型

    • 交互进程:在shell下启动。以在前台运行,也可以在后台运行
    • 批处理进程:和终端无关,被提交到一个作业队列中,以便顺序执行
    • 守护进程:和终端无关一直在后台运行

    进程状态

    • 运行态:
      • 正在运行
      • 准备运行
    • 等待态:进程在等待一个事件的发生或某种资源
      • 可终端
      • 不可中断
    • 停止态:进程被中止,收到信号后可继续运行
    • 死亡态:以终止的进程,但pcb没有被释放

    查看进程信息

    • ps 查看系统进程快照
    • top 查看进程动态信息   
    • /proc 查看进程详细信息

    修改进程优先级

    • nice 按用户指定的优先级运行进程
      • nice -n num ./test
      • num范围是 -20~19 越低优先级越高
      • 普通用户是没办法指定负数的优先级
    • renice 改变正在运行进程的优先级
      • renice -n num pid
      • 普通用户是没办法提高该优先级,只能进行降低

    进程相关

    • jobs 查看后台进程
    • bg 将挂起的进程在后台运行
      • bg id
      • id是jobs的后台进程编号
    • fg 吧后台运行的程序放到前台来运行
      • fg id
      • id 是jobs的后台进程编号
    • ctrl + z 将前台进程放到后台挂起

    C与进程相关函数

    父子进程

    • 子进程继承了父进程的内容
    • 父子进程有独立的地址空间,互不影响
    • 若父进程先结束
      • 子进程成为孤儿进程,被init进程收养
      • 子进程变成后台进程
    • 若子进程先结束
      • 父进程如果没有及时回收,子进程变成僵尸进程

    进程创建 - fork

    #include <unistd.h>
    pid_t fork(void);
    
    // 创建新的进程,失败返回-1
    // 成功时父进程返回子进程的进程号,子进程返回0

    创建完子进程后,从fork后下一条语句开始执行;

    父子进程的执行顺序不定

    父子进程可以多次调用fork

    获取当前进程 - getpid

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

    demo:创建子进程

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[]) {
        pid_t pid;
        if ((pid = fork()) < 0) {
            perror("fork");
            return -1;
        } else if (pid == 0) {
            printf("child process: my pid is :%d
    ",getpid());
        }else{
            printf("parent process :%d
    ",getpid());
        }
    }
    
    /* 输出:
    parent process :7124
    child process: my pid is :7125
    */

     

    结束进程

    #include <stdlib.h>
    void exit(int  status);
    
    #include <unistd.h>
    void _exit(intstatus);
    
    // 引入的头文件不同
    //*** exit结束进程时会刷新(流)的缓冲区
    // _exit结束时不会刷新缓冲区 可能会造成流文件丢失
    exit(0) // 退出当前程序

    进程资源回收

    • 子进程结束时由父进程回收
    • 孤儿进程由init进程回收
    • 若没有及时回收会出现僵尸进程
    #include <unistd.h>
    pid_t wait(int *status);
    
    // status指定保存子进程返回值和结束方式的地址
    // status为NULL表示直接释放子进程PCB,不接受返回值
    
    // 成功时返回回收的子进程号
    // 失败是返回EOF
    // 若子进程没有结束,父进程会一直阻塞
    // 若有多个子进程,那个先结束就先回收

    Demo:

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
        pid_t pid;
        int status = 0;
        if ((pid = fork()) < 0) {
            perror("fork");
            return -1;
        } else if (pid == 0) {
            printf("child process: my pid is :%d
    ", getpid());
            sleep(1);
            exit(2);
    
        } else {
            wait(&status);
            printf("%x
    ", status);
            printf("parent process :%d
    ", getpid());
        }
    }

     

    进程回收的另一种方式

    #include <unistd.h>
    pid_t waitpid(pid_t pid,int * status,int option);
    
    // pid可用于指定回收那个子进程或任意子进程
    // pid= -1 代表当前进程的任意子进程
    
    // status指定用于保存子进程返回值和结束方式的地址
    // option指定回收方式,0或WNOHANG 
    // 0表示阻塞一直等到进程结束
    // WNOHANG表示非阻塞
    
    // 成功时返回子进程的pid或0 0表示当前子进程还未结束
    // 失败时返回EOF

    进程返回值和结束方式

    • 子进程通过exit、_exit/return返回某个值(0~255)
    • 父进程调用wait(&status)回收

        WIFEXITED(status) // 判断子进程是否正常结束 0为false 1为true

        WEXITSTATUS(status)  // 获取子进程返回值

        WIFSIGNALED(status) // 判断子进程是否被信号结束

        WTERMSIG(status) // 获取结束子进程的信号类型

       

    守护进程

    • 守护进程(Deamon)是linux三种进程类型之一
    • 通常是在系统启动时运行,系统关闭时结束
    • linux系统中大量使用,很多服务程序以守护进程方式运行

    会话、控制终端

    • linux会话(session)、进程组的方式管理进程
    • 每个进程属于一个进程组
    • 会话是一个或多个进程组的集合。通常用户打开一个终端,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话
    • 终端关闭时,所有相关进程会被结束

    守护进程特点

    • 始终在后台运行
    • 独立于任何终端
    • 周期性的执行某种任务或等待处理特定事件

    守护进程创建

    • 创建子进程,父进程退出--子进程变为孤儿进程,被init进程收养;子进程在后台运行(还是会依附于当前终端)
    if (fork() > 0) {
        exit(0);
    }
    • 子进程创建新会话--子进程称为新的会话组长;脱离原先的终端
    • 更改当前工作目录--守护进程一直在后台运行,其工作目录不能被卸载一般指向根目录(/)或(tmp)
    chdir("/"); // 普通用户指向这个目录可读
    chdir("/tmp"); // 所有用户都是最高权限
    • 重设文件权限掩码--文件权限掩码设置为0;只影响当前进程
    #include <sys/stat.h>
    
    if (umask(0) < 0) {
        exit(-1);
    }
    • 关闭父进程打开的文件描述符--已脱离终端 stdin/stdout/stderr 无法再使用
    #include <zconf.h>
    
    for (int i = 0; i < getdtablesize(); ++i) {
        close(i);
    }

    Demo:每隔1秒写入时间到time.log

    #include <sys/stat.h>
    #include <zconf.h>
    
    int main(int argc, char *argv[]) {
        setsid();
        umask(0);
        chdir("/tmp");
        for (int i = 0; i < getdtablesize(); ++i) {
            close(i);
        }
        // ... 打开文件
        // ... 死循环写入日志
    }

    exec函数族

    • 进程调用exec函数族执行某个程序
    • 进程当前内容被指定的程序替换
    • 实现让父子进程执行不同程序
      • 父进程创建 子进程
      • 子进程调用exec函数族
      • 父进程不受影响

    通过shell命令去执行其他程序

    #include <unistd.h>
    int execl(const char *path,const  char*arg,...); /
    int execlp(const char *file,const char *arg,...);
    
    // 成功执行指定的程序
    // 失败返回EOF
    // path找到要执行程序的名称(要包含路径)
    // arg...传递给执行程序的参数表
    
    // file 执行程序的名称,在$PATH中查找

    Demo:

    if (execl("/bin/ls", "ls", "-a","/etc") < 0) {
            perror("excel");
        }
     if (execlp("ls", "ls", "-a","/etc") < 0) {
            perror("excel");
        }

     

    通过shell命令去执行其他程序 参数为数组

    #include <unistd.h>
    int execv(const char * path,char * const argv[]);
    int execvp(const char * file,char * const argv[]);
    
    // 成功时执行指定的成
    // 失败返回EOF
    // arg... 封装成指针数组 比上面exec更加灵活
    char *arg[] = {"ls", "-a", "/etc",NULL};
    if (execv("/bin/ls", arg) < 0) {
        perror("excel");
    }
    char *arg[] = {"ls", "-a", "/etc",NULL};
    if (execvp("ls", arg) < 0) {
        perror("excel");
    }

    通过shell命令去执行其他程序 字符串

    #include <stdlib.h>
    int system(const char * command);
    
    // 会自动创建一个子进程去执行command
    // 成功返回command指令的返回值
    // 失败返回EOF

     

    进程间通讯介绍

    • 早期UNIX进程间通信方式
      • 无名管道(pipe)
      • 有名管道(fifo)
      • 信号(signal)
    • System V IPC
      • 共享内存(share memory)
      • 消息队列(message queue)
      • 信号灯集(semaphore set)

     

    无名管道-pip

    无名管道特性

    无名管道使用范围,只能用于有亲缘关系的进程之间-因为无名管道没有实际的文件所对应,只能通过集成方式打开进行通讯

    无名管道是一个单工的,只能单方向传递数据

    无名管道的数据是存在在内存中,读取后则无

    无名管道创建

    #include <unistd.h>
    int pipe(int pfd[2]);
    
    // 成功返回0
    // 失败返回EOF
    // pfd包含两个元素的整型数组,用来保存文件描述符
    // pfd[0]用于读管道
    // pfd[1]用于写管道

    Demo:子进程1和子进程2分别往管道中写入字符串;父进程读取

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <string.h>
    
    
    int main(int argc, char *argv[]) {
        // 保存fork的返回值
        pid_t pid1, pid2;
        char buf[32];
        // 用于保存无名管道
        int pfd[2];
        // 创建无名进程
        if (pipe(pfd) < 0) {
            perror("pipe");
            return 0;
        }
        // 创建子进程
        if ((pid1 = fork()) < 0) {
            perror("pip1");
            return 0;
        } else if (pid1 == 0) {
            // pid1 == 0 子进程1 执行区
            strcpy(buf, "process1");
            write(pfd[1], buf, 32);
        } else {
            // 父进程执行区
            if ((pid2 = fork()) < 0) {
                perror("pip2");
                return 0;
            } else if (pid2 == 0) {
                // pid2 == 0 子进程2 执行区
                sleep(1);
                strcpy(buf, "process2");
                write(pfd[1], buf, 32);
            } else {
                // 父进程执行区
                // 等待回收任意子进程
                wait(NULL);
                read(pfd[0], buf, 32);
                printf("%s
    ", buf);
                // 等待回收任意子进程
                wait(NULL);
                read(pfd[0], buf, 32);
                printf("%s
    ", buf);
            }
        }
        return 0;
    }

    读无名管道

    写端存在

    • 有数据

    read函数返回实际读取的字节数

    • 无数据

    进程读阻塞

    写段不存在

    • 有数据

    read函数返回实际读取的字节数

    • 无数据

    read函数立即返回0

    读端存在

    • 有空间

    write返回实际写入的字节数

    • 无空间

    进程写阻塞

    读端不存在

    • 有空间

    系统异常结束(管道断裂)

    • 无空间

    系统异常结束(管道断裂)

    有名管道-fifo

    有名管道特点

    • 对应管道文件,可用于任意进程之间的通讯
    • 打开管道时可指定读写方式
    • 通过文件IO操作,内容存放在内存中

    只有读端和写端都存在的情况下才能正常运行否则会单方面阻塞

    有名管道的创建-mkfifo

    #include <unistd.h>
    #include <fcntl.h>
    int mkfifo(const char* path,mode_t mode);
    
    // 成功时返回0
    // 失败时返回EOF
    // path 创建的管道文件路径,如果没有就是默认在当前路径下
    // mode 管道文件的权限,如0666

    Demo:

    进程A:循环从键盘输入并写入有名管道myfifo,输入quit时推出

    进程B:循环统计进程A每次写入myfifo的字符串长度

    create.c

    #include <stdio.h>
    #include <sys/stat.h>
    
    // create fifo
    int main(void ){
        // 创建管道
        if (mkfifo("fifo",0666)<0){
            perror("mkfifo");
            return 0;
        }
    }

    write.c

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    int main(int argc, char *argv[]) {
        char buf[32];
        int pfd;
        // 以只写的方式打开管道
        if ((pfd = open("fifo", O_WRONLY)) < 0) {
            perror("mkfifo");
            return 0;
        }
        while (1) {
            // 循环写入
            fgets(buf, 32, stdin);
            // 如果输入quit则跳出
            if (strcmp(buf, "quit
    ") == 0) {
                break;
            }
            write(pfd, buf, 32);
        }
        // 关闭打开的管道
        close(pfd);
        return 0;
    }

    read.c

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    // read fifo
    int main() {
        char buf[32];
        int pfd;
        // 以只读方式打开管道
        if ((pfd = open("fifo", O_RDONLY)) < 0) {
            perror("mkfifo");
            return 0;
        }
        // 循环读
        while (read(pfd, buf, 32) > 0) {
            printf("the length of string is %d
    ", strlen(buf));
        }
        // 如果等于0说明该管道已经关闭了
        close(pfd);
        return 0;
    }

    信号机制

    • 信号是在软件层面上对中断机制的一种模拟,是一种异步通讯的方式
    • Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件
    • Linux对早期的unix信号机制进行了扩展
    • 进程对信号有不同的响应方式
      • 缺省方式
      • 忽略信号
      • 捕捉信号

    常用信号

    信号名 含义 默认操作
    SIGHUP 该信号在用户终端关闭时产生,通常是发给该终端关联的会话内的所有进程 终止
    SIGINT 该信号在用户键入INTR字符(Ctrl-c)时产生,内核发送此信号送到当前终端的所欲前台进程 终止
    SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来产生 终止
    SIGILL 该信号在一个进程企图执行一条非法执行时产生 终止
    SIGSEV 该信号在非法访问内存时产生,如野指针,缓冲区溢出 终止
    SIGPIPE 当进程往一个没有读端的管道中写入时产生,代表'管道断裂' 终止
    信号名 含义 默认操作
    SIGKILL 该信号用来结束进程,并且不能被捕捉和胡忽略 终止
    SIGTOP 该信号用于暂停,并且不能被捕捉和忽略 暂停进程
    SIGTSTP 该信号用户暂停进程,用户可以键入SUSP(ctrl-z) 暂停进程
    SIGCONT 该信号让进程进入运行态 继续运行
    SIGALRM 该信号用于通知进程定时器时间已到 终止
    SIGUSR1/2 该信号保留给用户程序使用 终止

    Signal.h定义信号的宏

    #define SIGHUP  1       /* hangup */
    #define SIGINT  2       /* interrupt */
    #define SIGQUIT 3       /* quit */
    #define SIGILL  4       /* illegal instruction (not reset when caught) */
    #define SIGTRAP 5       /* trace trap (not reset when caught) */
    #define SIGABRT 6       /* abort() */
    #if  (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE))
    #define SIGPOLL 7       /* pollable event ([XSR] generated, not supported) */
    #else   /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
    #define SIGIOT  SIGABRT /* compatibility */
    #define SIGEMT  7       /* EMT instruction */
    #endif  /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
    #define SIGFPE  8       /* floating point exception */
    #define SIGKILL 9       /* kill (cannot be caught or ignored) */
    #define SIGBUS  10      /* bus error */
    #define SIGSEGV 11      /* segmentation violation */
    #define SIGSYS  12      /* bad argument to system call */
    #define SIGPIPE 13      /* write on a pipe with no one to read it */
    #define SIGALRM 14      /* alarm clock */
    #define SIGTERM 15      /* software termination signal from kill */
    #define SIGURG  16      /* urgent condition on IO channel */
    #define SIGSTOP 17      /* sendable stop signal not from tty */
    #define SIGTSTP 18      /* stop signal from tty */
    #define SIGCONT 19      /* continue a stopped process */
    #define SIGCHLD 20      /* to parent on child stop or exit */
    #define SIGTTIN 21      /* to readers pgrp upon background tty read */
    #define SIGTTOU 22      /* like TTIN for output if (tp->t_local&LTOSTOP) */
    #if  (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
    #define SIGIO   23      /* input/output possible signal */
    #endif
    #define SIGXCPU 24      /* exceeded CPU time limit */
    #define SIGXFSZ 25      /* exceeded file size limit */
    #define SIGVTALRM 26    /* virtual time alarm */
    #define SIGPROF 27      /* profiling time alarm */
    #if  (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
    #define SIGWINCH 28     /* window size changes */
    #define SIGINFO 29      /* information request */
    #endif
    #define SIGUSR1 30      /* user defined signal 1 */
    #define SIGUSR2 31      /* user defined signal 2 */

     

     

    信号相关命令

    $ kill [-signal] pid

    • 默认发送SIGTERM
    • -sig可指定信号
    • pid指定发送对象
    $kill -9 1111 // 向pid为1111的进程发送关闭信号
    $kill -9 -1111 // 向1111进程组发送关闭信号
    $kill -9 -1 // -1表示除了init和当前进程之外所有进程发信号
    

      

    $ kill [-u user | prog]

    • prog 执行进程名
    • user 指定用户名

    信号发送-kill/raise

    #include <unistd.h>
    #include <signal.h>
    int kill(pid_t pid,int sig);
    int raise(int sig);
    
    // 成功时返回0
    // 失败时返回EOF
    // pid接受进程的进程号;0代表同组进程;-1代表所有进程
    // sig信号类型

    信号相关函数-alarm/pause

    设置定时器和暂停等待定时器

    Ps:alarm经常用于实现超时检测

    #include <unistd.h>
    #include <signal.h>
    
    int alarm(unsigned int seconds);
    // 成功时返回上一个定时器的剩余时间
    // 失败返回EOF
    // seconds 定时器的时间 如果是0则为取消该定时器
    // 一个进程中只能设定一个定时间,时间到时产生SIGALRM
    
    
    int pause(void);
    
    // 进程一直阻塞,知道被信号中断
    // 被信号中断返回-1
    // errno为EINTR

    Demo:

    #include <stdio.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[]) {
        // 设置定时器
        alarm(3);
        // 阻塞进程
        pause();
        // 直到定时器结束产生 SIGALRM pause被信号中断 实际上程序已经结束
        printf("pause over 
    "); // 这句话无法打印
        return 0;
    }

    设置信号响应方式-signal

    #include <unistd.h>
    #include <signal.h>
    
    void(*signal(int signo,void (*handler)(int)))(int);
    
    // signo 要设置的信号类型
    // (*handler)(int) 函数指针 传递函数类型;SIG_DFL代表缺省方式(系统的默认处理);SIG_IGN代表忽略该信号;如果传入该函数就是捕捉信号
    // 该返回值也是一个函数指针
    
    // 成功时返回原先的信号处理函数
    // 失败时返回SIG_ERR

    Demo:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/param.h>
    
    // 信号处理函数
    void handle(int signo) {
        if (signo == SIGINT) {
            puts("CATCH SIGINT");
        }
        if (signo == SIGQUIT) {
            puts("CATCH SIGQUIT");
        }
    }
    
    int main(int argc, char *argv[]) {
        // 设置信号响应方式
        signal(SIGINT, handle); // `ctrl - c`
        signal(SIGQUIT, handle); // `ctrl - \`
        while (1) pause();
        return 0;
    }

    System V IPC

    • IPC对象包括:对象内存、消息对列、信号灯集
    • 每一个IPC对象有唯一的ID
    • IPC对象创建后一直存在,直到被显示地删除
    • 每一个IPC对象有一个关联的KEY
    • ipcs/ipcrm shell命令

    IPCS、IPCRM

    $ipcs

    T     ID     KEY        MODE       OWNER    GROUP

    Message Queues:

     

    T     ID     KEY        MODE       OWNER    GROUP

    Shared Memory:

    m  65536 0x5104e14e --rw------- songzhibin    staff

     

    T     ID     KEY        MODE       OWNER    GROUP

    Semaphores:

    s  65536 0x0b046024 --ra-ra-ra-     root    wheel

    s  65537 0x0b04e111 --ra-ra-ra-    root    staff

    s  65538 0x25048769 --ra-ra-ra-    root    staff

    s  65539 0x5104e149 --ra-------    root    staff

    s  65540 0x5104e14f --ra-------    root    staff

    分别为消息队列、对象内存、信号等集

    每一个对象都有唯一的ID

    每一个IPC对象都有一个与之对应的KEY值 KEY值0代表是一个私有的,其他进程间不能通过KEY来访问

    $ipcrm  [-M | -Q | -S key] [-m | -q | -s id]

    // 可以通过key或者id来删除 IPC 

    IPC  KEY值生成-ftok

    #include <sys/types.h>
    #include <sys/ipc.h>
    
    key_t ftok(const char*path,int proj_id);
    
    // 成功返回一个合法的key值
    // 失败返回-1
    
    // path 存在且可以访问的文件路径
    // proj_id 用于生成key的数字,不能为0

    Ps:每个进程低啊用ftok的方式应该相同:path和proj_id参数值应该相同,否则找不到对应的IPC

    Demo:

    #include <stdio.h>
    #include <sys/ipc.h>
    
    int main(int argc, char *argv[]) {
        key_t key;
        // key值生成
        // 一般 proj_id 传入字符常量 
        if ((key = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
    }

    共享内存

    • 共享内存是一种最为高效的进程间通讯方式,进程可以直接读写内存,而不需要任何数据拷贝
    • 共享内存在内核空间创建,可以进程映射到用户访问空间,是用灵活
    • 由于多个进程可以同时访问共享内存,因此需要同步和互斥机制配合使用

     

    注意事项:

    • 每块共享内存的大小有限制
      • $ipcs -l
      • 修改 cat /proc/sys/kernel/shmmax
    • 共享内存删除的时间点
      • shmctl(shmid,IPC_RMID,NULL)添加删除标记
      • nattach变为0时真正删除 (nattach是映射的数目)

    共享内存使用步骤

    • 创建/打开共享内存
    • 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
    • 读写共享内存
    • 撤销共享内存映射
    • 删除共享内存对象

    共享内存的创建/打开-shmget

    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key,int size,int shmflg);
    
    // 成功时返回共享内存的id
    // 失败时返回EOF
    // key 和共享内存关联的key, IPC_PREIVATE或ftok生成 IPC_PREIVATE是零代表私有共享内存
    // size 共享内存的大小
    // shmflg 共享内存的标志位 IPC_CREAT|0666

    Demo:创建一个私有的共享内存,大小为512字节,权限为0666

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    int main(int argc, char *argv[]) {
        int shmid;
        // 如果私有的一定是新建的 不用指定 IPC_CREAT|0666
        if ((shmid = shmget(IPC_PRIVATE, 512, 0666)) < 0) {
            perror("shmget");
            return 0;
        }
    }

    Demo:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    key_t keyid;
    int shmid;
    
    int main(int argc, char *argv[]) {
        // 生成共享内存的key
        if ((keyid = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
        // 创建/打开共享内存
        // 如果共享内存不存在则创建,如果存在则打开
        if ((shmid = shmget(keyid, 1024, IPC_CREAT | 0666))<0) {
            perror("shmget");
            return 0;
        }
    }

    共享内存映射-shmat

    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    void *shmat(int shmid,const void *shmaddr,int shmflg);
    
    // 成功时返回映射后的地址
    // 失败时返回(void *) -1
    
    // shmid 要映射的共享内存的id
    // shmaddr 映射后的地址,如果传NULL表示系统自动映射
    // shmflg 标志位 0表示可读可写;SHM_RDONLY表示只读

    Demo:通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型 下面以字符串为例

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    key_t keyid;
    int shmid;
    char *addr;
    
    int main(int argc, char *argv[]) {
        // 生成共享内存的key
        if ((keyid = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
        // 创建/打开共享内存
        // 如果共享内存不存在则创建,如果存在则打开
        if ((shmid = shmget(keyid, 1024, IPC_CREAT | 0666)) < 0) {
            perror("shmget");
            return 0;
        }
        // 映射
        // NULL表示由系统分配地址, 0表示可读可写
        if ((addr = (char *) shmat(shmid, NULL, 0)) == (void *) -1) {
            perror("shmat");
            return 0;
        }
        // 获取标准输入 放到共享内存地址
        // 1024则为共享内存创建时的指定大小 防止越界
        fgets(addr, 1024, stdin);
    }

    共享内存撤销映射-shmdt

    Ps:不使用共享内存时应该撤销映射

    进程结束时自动撤销

    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    int shmdt(void *shmaddr);
    
    // 成功时返回0
    // 失败时返回EOF
    // shmaddr 映射后的首地址

    共享内存控制-shmctl

    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    int shmctl(int shmid,int cmd,struct shmid_ds *buf);
    
    // 成功时返回0
    // 失败时返回EOF
    
    // shmid 要操作的共享内存id
    // cmd 要执行的操作 
    // buf 保存或设置共享内存属性的地址
    // cmd宏定义
    // IPC_STAT 获取当前共享内存的属性 保存到 buf的结构体指针
    // IPC-SET 设置当前共享内存的属性 将buf的属性设置到贡献内存中
    // IPC_RMID 删除该共享内存的id, buf参数可以省略为NULL

    消息队列

    • 消息队列是System V IPC对象的一种
    • 消息队列由消息队列ID作为唯一标识
    • 消息队列就是一个消息的列表,用户可以在消息队列中添加消息。读取消息等
    • 消息队列可以按照类型来发送/接受消息

    消息队列在删除时直接删除发送接收会立即出错

    消息队列的使用步骤

    • 打开/创建消息队列-msgget
    • 向消息队列发送消-msgsnd
    • 从消息队列接收消息-msgrcv
    • 控制消息队列-msgctl

    打开/创建消息队列

    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int msgget(key_t,int msgflg);
    
    // 成功时返回消息队列的id
    // 失败时返回EOF
    // key和消息队列关联的key IPC_PRIVATE或ftok
    // msgflg 标志位 IPC_CREAT|0666

    Demo:创建/打开消息队列

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int main(int argc, char *argv[]) {
        int msgid;
        key_t key;
        // 生成key
        if ((key = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
        // 打开或创建消息队列
        if ((msgid = msgget(key, IPC_CREAT | 0666)) < 0) {
            perror("msgget");
            return 0;
        }
    }

    消息格式

    • 通讯双方首先定义好统一的消息格式
    • 用户根据应用需求定义结构体类型
    • 首成员类型为long,代表消息类型(正整数)
    • 其他成员都属于消息正文

    Demo:

    typedef struct {
        // 消息的类型 *第一个成员必须是 long
        long mType;
        char mContent[64]; // 消息的正文
    } Msg;

    消息发送-msgsnd

    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int msgsnd(int msgin,const void *msgp,size_t size,int msgflg);
    
    // 成功时返回0
    // 失败时返回-1
    // msgid 消息队列id
    // msgp 消息缓冲区的地址
    // size 发送消息的长度(正文的长度)
    // msgflg 标志位 0 或者 IPC_NOWALT , 0表示消息发送成功再返回,IPC_NOWALT不等待结果直接返回

    Demo:

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    // 发送消息的格式
    typedef struct {
        // 消息的类型 *第一个成员必须是 long 
        long mType;
        char mContent[64]; // 消息的正文
    } Msg;
    
    // 定义宏 消息正文的长度
    #define ContentSize (sizeof(Msg)-sizeof(long))
    
    int main(int argc, char *argv[]) {
        int msgid;
        key_t key;
        // 消息buf
        Msg buf;
        // 生成key
        if ((key = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
        // 打开或创建消息队列
        if ((msgid = msgget(key, IPC_CREAT | 0666)) < 0) {
            perror("msgget");
            return 0;
        }
        // 消息发送
        buf.mType = 100;
        // 从用户输入获取 正文 信息
        fgets(buf.mContent, 64, stdin);
        // 发送信息
        msgsnd(msgid, &buf, ContentSize, 0);
    }

    消息接受-msgrcv

    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int msgflg);
    
    // 成功时返回接受到的消息长度
    // 失败时返回-1
    
    // msgid 消息队列id
    // msgp 消息缓冲区的地址
    // size 指定接受的消息长度 超过指定消息长度会截断
    // msgtype 指定接受的消息类型 如果指定为0 接受最早的一条消息 不是按照类型接受 ,如果指定负数则是优先级接受
    // msgflg 标志位 0 或 IPC_NOWAIT 如果0表示会等待消息 IPC_NOWAIT不阻塞直接返回

    Demo:

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    // 发送消息的格式
    typedef struct {
        // 消息的类型 *第一个成员必须是 long
        long mType;
        char mContent[64]; // 消息的正文
    } Msg;
    
    // 定义宏 消息正文的长度
    #define ContentSize (sizeof(Msg)-sizeof(long))
    
    // receive
    int main(int argc, char *argv[]) {
        int msgid;
        key_t key;
        // 表示只接受100的消息
        long mtype = 100;
        // 消息buf
        Msg buf;
        // 生成key
        if ((key = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
        // 打开或创建消息队列
        if ((msgid = msgget(key, IPC_CREAT | 0666)) < 0) {
            perror("msgget");
            return 0;
        }
        // 消息接受
        if ((msgrcv(msgid, &buf, ContentSize, mtype, 0)) < 0) {
            perror("msgrcv");
            return 0;
        }
        printf("%s", buf.mContent);
    }

    控制消息队列-msgctl

    #include <sys/ipc.h>
    #include <sys/msg.h>
    
    int msgctl(int msgid,int cmd,struct msqid_ds *buf)
    
    // 成功时返回0
    // 失败时返回-1
    // msgid 消息队列id
    // cmd 要执行的操作 IPC_STAT(获取属性)/IPC_SET(设置属性)/IPC_RMID(删除) , 前两个依赖第三个buf 最后一个不依赖 可传NULL
    // buf 存放队列属性的地址

    信号灯

    • 信号灯也叫信号量,用于进程、线程同步或互斥的机制
    • 信号灯的类型
      • Posix 无名信号灯
      • Posix 有名信号灯
      • System V 信号灯
    • 信号灯等含义
      • 计数信号灯 
    • System V 信号灯
      • System V 信号灯是一个或多个计数信号灯的集合
      • 可以同时操作集合中的多个信号灯
      • 申请多个资源时避免死锁

    System V 信号灯使用步骤

    • 打开/创建信号灯-semget
    • 信号灯初始化-stmctl
    • P/V操作-semop
    • 删除信号灯-stmctl

    打开/创建信号灯-semget

    int semget(key_t key,int nsems,int semflg);
    
    // 成功返回信号灯集合的id
    // 失败时返回-1
    
    // key 和消息对列关联的key IPC_PRIVATE 或 ftok
    // nsems 集合中包含的计数信号灯个数
    // semflg 标记位 IPC_CREAT|0666 IPC_EXCL  (IPC_EXCL如果这个信号灯存在则报错 用于检查信号灯是否存在)只能初始化一次 所以需要判断是否已经存在

    信号灯初始化-semctl

    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    int semctl(int semid,int semnum,int cmd,...);
    
    // 成功返回0
    // 失败返回EOF
    // semid 要操作的信号灯集id
    // semnum 要操作的信号灯集合中的信号灯编号 从0开始到num-1
    // cmd 执行的操作 SETVAL(设置信号灯的值)  IPC_RMID(删除整个信号灯集合)
    // ... union semun 取决于cmd SETVAL时需要用到 

    Demo:假设信号灯集合中包含两个信号灯;第一个初始化为2,第一个初始化为0

    #include <stdio.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    // 声明共用体类型
    union semun mun;
    
    int main(int argc, char *argv[]) {
        // 信号灯id
        int semid;
        key_t key;
        // 生成key
        if ((key = ftok(".", 'a')) == -1) {
            perror("ftok");
            return 0;
        }
        // 负责创建的进程负责初始化
        // 创建 有2个信号灯的信号灯集合
        if ((semid = semget(key, 2, IPC_CREAT | 0666 | IPC_EXCL)) < 0) {
            perror("semget");
            return 0;
        }
        // 指定类型1
        mun.val = 2;
        // 初始化信号灯集合中的0号信号灯的val为2
        if (semctl(semid, 0, SETVAL, mun) < 0) {
            perror("semctl");
            return 0;
        }
        // 指定类型2
        mun.val = 0;
        // 初始化信号灯集合中的1号的信号灯的val为0
        if (semctl(semid, 1, SETVAL, mun) < 0) {
            perror("semctl");
            return 0;
        }
    
    }

    信号灯的P/V操作-semop

    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    int semop(int semid,struct sembuf * sops,unsigned nsops);
    
    // 成功返回0
    // 失败返回-1
    
    // semid 要操作信号灯集id
    // sops 描述对信号灯操作的结构体(数组) 系统中已经定义好的sembuf结构体
    // nsops要操作的信号灯的个数
    
    struct sembuf {
        unsigned short  sem_num;       // 指定操作信号灯的编号
        short           sem_op;         // 负数:P操作 整数:V操作
        short           sem_flg;        // 0/IPC_NOWAIT 0表示阻塞等待,IPC_NOWAIT立刻返回,不阻塞
    };
    Songzhibin
  • 相关阅读:
    查询避免Unknown column ‘xxx’ in ‘where clause
    Spring依赖循环:The dependencies of some of the beans in the application context form a cycle
    POJ(北京大学)刷题导航
    ACM使用Java提交总是超时的解决方法
    申请了服务器,建立了新博客。 不在用这个了。
    jeecg数据库添加字段后表单的修改
    jeecg普通表和数据字典的关联
    jeecg添加滚动图
    jeecg定时任务的bug记录
    classpath究竟是指哪里?
  • 原文地址:https://www.cnblogs.com/binHome/p/12845559.html
Copyright © 2020-2023  润新知