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
原文地址:https://www.cnblogs.com/binHome/p/12845559.html