一文搞懂信号量

一.信号量

1.1信号量概念

信号量是一种同步机制,用于控制对共享资源的访问。它是由计算机科学家 Edsger Dijkstra 在 1965 年提出的。

信号量可以理解为一个计数器,它可以表示可用资源的数量,可以是整数类型。信号量有两个主要操作:P(等待)和 V(释放)。

  1. P(等待)操作:当一个进程或线程需要访问共享资源时,它会尝试执行 P 操作。如果信号量的值大于 0,则进程可以继续访问资源,并将信号量的值减 1;如果信号量的值等于 0,则进程会被阻塞,直到信号量的值变为正数。

  2. V(释放)操作:当一个进程或线程完成对共享资源的访问时,它会执行 V 操作,将信号量的值加 1。如果有其他等待进程被阻塞,它们中的一个将被唤醒并获得对资源的访问权限。

信号量主要用于避免多个进程或线程同时访问共享资源导致的竞态条件。通过合理地控制信号量的值,可以实现对共享资源的互斥访问和同步操作。

此外,信号量还可以用于解决生产者-消费者问题、读者-写者问题等并发编程中的同步和互斥需求。

需要注意的是,信号量的正确使用需要仔细设计和管理,以避免死锁和竞态条件的发生。因此,在使用信号量时,应该了解其概念,并根据具体的应用场景进行合理的使用和配置。

1.2信号量编程步骤

使用信号量进行编程时,可以按照以下步骤进行:

  1. 导入信号量库:根据所使用的编程语言,首先需要导入相关的信号量库或模块。

  2. 创建信号量:使用相应的函数或方法创建一个信号量对象,并指定初始值。初始值表示可用资源的数量。

  3. P(等待)操作:当一个进程或线程需要使用共享资源时,执行 P 操作,尝试获取信号量。具体的实现细节可能因编程语言不同而有所差异,通常使用对应的函数或方法。

  4. 判断信号量值:在执行 P 操作后,判断信号量的值。如果信号量的值大于 0,则获得了对共享资源的访问权限;如果信号量的值等于 0,则进程或线程会被阻塞,直到信号量的值变为正数。

  5. 访问共享资源:在获得对共享资源的访问权限后,执行需要的操作,可以是对共享资源的读取或修改。

  6. V(释放)操作:完成对共享资源的访问后,执行 V 操作,释放信号量。这将增加信号量的值。

以上是使用信号量的基本编程步骤。需要注意的是,对于不同的编程语言和库,具体的函数和方法名称可能会有所差异,因此请参考相应的文档和资源以确保正确使用信号量机制。

此外,在使用信号量时,还应注意避免出现死锁和竞态条件等并发编程中的常见问题。合理地设计和管理信号量的使用,以确保资源访问的互斥性和同步性,是编程中的重要考虑因素。

1.3函数接口

1.ftok

ftok 函数是一个用于生成 System V IPC 键值 (key) 的函数,它可以将一个已存在的文件的路径名(或其他非零字符串)和一个整数项目标识符(如进程 ID)组合生成一个唯一的键值。

函数原型如下:

key_t ftok(const char *pathname, int proj_id);
  • pathname:一个已存在的文件路径名,用于生成 IPC 键值;文件必须对调用进程可读。
  • proj_id:一个非零的整数项目标识符,可以是任意值,用来区分不同的 IPC 对象。

ftok 函数根据给定的文件路径名和项目标识符计算出一个唯一的键值(key),该键值可以用于创建或访问 System V 信号量、共享内存和消息队列等 IPC 对象。

需要注意的是,使用 ftok 函数生成的键值并不保证在不同的系统上相同,因此,如果需要跨平台的 IPC 通信,建议使用其他方式生成键值。

示例用法:

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

int main() {
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
        perror("ftok");
        return -1;
    }
    
    printf("Generated key: %x\n", key);
    
    return 0;
}

以上示例中,通过给定文件路径 /path/to/file 和项目标识符 'A',使用 ftok 函数生成了一个对应的键值。生成的键值将会以 16 进制形式打印出来。

2.semget

semget() 函数用于创建或访问一个 System V 信号量集。它返回一个与指定键值关联的信号量集标识符(semid)。如果已存在具有相同键值的信号量集,则返回其对应的标识符。如果不存在,则根据给定的键值创建一个新的信号量集。

函数原型如下:

int semget(key_t key, int nsems, int semflg);
  • key:用于标识信号量集的键值。通常可以使用 ftok 函数生成。
  • nsems:指定将创建或访问的信号量集中的信号量数量。
  • semflg:标志参数,用于指定信号量集的创建和访问方式。

返回值:

  • 成功:返回一个大于等于 0 的信号量集标识符(semid)。
  • 失败:返回 -1,并设置 errno 来指示错误。

使用 semget() 函数创建新的信号量集时,需要满足以下条件:

  • 要创建的信号量集不存在具有相同键值的信号量集。
  • key 为 IPC_PRIVATE,表示创建一个私有的信号量集。

常用的 semflg 标志参数如下:

  • IPC_CREAT:如果与给定键值关联的信号量集不存在,则创建一个新的信号量集。
  • IPC_EXCL:与 IPC_CREAT 参数一起使用,如果该信号量集已经存在,则创建失败。

示例用法:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>

int main() {
    int semid;
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
        perror("ftok");
        return -1;
    }
    
    semid = semget(key, 3, IPC_CREAT | IPC_EXCL | 0644);
    if (semid == -1) {
        perror("semget");
        return -1;
    }
    
    printf("Semaphore ID: %d\n", semid);
    
    return 0;
}

以上示例中,通过 ftok 函数生成一个键值,然后使用 semget 函数创建一个具有 3 个信号量的新信号量集。如果已存在具有相同键值的信号量集,创建会失败。最后,打印生成的信号量集标识符(semid)。

3.semctl

semctl() 函数用于对 System V 信号量集进行控制操作,如获取信号量的状态、设置信号量的值、删除信号量集等。它可以实现对单个信号量或整个信号量集的操作。

函数原型如下:

int semctl(int semid, int semnum, int cmd, ...);
  • semid:目标信号量集的标识符。
  • semnum:用于指定要操作的具体信号量的索引。对于整个信号量集的操作,此参数一般设置为 0。
  • cmd:指定要执行的控制操作。

semctl() 函数的第四个参数为可选参数,根据不同的 cmd 命令,它的类型和用法可能不同。常用的 cmd 控制命令如下:

  • IPC_STAT:获取信号量集的状态信息,将结果存储在 semun 结构体中。
  • SETVAL:设置单个信号量的值。第四个参数为一个新的值,可以是整数或结构体 semun
  • GETVAL:获取单个信号量的值。
  • IPC_RMID:删除整个信号量集。

示例用法:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <errno.h>

int main() {
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
        perror("ftok");
        return -1;
    }
    
    int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0644);
    if (semid == -1) {
        perror("semget");
        return -1;
    }
    
    // 设置信号量的值
    union semun arg;
    arg.val = 5;
    int result = semctl(semid, 0, SETVAL, arg);
    if (result == -1) {
        perror("semctl");
        return -1;
    }
    
    // 获取信号量的值
    int val = semctl(semid, 0, GETVAL);
    if (val == -1) {
        perror("semctl");
        return -1;
    }
    printf("Semaphore value: %d\n", val);
    
    // 删除信号量集
    result = semctl(semid, 0, IPC_RMID);
    if (result == -1) {
        perror("semctl");
        return -1;
    }
    
    return 0;
}

以上示例中,通过 ftok 函数生成一个键值,然后使用 semget 函数创建一个具有一个信号量的新信号量集。接着,使用 semctl 函数分别设置该信号量的值为 5,并获取其值并打印。最后,使用 semctl 函数将删除信号量集。

需要注意的是,semctl 函数的错误处理需要通过返回值判断并使用 perror 函数打印错误信息。另外,对于复杂的操作,可能需要使用结构体 semid_dssemun,详细使用方法需要参考相关文档和手册。

4.semop

semop() 函数用于执行一系列的信号量操作,即对 System V 信号量集进行 P(等待)和 V(释放)操作,实现进程间的互斥和同步。

函数原型如下:

int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:目标信号量集的标识符。
  • sops:指向 sembuf 结构体数组的指针,每个结构体表示一个信号量操作。
  • nsops:指定要执行的信号量操作的数量。

sembuf 结构体定义如下:

struct sembuf {
    unsigned short sem_num;  // 信号量的索引
    short sem_op;            // 信号量操作
    short sem_flg;           // 操作标志
};
  • sem_num:要操作的信号量的索引。
  • sem_op:表示信号量的操作,负值表示 P(等待)操作,正值表示 V(释放)操作,0 表示等待信号量值为 0。
  • sem_flg:操作标志,控制操作行为。常用的标志包括 IPC_NOWAIT(非阻塞)和 SEM_UNDO(系统恢复时撤消操作)等。

示例用法:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <errno.h>

int main() {
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
        perror("ftok");
        return -1;
    }
    
    int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0644);
    if (semid == -1) {
        perror("semget");
        return -1;
    }
    
    // 设置信号量的初始值为 1
    union semun arg;
    arg.val = 1;
    int result = semctl(semid, 0, SETVAL, arg);
    if (result == -1) {
        perror("semctl");
        return -1;
    }
    
    // 等待信号量的值为 0
    struct sembuf sop;
    sop.sem_num = 0;
    sop.sem_op = 0;
    sop.sem_flg = 0;
    result = semop(semid, &sop, 1);
    if (result == -1) {
        perror("semop");
        return -1;
    }
    
    // 对信号量做 P 操作
    sop.sem_op = -1;
    result = semop(semid, &sop, 1);
    if (result == -1) {
        perror("semop");
        return -1;
    }
    
    printf("Semaphore operation complete.\n");
    
    // 对信号量做 V 操作
    sop.sem_op = 1;
    result = semop(semid, &sop, 1);
    if (result == -1) {
        perror("semop");
        return -1;
    }
    
    // 删除信号量集
    result = semctl(semid, 0, IPC_RMID);
    if (result == -1) {
        perror("semctl");
        return -1;
    }
    
    return 0;
}

以上示例中,通过 ftok 函数生成一个键值,然后使用 semget 函数创建一个具有一个信号量的新信号量集。接着,使用 semctl 函数设置该信号量的初始值为 1。然后,使用 semop 函数对信号量进行操作,先等待信号量的值为 0,再执行 P(等待)操作,最后执行 V(释放)操作。最后,通过 semctl 函数删除信号量集。

需要注意的是,semop 函数的错误处理需要通过返回值判断并使用 perror 函数打印错误信息。另外,对于复杂的操作,可能需要使用多个 sembuf 结构体来执行一系列的信号量操作。详细使用方法需要参考相关文档和手册。

1.4编程实战

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdlib.h>
union semun {
    int val;
    struct semid_ds *buf;
};

void mysemop(int semid, int num, int op)
{
    struct sembuf buf;
    buf.sem_flg = 0;   //堵塞
    buf.sem_num = num; //信号灯的编号
    buf.sem_op = op;   //pv操作
    semop(semid, &buf, 1);
}

int main(int argc, char const *argv[])
{
    //1.产生一个唯一的key值
    key_t key = ftok("/home/hq/demo/进程/a.txt", 'B');
    if (key < 0)
    {
        perror("ftok is err");
        exit(0);
    }

    //2.创建或打开信号灯集
    int semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);
    if (semid < 0)
    {
        if (errno == 17)
            semget(key, 2, 0666);
        else
        {
            perror("semget is err");
            exit(0);
        }
    }

    //3.初始化信号灯集
    union semun sem;
    sem.val = 0;
    semctl(semid, 0, SETVAL, sem);
    sem.val = 10;
    semctl(semid, 1, SETVAL, sem);

    //3.pv操作
    int i = 6;
    while (i--)
    {
        mysemop(semid, 1, -1);
        printf("%d\n", semctl(semid, 1, GETVAL));
    }

    //4.删除信号灯集
    semctl(semid, 0, IPC_RMID);

    return 0;
}

CAN(Controller Area Network,控制器局域网)总线协议是一种广泛应用于工业自动化、汽车电子等领域的串行通讯协议。其帧格式如下: <img src="https://img-blog.csdnimg.cn/20200925125252655.png" width="400"> CAN总线协议的帧分为标准帧和扩展帧两种,其中标准帧包含11位标识符,扩展帧包含29位标识符。在CAN总线上,所有节点都可以同时发送和接收数据,因此需要在帧中包含发送方和接收方的信息。 帧格式的具体解释如下: 1. 帧起始符(SOF):一个固定的位模式,表示帧的起始。 2. 报文控制(CTRL):包含几个控制位,如IDE、RTR等。其中IDE表示标识符的类型,0表示标准帧,1表示扩展帧;RTR表示远程请求帧,0表示数据帧,1表示远程请求帧。 3. 标识符(ID):11位或29位的标识符,用于区分不同的CAN消息。 4. 控制域(CTL):包含几个控制位,如DLC、EDL等。其中DLC表示数据长度,即数据域的字节数;EDL表示数据长度是否扩展,0表示标准数据帧,1表示扩展数据帧。 5. 数据域(DATA):0~8字节的数据。 6. CRC:用于校验数据是否正确。 7. 确认位(ACK):由接收方发送的确认信息,表示数据是否正确接收。 8. 结束符(EOF):一个固定的位模式,表示帧的结束。 以上就是CAN总线协议的帧格式。在实际应用中,节点之间通过CAN总线进行数据交换,通过解析帧中的各个字段,可以判断消息的发送方、接收方、数据内容等信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

左手的月光

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

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

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

打赏作者

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

抵扣说明:

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

余额充值