进程与线程操作(LINUX)



线程


1、线程是程序最基本的运行单位,而进程是不能运行的,真正运行的是进程中的线程。
2、进程相当于是一个办公场所,线程是里面的工作人员,真正工作的是线程,进程只是为线程提供一个环境

⚫ 串行:一件事做完再做新的事
⚫ 并发:交替做不同的事
⚫ 并行:同时做不同的事

编译

在编译过程后中,pthread库不是linux系统库,所以编译过程中要加上 -lpthead

线程存在的原因

进程切换开销大,为了减少开销,演化出线程概念


获取自身线程ID

#include <pthread.h> 

pthread_t pthread_self(void);

创建线程

int pthread_create(	pthread_t *thread, 
					const pthread_attr_t *attr,
					void *(*start_routine) (void *), 
					void *arg);

参数说明:
• thread:用于保存成功创建的线程ID值
• attr:设置线程属性,设置为NULL的时候表示默认属性。
• start_routine:线程执行函数
• arg:传入线程的实参

返回值:
成功: 0
失败: 返回对应的错误代码。

在创建线程的时候,如果不打算使用NULL创建默认值线程的话,则需要填写attr参数

#include <pthread.h> 

//pthread_attr_t类型对象初始化,然后把该对象设置为默认值
int pthread_attr_init(pthread_attr_t *attr); 

//pthread_attr_t类型对象删除
int pthread_attr_destroy(pthread_attr_t *attr);

想以分离状态创建线程,则在pthread_attr_t类型对象初始化后,对默认值进行修改
分离后无法被pthread_join()回收,线程结束后由操作系统收回其所占用的资源

#include <pthread.h> 

//设置分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 
参数:
attr:pthread_attr_t类型对象指针
detachstate:
PTHREAD_CREATE_DETACHED:分离状态启动线程

PTHREAD_CREATE_JOINABLE:线程默认值,正常启动线程,可以被其它线程获取终止状态信息

//获取分离状态
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

线程退出、等待和退出处理函数

void pthread_exit(void *retval);

参数:
retval:用于线程的返回值,返回值可由另一个线程调用pthread_join()来获取
如果线程是执行return语句终止的,那么return的返回值也是可以通过pthread_join()来获取的。

如果不关心线程的退出值,形参为NULL即可。
//等待线程退出
int pthread_join(pthread_t tid, void **rval_ptr);

返回值:
成功:0
/*
进程有进程退出处理函数,线程也有线程退出处理函数,
而且线程的退出处理函数和进程不同,
进程只能注册一个,线程能注册多个退出函数。
线程退出函数是一个函数栈,栈是先入后出,意思就是先注册的退出函数后执行
只有执行完所有的线程退出函数,线程才会终止
*/
#include <pthread.h> 

//添加退出函数
void pthread_cleanup_push(void (*routine)(void *), void *arg); 
参数:
routine:退出函数指针
arg:传递第一个参数函数指针的值

//删除最后注册的退出函数
void pthread_cleanup_pop(int execute);
参数:
execute: 
0的时候,直接删除最后注册的函数且不会执行

非0:执行、删除最后注册函数。
不管填什么值,只要是非0值,统一都是清楚并执行最后那个函数,
并不会因为不同的非0值而操作之前注册的函数。
因为是函数栈,一定是先入后出。

区别:都会删除最后注册的函数,只是区别在于会不会执行而已

线程执行以下动作时,清理函数栈中的清理函数才会被执行:
1、 线程调用pthread_exit()退出
2、 线程响应取消请求时
3、 用非0参数调用pthread_cleanup_pop()

ps:
在线程中使用return语句退出线程不会执行清楚函数

线程取消

cancel /ˈkæns(ə)l/ v.取消

#include <pthread.h> 

int pthread_cancel(pthread_t thread);
作用:向线程发送 停止 请求
返回值:
成功:0
失败:返回错误码

int pthread_setcancelstate(int state, int *oldstate); 
作用:设置线程取消功能是否开启
参数:
state:设置状态
state值:
PTHREAD_CANCEL_ENABLE:线程可以取消,新创建线程的默认值
PTHREAD_CANCEL_DISABLE:线程不可被取消.如接收到取消请求,则会将请求挂起
						直至线程的取消性状态变为PTHREAD_CANCEL_ENABLE
oldstate:之前的旧状态
返回值:
成功:0
失败:0

int pthread_setcanceltype(int type, int *oldtype);
前提是设置线程的状态为PTHREAD_CANCEL_ENABLE(允许线程取消)
参数:
type:
PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程继续运行,取消请求被挂起,直到线程到达某个取消点为止,新线程包括主线程默认的取消类型
PTHREAD_CANCEL_ASYNCHRONOUS:可能会在任何时间点(也许是立即取消,但不一定)取消线程

取消点

1、取消点其实是一系列函数,当执行到这些函数的时候,才会真正响应取消请求
2、在没有出现取消点时,取消请求无法得到处理。原因:系统认为没有到达取消点时,线程此时正在执行的工作是不能被停止的,正在执行关键代码,此时终止线程将可能会导致出现意想不到的异常发生
3、线程在调用这些函数时,如果收到了取消请求,那么线程便会遭到取消
在这里插入图片描述

若一个线程里面没有取消点,就算有取消请求也会被挂起,为了解决这个问题。使用pthread_testcancel函数可以产生取消点

#include <pthread.h> 

void pthread_testcancel(void);
作用:产生取消点

线程分离

作用:不关心线程的返回状态,只是希望终止时候能自动回收线程资源
状态不可逆,设置为分离状态后无法回到之前状态并且无法用pthread_join等待和回收资源

#include <pthread.h> 

int pthread_detach(pthread_t thread);
返回值:
成功:0

线程自身分离:pthread_detach(pthread_self()),不能写成pthread_detach(NULL)

示例

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void clean_func(void *arg)
{
	char *str = (char *)arg;
	printf("%s\n",str);
}

void *thread_task(void *arg)
{
	//注册线程取消函数
	pthread_cleanup_push(clean_func,"one");
	pthread_cleanup_push(clean_func,"two");
	pthread_cleanup_push(clean_func,"three");

	//提前手动删除并执行线程取消函数
	pthread_cleanup_pop(1);

	while(1)
	{
		//添加取消点
		pthread_testcancel();
	}
	
	//线程退出
	pthread_exit(NULL);

	pthread_cleanup_pop(0);
	pthread_cleanup_pop(0);
}

int main(int argc, char const *argv[])
{
	pthread_t tid;
	pthread_attr_t attr;

	//线程启动属性配置,以分离模式启动线程
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	//创建线程
	printf("创建线程\n");
	pthread_create(&tid,&attr,thread_task,NULL);

	sleep(3);

	printf("线程取消\n");
	pthread_cancel(tid);

	//销毁线程属性
	pthread_attr_destroy(&attr);

	while(1)
	{
		sleep(5);
		break;
	}

	return 0;
}

线程同步



进程


孤儿进程:
1、父进程比子进程先结束,子进程变成了“孤儿”,这种子进程称为孤儿进程。
2、在Linux系统中,所有的孤儿进程都会自动成为init进程(进程号为1)的子进程
3、子进程调用getppid()将返回1,init进程变成了孤儿进程的“养父”

僵尸进程:
1、子进程先于父进程结束,父进程还未来得及给子进程“收尸”,子进程就会变成了一个僵尸进程
2、进程结束后,通常需要父进程为其“收尸”,回收子进程占用的内存资源
3、父进程通过调用wait()或waitpid()函数回收子进程资源,归还给系统
4、父进程为子进程“收尸”后,僵尸进程就会被内核彻底删除
5、父进程没有为子进程收尸就退出了,那么此时init进程将会接管它的子进程并自动调用wait()收尸,从而在系统中移除僵尸进程
6、系统如果存在大量的僵尸进程,内核进程表将会被填满,导致阻碍新进程的创建
7、需要注意,僵尸进程是无法通过信号将其杀死的,即使是“一击必杀”信号SIGKILL也无法将其杀死。那么这种情况下,只能杀死僵尸进程的父进程(或等待其父进程终止),这样init进程将会接管这些僵尸进程,从而将它们从系统中清理掉!


状态

在这里插入图片描述
在这里插入图片描述


子进程创建

fork

fork()调用之后,父、子进程会各自执行fork()之后的指令。
共享代码段,不共享数据段、堆、栈
子进程拥有父进程数据段、堆、栈等副本

fork()函数的关键是,完成调用后将存在两个进程,一个是(父进程)、另一个则是子进程,并且两个进程(父、子)都会从fork()函数的返回处继续执行,从而调用fork()会导致返回两次值,子进程返回一个值、父进程返回一个值。
在程序代码中,可通过返回值来区分是子进程还是父进程。

fork()调用成功后,子进程和父进程会在各自的进程空间中继续执行fork()调用之后的代码

fork之前父进程如果使用了open函数,子进程会拷贝父进程的文件描述符表。这样父、子进程的文件描述表指向磁盘中相同的文件,文件在父子进程中实现了共享。例如,子进程更新了文件偏移量,父进程进行读写操作的时候,光标位置也会被影响

如果是fork( )以后,父子进程再分别打开同一个文件,则这时候文件不再共享。
在这里插入图片描述

#include <unistd.h> 

//创建子进程,使用fork的进程为父进程,fork创建的进程为子进程
pid_t fork(void);

返回值:
成功:
父进程:子进程的PID
子进程:0

失败:父进程返回-1,不创建子进程


vfork

使用vfork()可能会导致一些难以察觉的程序bug,所以尽量避免使用vfork()来创建子进程,尽量舍弃vfork()而使用fork()来创建进程


终止

exit、_exit

_exit()函数会清除使用的内存空间,销毁在内核中的各种数据结构,关闭进程的所有文件描述符,并结束进程、将控制权交给操作系统,_exit()和_Exit()属于系统调用函数

#include <unistd.h> 

//_exit()和_Exit()两者等价,用法作用是一样的
void _exit(int status);
void _Exit(int status);

exit()是标准C库函数,exit()会执行一些清理工作,最后调用_exit()函数

#include <stdlib.h>

void exit(int status);

exit()_exit() 区别:
1、exit() 最终是通过调用 _exit() 来终止进程
2 、程序中注册了进程终止处理函数,exit() 会调用终止处理函数
3、exit() 会刷新stdio流缓冲区

父、子进程不应都使用exit()来终止,只能一个进程使用exit()、另一个使用_exit()退出。
推荐子进程使用_exit()退出,父进程使用exit()退出,原因是调用exit()函数会在终止进程时刷新进程的stdio缓冲区


进程终止处理函数

#include <stdlib.h> 

//用于注册进程在正常终止时调用的函数
int atexit(void (*function)(void));
参数:
function:函数指针,指向注册的函数

返回值:
成功:0
失败:非0

等待进程

wait

系统调用wait()的动作:
1、调用wait()函数会阻塞等待,直到有一个子进程终止

2、 如果进程没有子进程,调用wait()意味着并没有需要等待的子进程,那么wait()将返回错误(返回-1),并且会将errno设置为ECHILD

3、进程调用wait()之前,子进程中已经有一个或多个终止了,那么wait()将不会阻塞。
wait()函数的作用除了获取子进程的终止状态信息之外,更重要的一点,就是回收子进程的资源,俗称为子进程“收尸”。
所以在调用wait()函数之前,已经有子进程终止了,意味着正等待着父进程为其“收尸”,所以调用wait()将不会阻塞,而是会立即替该子进程“收尸”,然后返回到正常的程序流程中,一次wait()调用只能处理一次

#include <sys/types.h> 
#include <sys/wait.h> 

pid_t wait(int *status);
函数:
status:存放子进程终止时的状态信息。NULL则表示不接收子进程终止时的状态信息。

返回值:
成功:返回终止子进程的进程号
失败:-1

waitpid

#include <sys/types.h> 
#include <sys/wait.h> 

pid_t waitpid(pid_t pid, int *status, int options);
函数:
pid:用于表示需要等待的某个具体子进程
pid的取值意义如下:
1、pid大于0,表示等待进程号为pid的子进程;
2、pid等于0,则等待与调用进程(父进程)同一个进程组的所有子进程;
3、pid等于-1,则等待任意子进程。wait(&status)waitpid(-1, &status, 0)等价。
4、pid小于-1,则会等待进程组标识符与pid绝对值相等的所有子进程;

status:存放进程终止时的状态信息

options:位掩码,可包括0个或多个标志:

'WNOHANG':子进程没有发生状态改变(终止、暂停),则立即返回,相当于非阻塞等待,通过返回值可以判断子进程发生状态改变情况,若返回值等于0表示没有发生改变。
'WUNTRACED':除了返回子进程终止的状态信息外,还返回因信号而停止(暂停运行)的子进程状态信息
'WCONTINUED':返回因收到SIGCONT信号而恢复运行的子进程的状态信息

返回值:
成功:返回终止子进程的进程号
失败:-1

子进程资源回收

当发生以下两种情况时,父进程会收到SIGCHLD信号:
1、父进程中的某个子进程终止时,父进程会收到SIGCHLD信号;
2、父进程的某个子进程因收到信号而停止(暂停运行)或恢复时,内核也可能向父进程发送该信号。

子进程的终止属于异步事件,父进程事先是无法预知的。

当子进程状态改变时(终止、暂停或恢复),父进程会收到SIGCHLD信号,而SIGCHLD信号的系统默认处理方式是忽略,所以捕获、绑定SIGCHLD信号处理函数,并在信号处理函数中调用wait()收回子进程,回收完毕之后再回到父进程自己的工作流程中

SIGCHLD信号是不可靠信号,如果有两个子进程相继终止,即使产生了两次SIGCHLD信号,父进程也只能捕获到一次SIGCHLD信号,可能就会导致有些僵尸进程成为“漏网之鱼”

解决方法:
在SIGCHLD信号处理函数中以非阻塞方式来循环调用waitpid(),直至再无其它终止的子进程需要处理为止

while( waitpid(-1, NULL, WNOHANG) > 0 ) 
	continue;

exec簇

使用场合:子进程不运行父进程的代码,而运行自己的代码

#include <unistd.h> 

int execve(const char *filename, char *const argv[], char *const envp[]);

参数:
filename:可执行文件的路径+文件名

argv:
字符串数组,最后一个为NULL,代表结束。
传递给新程序的命令行参数,类似于main函数的第二个参数argv
第一个元素就是可执行程序路径名

envp:字符串指针数组,指定了新程序的环境变量列表,以NULL结束。字符串格式为name=value

返回值:
调用成功将不会返回返回值
失败将返回-1,并设置errno

例子:
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

int main(int argc, char *argv[]) 
{ 
	char *args[] = {"/bin/ls", "-l", NULL};
	char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL}; 
	execve("/bin/ls", args, env_arr);
	perror("execve error"); 
	return 0;
}

进程组

1、一个进程只能属于一个进程组
2、进程组有一个组长进程,组长进程的ID等于进程组ID
3、组长进程不能再创建新的进程组
4、进程组中只要还存在一个进程,该进程组就存在,与组长进程是否终止无关
5、默认情况下,新创建的进程会继承父进程的进程组ID

#include <unistd.h> 

//获取进程组ID
pid_t getpgid(pid_t pid);  //get group id of process
成功:返回进程组ID
失败:-1

//获取进程组ID
pid_t getpgrp(void); //get group of process

两个函数区别:
getpgid可以通过PID号获取对应的进程组ID
getpgrp只能返回调用函数进程的进程组ID

getpgrp()等价于geipgid(0);
#include <unistd.h> 

int setpgid(pid_t pid, pid_t pgid); //add process(pid) to group
pid等于gpid:进程变成组长进程,创建一个新的进程组
pid等于0:函数调用进程本身
pgid等于0:创建一个新的进程组,由指定的pid进程作为进程组组长进程

int setpgrp(void);

setpgrp()函数等价于setpgid(0, 0)

进程只能为自己或子进程设置进程组ID
子进程调用exec函数后,就不能更改子进程的进程组ID了

守护进程(daemon)

系统与用户交互的界面称为终端,从终端运行的进程都会依附于终端,普通进程是和运行该进程的终端相绑定的。当终端被关闭,会话就会退出,由终端运行的所有进程都会被终止

守护进程能脱离终端并在后台运行,脱离终端的目的是为了避免进程在运行过程中的信息打印在终端,并且进程也不会被终端所产生的信息所打断

守护进程自成进程组、自成会话,即pid=gid=sid

查询守护进程 :
使用"ps -ajx"命令
TTY参数栏是问号" ?" : 表示进程没有控制终端,也就是守护进程

COMMAND参数栏使用中括号[ ]括起来的 : 表示内核线程,这些线程是在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用k开头的名字,表示Kernel


IPC

进程间通信:interprocess communication,简称IPC

管道

无名管道:PIPE
有名管道:FIFO

在LINUX shell中,可以使用管道符号" | "把进程输出重定义成另一个进程输入,例如: ps -aux | grep root

管道实现形态是文件,但管道不占用磁盘存储的空间,占用的是内存空间,因此Linux 上的管道是一个操作方式为文件的内存缓冲区

无名管道常见形态是shell中的 " | " 符号,只能在父子进程中使用。父进程在fork之前必须打开一个管道文件,再fork产生子进程,这样父子进程就会得到同一个管道文件的描述符,以达到通信的目的。

两者区别:
1、有名管道可以让无关系的进程进行通讯,无名管道只能让父子进程之间通信
2、有名管道写入操作具有原子性,支持同时多个进程写入而不会发生数据践踏。无名管道写入操作没有原子性,存在发生数据践踏可能性

两者共同:
1、因为数据是存储在内存中的,所以不能用lseek来偏移
2、写入、读取、关闭都是使用read、write、close来进行

无名管道:
管道的写入端写入数据相当于数据写入内存中,由内核缓冲,然后可从管道的读取端读取数据,数据遵循先进先出原则

#include <unistd.h>
//创建一个匿名管道,单向数据通道
int pipe(int pipefd[2]);
参数:
pipefd数组:用于存储匿名管道的文件描述符。pipefd[0] 是管道的读取端,pipefd[1]是管道的写入端

返回值:
成功:0
失败:-1

匿名管道在父进程fork以后会存在两个读取端与两个写入端
在这里插入图片描述

父进程发送数据给子进程,则父进程需要关闭读取端,子进程关闭写入端
在这里插入图片描述

父进程从子进程读取数据,则父进程需要关闭写入端,子进程关闭读取端
在这里插入图片描述

当不需要管道的时候,就在进程中将未关闭的一端关闭即可


有名管道:

检测文件属性
#include <unistd.h>
int access(const char *pathname, int mode);
参数:
mode:
F_OK:是否存在
R_OK:是否可读
W_OK:是否可写
X_OK:是否可执行

返回值:
成功:0
失败:-1
#include <sys/types.h>
#include <sys/stat.h>
//创建有名管道文件
int mkfifo(const char * pathname,mode_t mode);

参数:
pathname:路径
mode:可以用9bit来表示(UGO) 用八进制,例如: 0666,在C语言中,以0开头的整数字面值表示八进制数
U:文件所有者
G:group所有者的用户组成员
O:other其他用户

使用open打开的时:
O_NONBLOCK:非阻塞

返回值:
成功:0
失败:-1

消息队列

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

//根据路径名和id转换成IPC的key
key_t ftok(const char *pathname, int id);
参数:
id:有效位只有8位,即填写(1-255,而且这个值必须是非0值

返回值:
成功:生成的ket_t值
失败:-1

//创建或获取消息队列
int msgget(key_t key, int msgflg);

参数:
key:消息队列的标识,函数ftok的返回值或IPC_PRIVATE
当key被指定为IPC_PRIVATE 时,系统会自动产生一个未用的key 来对应一个新的消息队列对象,这个消息队列一般用于进程内部间的通信。

msgflg: IPC_CREAT、IPC_EXCL和队列使用权限mode
是一个int值,多个条件可以用 "|"来联合

IPC_CREAT :
消息队列不存在:创建
存在:返回存在的消息队列的标识符

IPC_EXCL:
单个存在没什么意义,主要和IPC_CREAT一起使用(用”|”连接)。如果消息队列不存在则创建,否则错误返回

mode:队列使用权限,权限只有读和写,执行权限是无效的,如06000666(八进制,使用0作为前缀)等

返回值:
成功:返回队列ID
失败:-1
EACCES:指定的消息队列已存在,但调用进程没有权限访问它
EEXIST:key 指定的消息队列已存在,而msgflg 中同时指定IPC_CREAT 和IPC_EXCL标志
ENOENT:key 指定的消息队列不存在同时msgflg 中没有指定IPC_CREAT 标志
ENOMEM:需要建立消息队列,但内存不足
ENOSPC:需要建立消息队列,但已达到系统的限制
//将消息写入到消息队列
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数说明:
msqid:消息队列标识符
msgp:发送给队列的消息,可发送自定义的结构体,但消息的第一个字段必须是long类型,表示消息的类型
struct msg_s {
	long type;   该值表示消息类型
	/*
		自定义内容
	*/
};
msgsz:消息的大小,不包含消息类型占用的4个字节(即第一个参数长度)
msgflg:
0 :当消息队列满时,函数将会阻塞,直到消息能写进

如果为IPC_NOWAIT 则表示:当消息队列已满的时候,函数不等待立即返回;
如果为IPC_NOERROR:若发送的消息大于size 字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程

返回值:
成功:0
失败:-1
EAGAIN:参数msgflg 设为IPC_NOWAIT,而消息队列已满。
EIDRM:标识符为msqid 的消息队列已被删除。
EACCESS:无权限写入消息队列。
EFAULT:参数msgp 指向无效的内存地址
EINTR:队列已满而处于等待情况下被信号中断
EINVAL:无效的参数msqid、msgsz 或参数消息类型type 小于0
//从消息队列中读取消息,读取后将消息从消息队列中删除
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数说明:
• msqid:消息队列标识符。
• msgp:存放消息的结构体,结构体类型要与msgsnd() 函数发送的类型相同
• msgsz:要接收消息的大小,不包含消息类型占用的4 个字节
• msgtyp : 两种情况,是否根据消息的类型去接收消息
不关注消息类型:
0:接收队列的第一个消息
关注消息类型:
大于0:符合 msgtyp == (*(long *)msgp) 条件的第一个消息
接收队列里面 值等于typp(msgsnd函数第二个参数结构体对应的msg_type值)的第一个消息

小于0: 符合 (*(long *)msgp)abs(msgtyp) 条件的第一个消息
接收类型等于或者小于msgtyp 绝对值的第一个消息

• msgflg 用于设置接收的处理方式,取值情况如下:
0: 阻塞式接收消息
IPC_NOWAIT:不阻塞接收,若没有消息接收,则立即返回,错误码为ENOMSG

IPC_EXCEPT:符合 msgtyp != (*(long *)msgp) 条件的第一个消息
与msgtype配合使用,返回队列中第一个类型不为msgtype的消息

IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃

返回值:
成功:实际读取到的消息数据长度
失败:-1,
错误代码:
– E2BIG:消息数据长度大于msgsz 而msgflag 没有设置IPC_NOERROR
– EIDRM:标识符为msqid 的消息队列已被删除
– EACCESS:无权限读取该消息队列
– EFAULT:参数msgp 指向无效的内存地址
– ENOMSG:参数msgflg 设为IPC_NOWAIT,而消息队列中无消息可读
– EINTR:等待读取队列内的消息情况下被信号中断
//设置或者获取消息队列的相关属性
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数说明:
• msqid:消息队列标识符
• cmd :操作命令
IPC_STAT 获取消息队列的信息,获取到的信息会储存在结构体msqid_ds 类型的buf 中。
IPC_SET 设置消息队列的属性,要设置的属性需先存储在结构体msqid_ds类型的buf中,
可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode 以及msg_qbytes,储存在结构体msqid_ds中

– IPC_RMID 立即删除该MSG,并且唤醒所有阻塞在该MSG上的进程,同时忽略第三个参数
– IPC_INFO 获得关于当前系统中MSG的限制值信息
– MSG_INFO 获得关于当前系统中MSG的相关资源消耗信息
– MSG_STAT 同IPC_STAT,但msgid 为该消息队列在内核中记录所有消息队列信息的数组的下标,因此通过迭代所有的下标可以获得系统中所有消息队列的相关信息

• buf:相关信息结构体缓冲区。

返回值:
成功:0
失败:-1
错误代码:
∗ EACCESS:参数cmd 为IPC_STAT,确无权限读取该消息队列。
∗ EFAULT:参数buf 指向无效的内存地址。
∗ EIDRM:标识符为msqid 的消息队列已被删除。
∗ EINVAL:无效的参数cmd 或msqid。
∗ EPERM:参数cmd 为IPC_SET 或IPC_RMID,却无足够的权限执行。
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值