信号量的学习

12 篇文章 0 订阅
信号量
一.什么是信号量
信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)
所拥有。
信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为 0,说明
它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
二.信号量的分类
在学习信号量之前,我们必须先知道——Linux 提供两种信号量:
(1) 内核信号量,由内核控制路径使用
(2) 用 户 态 进 程 使 用 的 信 号 量 , 这 种 信 号 量 又 分 为 POSIX 信 号 量 和
SYSTEM V 信号量。
POSIX 信号量又分为有名信号量和无名信号量。
有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名
信号量,其值保存在内存中。
倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷
失了方向。
三.内核信号量
1.内核信号量的构成
内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。
然而,
当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。
只有在资源
被释放时,进程才再次变为可运行。
只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内
核信号量。
内核信号量是 struct semaphore 类型的对象,它在<asm/semaphore.h>中定义:
struct semaphore {
   atomic_t count;
   int sleepers;
   wait_queue_head_t wait;
  }
count:相当于信号量的值,大于 0,资源空闲;等于 0,资源忙,但没有进程等待这
个保护的资源;小于 0,资源不可用,并至少有一个进程等待资源。
wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。
2.内核信号量的相关函数
(1)初始化:
void sema_init (struct semaphore *sem, int val);
void init_MUTEX (struct semaphore *sem); //将 sem 的值置为 1,表示资源空闲
void init_MUTEX_LOCKED (struct semaphore *sem); //将 sem 的值置为 0,表示资
源忙
(2)申请内核信号量所保护的资源:
void down(struct semaphore * sem);
// 可引起睡眠
int down_interruptible(struct semaphore * sem); // down_interruptible 能被信
号打断
int down_trylock(struct semaphore * sem);
// 非阻塞函数,不会睡眠。无法锁定
资源则
马上返回
(3)释放内核信号量所保护的资源:
void up(struct semaphore * sem);
4.内核信号量的使用例程
在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的
Linux 内核中
共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。
解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。
ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
 //获得信号量
 if (down_interruptible(&sem))
 {
  return - ERESTARTSYS;
 }
 //将用户空间的数据复制到内核空间的 global_var
 if (copy_from_user(&global_var, buf, sizeof(int)))
 {
  up(&sem);
  return - EFAULT;
 }
 //释放信号量
 up(&sem);
 return sizeof(int);
}
四.POSIX 信号量与 SYSTEM V 信号量的比较
1. 对 POSIX 来说,信号量是个非负整数。常用于线程间同步。
而 SYSTEM V 信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,
这个结构体是为 SYSTEM V IPC 服务的,信号量只不过是它的一部分。
常用于进程间同步。
2.POSIX 信号量的引用头文件是“<semaphore.h>”,而 SYSTEM V 信号量的引用头
文件是“<sys/sem.h>”。
3.从使用的角度,System V 信号量是复杂的,而 Posix 信号量是简单。比如,POSIX 信
号量的创建和初始化或 PV 操作就很非常方便。
五.POSIX 信号量详解
1.无名信号量
无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初
始化该无名信号量,之后就可以放心使用了。
无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。
也就是说,无名信
号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程
(线程)的共享变量,这两个条件是缺一不可的。
常见的无名信号量相关函数:sem_destroy
int sem_init(sem_t *sem, int pshared, unsigned int value);
1)pshared==0 用于同一多线程的同步;
2)若 pshared>0 用于多个相关进程间的同步(即由 fork 产生的)
int sem_getvalue(sem_t *sem, int *sval);
取回信号量 sem 的当前值,把该值保存到 sval 中。
若有 1 个或更多的线程或进程调用 sem_wait 阻塞在该信号量上,该函数返回两种值:
1) 返回 0
2) 返回阻塞在该信号量上的进程或线程数目
linux 采用返回的第一种策略。
sem_wait(或 sem_trywait)相当于 P 操作,即申请资源。
int sem_wait(sem_t *sem);
// 这是一个阻塞的函数
测试所指定信号量的值,它的操作是原子的。
若 sem>0,那么它减 1 并立即返回。
若 sem==0,则睡眠直到 sem>0,此时立即减 1,然后返回。
int sem_trywait(sem_t *sem); // 非阻塞的函数
其他的行为和 sem_wait 一样,除了:
若 sem==0,不是睡眠,而是返回一个错误 EAGAIN。
sem_post 相当于 V 操作,释放资源。
int sem_post(sem_t *sem);
把指定的信号量 sem 的值加 1;
呼醒正在等待该信号量的任意线程。
注意:在这些函数中,只有 sem_post 是信号安全的函数,它是可重入函数
(a)无名信号量在多线程间的同步
无名信号量的常见用法是将要保护的变量放在 sem_wait 和 sem_post 中间所形成的
临界区内,这样该变量就会被保护起来,例如:
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id, 0, 1);
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main,,,\n");
return 0;
}
上面的例程,到底哪个线程先申请到信号量资源,这是随机的。
如果想要某个特定的顺
序的话,可以用 2 个信号量来实现。例如下面的例程是线程 1 先执行完,然后线程 2 才继
续执行,直至结束。
int number; // 被保护的全局变量
sem_t sem_id1, sem_id2;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id1);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id2);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id2);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id1);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id1, 0, 1); // 空闲的
sem_init(&sem_id2, 0, 0); // 忙的
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main,,,\n");
return 0;
}
(b)无名信号量在相关进程间的同步
说是相关进程,是因为本程序中共有 2 个进程,其中一个是另外一个的子进程(由
fork
产生)的。
本来对于 fork 来说,子进程只继承了父进程的代码副本,mutex 理应在父子进程
中是相互独立的两个变量,但由于在初始化 mutex 的时候,由 pshared = 1
指定了 mutex 处于共享内存区域,所以此时 mutex 变成了父子进程共享的一
个变量。此时,mutex 就可以用来同步相关进程了。
#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
int fd, i,count=0,nloop=10,zero=0,*ptr;
sem_t mutex;
//open a file and map it into memory
fd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU);
write(fd,&zero,sizeof(int));
ptr = mmap( NULL,sizeof(int),PROT_READ |
PROT_WRITE,MAP_SHARED,fd,0 );
close(fd);
/* create, initialize semaphore */
if( sem_init(&mutex,1,1) < 0) //
{
perror("semaphore initilization");
exit(0);
}
if (fork() == 0)
{ /* child process*/
for (i = 0; i < nloop; i++)
{
sem_wait(&mutex);
printf("child: %d\n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}
/* back to parent process */
for (i = 0; i < nloop; i++)
{
sem_wait(&mutex);
printf("parent: %d\n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}
2.有名信号量
有名信号量的特点是把信号量的值保存在文件中。
这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关
进程。
(a)有名信号量能在进程间共享的原因
由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父
进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当
然文件里面保存的有名信号量值就共享了。
(b)有名信号量相关函数说明
有名信号量在使用的时候,和无名信号量共享 sem_wait 和 sem_post 函数。
区别是有名信号量使用 sem_open 代替 sem_init,另外在结束的时候要像关闭文件
一样去关闭这个有名信号量。
(1)打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完
成了信号量的创建、初始化和权限的设置。
sem_t *sem_open(const char *name, int oflag, mode_t mode , int value);
name 是文件的路径名;
Oflag 有 O_CREAT 或 O_CREAT|EXCL 两个取值;
mode_t 控制新的信号量的访问权限;
Value 指定信号量的初始化值。
注意:
这里的 name 不能写成/tmp/aaa.sem 这样的格式,因为在 linux 下,sem 都是创建
在/dev/shm 目录下。
你可以将 name 写成“/mysem”或“mysem”,创建出来的文件都
是“/dev/shm/sem.mysem”,千万不要写路径。
也千万不要写“/tmp/mysem”之类的。
当 oflag = O_CREAT 时,若 name 指定的信号量不存在时,则会创建一个,而且后
面的 mode 和 value 参数必须有效。若 name 指定的信号量已存在,则直接打开该信号量,
同时忽略 mode 和 value 参数。
当 oflag = O_CREAT|O_EXCL 时,若 name 指定的信号量已存在,该函数会直接返
回 error。
(2) 一旦你使用了一信号量,销毁它们就变得很重要。
在做这个之前,要确定所有对这个有名信号量的引用都已经通过 sem_close()函数
关闭了,然后只需在退出或是退出处理函数中调用 sem_unlink()去删除系统中的信号量,
注意如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作
用。
也就是说,必须是最后一个使用该信号量的进程来执行 sem_unlick 才有效。
因为每个
信号灯有一个引用计数器记录当前的打开次数,sem_unlink 必须等待这个数为 0 时才能
把 name 所指的信号灯从文件系统中删除。也就是要等待最后一个 sem_close 发生。
(c)有名信号量在无相关进程间的同步
前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于
共享内存区,只有这样才能被无相关的进程所共享。
在下面这个例子中,服务进程和客户进程都使用 shmget 和 shmat 来获取得一块共
享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。
<u>File1: server.c </u>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize semaphore
mutex = sem_open(SEM_NAME,O_CREAT,0644,1);
if(mutex == SEM_FAILED)
{
perror("unable to create semaphore");
sem_unlink(SEM_NAME);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,IPC_CREAT|0666);
if(shmid<0)
{
perror("failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid,NULL,0);
//start writing into memory
s = shm;
for(ch='A';ch<='Z';ch++)
{
sem_wait(mutex);
*s++ = ch;
sem_post(mutex);
}
//the below loop could be replaced by binary semaphore
while(*shm != '*')
{
sleep(1);
}
sem_close(mutex);
sem_unlink(SEM_NAME);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
<u>File 2: client.c</u>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SHMSZ 27
char SEM_NAME[]= "vik";
int main()
{
char ch;
int shmid;
key_t key;
char *shm,*s;
sem_t *mutex;
//name the shared memory segment
key = 1000;
//create & initialize existing semaphore
mutex = sem_open(SEM_NAME,0,0644,0);
if(mutex == SEM_FAILED)
{
perror("reader:unable to execute semaphore");
sem_close(mutex);
exit(-1);
}
//create the shared memory segment with this key
shmid = shmget(key,SHMSZ,0666);
if(shmid<0)
{
perror("reader:failure in shmget");
exit(-1);
}
//attach this segment to virtual memory
shm = shmat(shmid,NULL,0);
//start reading
s = shm;
for(s=shm;*s!=NULL;s++)
{
sem_wait(mutex);
putchar(*s);
sem_post(mutex);
}
//once done signal exiting of reader:This can be replaced by another
semaphore
*shm = '*';
sem_close(mutex);
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
六.SYSTEM V 信号量
这是信号量值的集合,而不是单个信号量。相关的信号量操作函数由<sys/ipc.h>引
用。
1.信号量结构体
内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:
struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
struct sem *sem_base;
/* 某个信号量 sem 结构数组的指针,当前信号量集
中的每个信号量对应其中一个数组元素 */
ushort sem_nsems;
/* sem_base 数组的个数 */
time_t sem_otime;
/* 最后一次成功修改信号量数组的时间 */
time_t sem_ctime;
/* 成功创建时间 */
};
struct sem {
ushort semval;
short sempid;
ushort semncnt;
ushort semzcnt;
/* 信号量的当前值 */
/* 最后一次返回该信号量的进程 ID 号 */
/* 等待 semval 大于当前值的进程个数 */
/* 等待 semval 变成 0 的进程个数 */
};
2.常见的 SYSTEM V 信号量函数
(a)关键字和描述符
SYSTEM V 信号量是 SYSTEM V IPC(即 SYSTEM V 进程间通信)的组成部分,其
他的有 SYSTEM V 消息队列,SYSTEM V 共享内存。而关键字和 IPC 描述符无疑是它们的
共同点,也使用它们,就不得不先对它们进行熟悉。
这里只对 SYSTEM V 信号量进行讨论。
IPC 描述符相当于引用 ID 号,要想使用 SYSTEM V 信号量(或 MSG、SHM),就必
须用 IPC 描述符来调用信号量。而 IPC 描述符是内核动态提供的(通过 semget 来获取),
用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字 KEY
来定位描述符。
某个 KEY 只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和
客户事先认可共同使用某个 KEY,那么大家就都能定位到同一个描述符,也就能定位到同
一个信号量,这样就达到了 SYSTEM V 信号量在进程间共享的目的。
(b)创建和打开信号量
int semget(key_t key, int nsems, int oflag)
(1) nsems>0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更
改。
(2) nsems==0 : 访问一个已存在的集合
(3) 返回的是一个称为信号量标识符的整数,semop 和 semctl 函数将使用它。
(4) 创建成功后信号量结构被设置:
.sem_perm 的 uid 和 gid 成员被设置成的调用进程的有效用户 ID 和有效组 ID
.oflag 参数中的读写权限位存入 sem_perm.mode
.sem_otime 被置为 0,sem_ctime 被设置为当前时间
.sem_nsems 被置为 nsems 参数的值
该集合中的每个信号量不初始化,这些结构是在 semctl,用参数 SET_VAL,SETALL
初始化的。
semget 函数执行成功后,就产生了一个由内核维持的类型为 semid_ds 结构体的信
号量集,返回 semid 就是指向该信号量集的引索。
(c)关键字的获取
有多种方法使客户机和服务器在同一 IPC 结构上会合:
(1) 服务器可以指定关键字 IPC_PRIVATE 创建一个新 IPC 结构,将返回的标识符存放在某
处(例如一个文件)以便客户机取用。
关键字 IPC_PRIVATE 保证服务器创建一个新 IPC 结
构。
这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件
取得此标识符。
IPC_PRIVATE 关键字也可用于父、
子关系进程。
父进程指定 IPC_PRIVATE 创建一个新
IPC 结构,所返回的标识符在 fork 后可由子进程使用。
子进程可将此标识符作为 exec 函数
的一个参数传给一个新程序。
(2) 在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键
字创建一个新的 IPC 结构。这种方法的问题是该关键字可能已与一个 IPC 结构相结合,在
semget 或 shmget)出错返回。
此情况下,get 函数(msgget、
服务器必须处理这一错误,
删除已存在的 IPC 结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。
(3) 客户机和服务器认同一个路径名和课题 I D(课题 I D 是 0 ~ 2 5 5 之间的字符值) ,
然后调用函数 ftok 将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键
字的问题。
使用 ftok 并非高枕无忧。有这样一种例外:服务器使用 ftok 获取得一个关键字后,该
文件就被删除了,然后重建。此时客户端以此重建后的文件来 ftok 所获取的关键字就和服
务器的关键字不一样了。所以一般商用的软件都不怎么用 ftok。
一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使
用 ftok,而只是在该头文件中存放一个大家都知道的关键字。
(d)设置信号量的值(PV 操作)
int semop(int semid, struct sembuf *opsptr, size_t nops);
(1) semid: 是 semget 返回的 semid
(2)opsptr: 指向信号量操作结构数组
(3) nops : opsptr 所指向的数组中的 sembuf 结构体的个数
struct sembuf {
short sem_num; // 要操作的信号量在信号量集里的编号,
short sem_op;
// 信号量操作
short sem_flg;
// 操作表示符
};
(4) 若 sem_op 是正数,其值就加到 semval 上,即释放信号量控制的资源
若 sem_op 是 0,那么调用者希望等到 semval 变为 0,如果 semval 是 0 就返回;
若 sem_op 是负数,那么调用者希望等待 semval 变为大于或等于 sem_op 的绝对
值
例如,当前 semval 为 2,而 sem_op = -3,那么怎么办?
注意:semval 是指 semid_ds 中的信号量集中的某个信号量的值
(5) sem_flg
SEM_UNDO
由进程自动释放信号量
IPC_NOWAIT 不阻塞
到这里,读者肯定有个疑惑:semop 希望改变的 semval 到底在哪里?我们怎么没看
到有它的痕迹?其实,前面已经说明了,当使用 semget 时,就产生了一个由内核维护的
信号量集(当然每个信号量值即 semval 也是只由内核才能看得到了),用户能看到的就
是返回的 semid。内核通过 semop 函数的参数,知道应该去改变 semid 所指向的信号量
的哪个 semval。
(e)对信号集实行控制操作(semval 的赋值等)
int semctl(int semid, int semum, int cmd, ../* union semun arg */);
semid 是信号量集合;
semnum 是信号在集合中的序号;
semum 是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成:
union semun
{
int val;
// cmd == SETVAL
struct semid_ds *buf
// cmd == IPC_SET 或者 cmd == IPC_STAT
ushort *array;
// cmd == SETALL,或 cmd = GETALL
};
val 只有 cmd ==SETVAL 时才有用,此时指定的 semval = arg.val。
注意:当 cmd == GETVAL 时,semctl 函数返回的值就是我们想要的 semval。千
万不要以为指定的 semval 被返回到 arg.val 中。
array 指向一个数组,当 cmd==SETALL 时,就根据 arg.array 来将信号量集的所
有值都赋值;当 cmd ==GETALL 时,就将信号量集的所有值返回到 arg.array 指定的数
组中。
buf 指针只在 cmd==IPC_STAT 或 IPC_SET 时有用,作用是 semid 所指向的信号量
集(semid_ds 机构体)。一般情况下不常用,这里不做谈论。
另外,cmd == IPC_RMID 还是比较有用的。
(f)例码
#include
#include
#include
#include
<sys/types.h>
<sys/ipc.h>
<sys/sem.h>
<stdio.h>
static int
nsems;
static int semflg;
static int semid;
int
errno=0;
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
int main()
{
struct sembuf sops[2]; //要用到两个信号量,所以要定义两个操作数组
int rslt;
unsigned short argarray[80];
arg.array = argarray;
semid = semget(IPC_PRIVATE, 2, 0666);
if(semid < 0 )
{
printf("semget failed. errno: %d\n", errno);
exit(0);
}
//获取 0th 信号量的原始值
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
//初始化 0th 信号量,然后再读取,检查初始化有没有成功
arg.val = 1; // 同一时间只允许一个占有者
semctl(semid, 0, SETVAL, arg);
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
sops[1].sem_flg = 0;
rslt=semop(semid, sops, 1); //申请 0th 信号量,尝试锁定
if (rslt < 0 )
{
printf("semop failed. errno: %d\n", errno);
exit(0);
}
//可以在这里对资源进行锁定
sops[0].sem_op = 1;
semop(semid, sops, 1); //释放 0th 信号量
rslt = semctl(semid, 0, GETVAL);
printf("val = %d\n",rslt);
rslt=semctl(semid, 0, GETALL, arg);
if (rslt < 0)
{
printf("semctl failed. errno: %d\n", errno);
exit(0);
}
printf("val1:%d
val2:
%d\n",(unsigned
int)argarray[0],(unsigned
int)argarray[1]);
if(semctl(semid, 1, IPC_RMID) == -1)
{
Perror(“semctl failure while clearing reason”);
}
return(0);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值