目录
进程
概念
进程:动态概念(运行中程序)
程序:静态(一些指令集合)程序文件(可以移动)
软件:文档加程序
进程:进程是系统分配资源的最小单位
进程控制(进程状态)动态--生命周期 创建--调度--销毁
进程创建fork--系统调用
pid_t fork(void)
返回值
<0创建失败
==0现在所处的进程是子进程
>0 现在所处的进程是父进程(子进程的id号)
fork的两种用法
(1)一个父进程希望复制自己,使父,子进程同时执行不同代码段。这在网络服务进程中是常见的--父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则等待下一个服务请求到达
(2)一个进程执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec
每一次fork都会产生一个子进程,而wait(),相当于递归一样,等待最后一个子进程死后才去打印它上一个进程:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
pid_t x;
int i;
for(i=0; i<10; i++)
{
x = fork();
if(x > 0)
break;
if(x == 0)
continue;
}
wait(NULL);
printf("PID %d PPID %d\n", getpid(), getppid());
return 0;
}
vfork---先运行子进程,(子进程退出后)运行父进程
注意:一定要在子进程里要用exit()来退出子进程,不然会进入无限的循环。
fork()与vfork()的区别~~~:>>
主要为两点:
(1)执行次序:fork():对父子进程的调度室由调度器决定的;
vfork():是先调用子进程,等子进程的exit(1)被调用后,再调用父进程;
(2)对数据段的影响:fork():父子进程不共享一段地址空间,修改子进程,父进程的内容并不会受影响。
vfork():在子进程调用exit之前,它在父进程的空间中运行,也就是说会更改父进程的数据段、 栈和堆。。即共享代码区和数据区,且地址和内容都是一样的。
进程ID
每一个进程都有一个非负数整型表示的唯一ID。
getpid()--获取当前进程的id号
getppid()--获取当前进程的父进程id
getuid()--调用进程用户的实际ID
geteuid()--调用进程的有效用户ID
getgid()---调用进程实际组ID
getegid()--调用进程的有效组ID
僵尸进程
父进程还运行子进程已经退出(但是父进程没有回收子进程资源)(编程时候必须避免)
父进程回收子进程资源 在父进程中调用wait
pid_t wait(int *status)
孤儿进程
父进程已经退出子进程还在运行(最后都会把init进程作为父进程)
操作过程:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正在终止的子进程,如果是,则将该进程的父进程ID更改为1(init的进程ID)
进程退出
正常退出(程序运行完毕)
调用exit退出 (会做善后处理,关闭文件,清空缓存)
void exit(int status);会关闭所有IO流,进行冲洗
调用_exit退出 (直接退出) void _exit(int status);
status:子进程的退出值
无返回值
等待子进程
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出。如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);
WNOHANG:非阻塞等待
WCONTINUED:报告任意一个从暂停态出来且从未报告的子进程的状态
WUNTRQCED:报告任意一个当前处于暂停态且从未报告的子进程的状态
子进程的结束状态值会由参数status返回,而子进程的进程PID也会一起返回,如果不在意结束状态,则参数status可以设置为NULL。如果执行成功则返回进程PID,如果失败则返回-1
waipid:如果想让父进程周期性检查某个特定的子进程是否已经终止,可以使用waitpid(child_pid,(int *)0,WNOHANG)
exec函数族(接管一个进程的所有资源)
int execl(const char *path, const char *arg, ...); 后面变参必须以NULL结束(类似于main函数参数)
execl("/bin/ls", "ls","-a", "-l", NULL); ls -a -l 必须把要运行的程序路径加到PATH环境变中
int execlp(const char *file, const char *arg, ...);
execl("ls", "ls","-a", "-l", NULL); ls -a -l
int execle(const char *path, const char *arg,..., char * const envp[]);
char *envp[] = {"MM=/home/gec", "/usr/local", NULL};
execle("/home/gec/myshare/main", "main", NULL, envp);
int execv(const char *path, char *const argv[]);
char *argv[] = {"ls","-a", "-l", NULL};
execv("/bin/ls", argv);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
system("/home/gec/main")---类似于函数调用
exec函数族成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,,后面的代码无法运行,也无法返回
守护进程
是指在后台运行且不与终端相连街的一种进程(也称之为精灵进程或后台进程)
精灵进程示例(将一句话写到日记文件里)
daemon.h
#ifndef __DAEMON_H
#define __DAEMON_H
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int daemon_init(void);
#endif
daemon.c
#include "daemon.h"
int daemon_init(void)
{
pid_t pid;
int fd0, fd1, fd2, max_fd,i;
// 1
if((pid = fork()) < 0)
{
perror("fork faild!");
exit(1);
}
else if(pid != 0)//让父进程能出
{
exit(0);
}
// 2
signal(SIGHUP, SIG_IGN);//忽略终端被关闭的信号
// 3
if(setsid() < 0) //创建一个新的会话
{
exit(1);
}
// 4
if((pid = fork()) < 0)
{
perror("fork faild!");
exit(1);
}
else if(pid != 0)
exit(0);
// 5
setpgrp(); //设置孙子进程的进程组,彻底脱离会话
// 6 关闭看有的文件描述符,默认是1024个,但没有那么多打开,那么close就没反应的
max_fd = sysconf(_SC_OPEN_MAX);//获取文件打开的最大个数
for (i = max_fd; i>=0; i--)
{
close(i);//关闭文件
}
// 7 ; 0:表示不会影响你设置的权限,当你是umask(0777)的话,你要设置的文件权限都会被减为0,没有权限的意思
umask(0);
// 8 修改守护进程的当前工作路径,让它挂载到跟目录不会被卸载
chdir("/");
//Initialize the log file. 打开日记文件
openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);
return 0;
}
main.c
#include <unistd.h>
#include "daemon.h"
int main(void)
{
daemon_init(); //初始化一个精灵进程
while(1)
{
syslog(LOG_DAEMON, "I am a daemonAAA!"); //将这句话写到日记文件里
sleep(2); //2S写一次,但日记里只会出现一次,下面会告诉你重复了多少次
}
return 0;
}
进程间通信
进程间通信 管道, 信号, 消息队列,信号量,内存共享
管道
无名管道
无名管道--只使用于亲属进程(父子进程)
PIPE的特征:
1、没有名字,因此无法使用open() (只能在一个进程中被创建出来,通过继承方式将它的文件描述符传递给子进程,这也是 PIPE只能用于亲缘进程的原因)
2、 只能用于亲缘进程间的通信(父子进程,兄弟进程,祖孙进程)
3、 半双工工作方式:读写端分开
4、 写入操作不具有原子性,因此只能用于一对一的简单通信情型
5、 不能使用lseek()来定位(因为它的数据不像普通文件那样按块的方式存放在如硬盘,Flash等块设备上)
创建无名管道
int pipe(int pipefd[2]);
从pipefd[1]写入,从pipefd[0]读出
代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd[2];//用于存放PIPE的两个文件描述符
int ret = pipe(fd);
if(ret < 0)
{
perror("create pipe failed \n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("create fork failed\n");
}
if(pid == 0)
{
write(fd[1],"hello world",12);
}
if(pid > 0)
{
char buf[12] = {0};
read(fd[0],buf,12);
printf("%s\n",buf);
}
close(fd[0]);
close(fd[1]);
return 0;
}
管道和exec函数
pipe2.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
int processed;
int fd[2];
const char data[] = "123";
char buf[BUFSIZ+1];
memset(buf,'\0',sizeof(buf));
int ret = pipe(fd);
if(ret < 0)
{
perror("create pipe failed\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
perror("create fork failed\n");
return -1;
}
if(pid == 0)
{
//把读取管道数据的文件描述符保存到一个缓存区中,
//该缓存区的内容将构成pipe3程序的一个参数
sprintf(buf,"%d",fd[0]);
//(char *)0的作用是终止被调用程序的参数列表
(void)execl("pipe3","pipe3",buf,(char *)0);
exit(EXIT_FAILURE);
}
if(pid > 0)
{
processed = write(fd[1],data,strlen(data));
printf("%d --wrote %d bytes\n",getpid(),processed);
}
return 0;
}
pipe3.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
int processed;
char buf[BUFSIZ+1];
int descriptor;
memset(buf,'\0',sizeof(buf));
sscanf(argv[1],"%d",&descriptor);
processed = read(descriptor,buf,BUFSIZ);
printf("%d --read %d bytes: %s\n",getpid(),processed,buf);
return 0;
}
有名管道
有一个实实在在的管道文件-
FIFO的特征
1、有名字,存储于普通文件系统之中
2、任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符(程序不能以O_RDWR模式打开FIFO文件进行读写操作)
3、跟普通文件一样,使统一的read()/write()来读/写
4、跟普通文件不同,不能使用lseek()来定位,原因同PIPE
5、具有写入原子性,支持多写者同时进行写操作而数据不会被践踏
6、 First in First out 最先被写入FIFO的数据,最先被读出
创建管道 mkfifo 文件名
int mkfifo(const char *pathname, mode_t mode);
管道的读写
读操作read
有写者===》有数据===》 正常读取
===》无数据===》 阻塞等待
无写者===》有数据===》 正常读取
===》无数据===》立即返回
写操作write
有读者===》缓存未满===》正常写入
缓存已满===》阻塞等待
无读者===》缓存未满===》立即收到SIGPIPE
===》 缓存已满===》 立即收到SIGPIPE
FIFO间通信代码
fifoA.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define FIFOPATH "/tmp/fifo"
int main(int argc, char **argv)
{
int ret = mkfifo(FIFOPATH, 00777);
if(ret < 0 && errno != EEXIST)
{
perror("create fail");
return -1;
}
//
int fd = open(FIFOPATH, O_RDWR);
if(fd < 0)
{
perror("open fifo fail");
return -1;
}
char buf[32] = {0};
while(1)
{
scanf("%s", buf);
ret = write(fd, buf, strlen(buf)+1);
if(ret <= 0)
{
perror("write fail");
break;
}
if(strcmp(buf, "quit") == 0) break;
}
close(fd);
return 0;
}
fifoB.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define FIFOPATH "/tmp/fifo"
int main(int argc, char **argv)
{
int ret = mkfifo(FIFOPATH, 00777);
if(ret < 0 && errno != EEXIST)
{
perror("create fail");
return -1;
}
//
int fd = open(FIFOPATH, O_RDWR);
if(fd < 0)
{
perror("open fifo fail");
return -1;
}
char buf[32] = {0};
while(1)
{
ret = read(fd, buf, 32);
if(ret <= 0)
{
perror("write fail");
break;
}
if(strcmp(buf, "quit") == 0) break;
printf("wwww%s\n", buf);
}
close(fd);
return 0;
}
popen()和pclose()
FILE *popen(const char *command, const char *type);
popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据
参数
command:字符串是要运行的程序名和相应的参数
type:
“r”:被调用的程序输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数 (如fread)来读取被调用程序的输出
“w”:调用程序就可以用fwrite嗲用向被调用程序发送数据,而被调用程序就可以在自己的标准输入上读取这些数据,被调用的程 序 通常不会意识到自己正从另一个进程读取数据,它只是在标准输入流上读取数据,然后做出相应的操作
int pclose(FILE *stream);
读取外部程序的输出:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
FILE *read_fp;
char buffer[BUFSIZ+1];
int chars_read;
memset(buffer,'\0',sizeof(buffer));
read_fp = popen("ls -l","r");
if(read_fp != NULL)
{
chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
if(chars_read > 0)
{
printf("output was:-\n%s\n",buffer);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
return 0;
}
将读出送往外部程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
FILE *write_fp;
char buffer[BUFSIZ+1];
sprintf(buffer,"Once upon a time,threr was...\n");
write_fp = popen("od -c","w");
if(write_fp != NULL)
{
fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
return 0;
}
管道的弊端
无法在管道中读取一个指定的数据,因为这些数据没有做任何标记,读者进程只能按次序地逐个读取
信号
发送和接受信号
kill()和signal()
发送信号kill:
信号拦截signal
1、 sighandler_t signal(int signum, sighandler_t handler);(拦截的信号, 拦截后要做的事情)
2、 typedef void (*sighandler_t)(int);
(升级板)sigqueue()和sigaction()
struct sigaction的结构体
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
代码:
void ouch(int sig)
{
printf("ouch %d",sig);
}
int main(int argc, char **argv)
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT,&act,NULL);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
sigaction.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <strings.h>
void f(int sig, siginfo_t *info, void *arg)
{
printf("sig: %d\n", sig); // SIGINT(2)
printf("int: %d\n", info->si_int); // a.sival_int
printf("from %d\n", info->si_pid);
}
int main(void)
{
pid_t x = fork();
if(x > 0) // 父进程
{
struct sigaction act;
bzero(&act, sizeof(act));
act.sa_sigaction = f;
act.sa_flags = SA_SIGINFO; // 我要用扩展版的响应函数
// 向内核申请,将来收到SIGINT信号的时候,按照act结构体中
// 描述的情况来处理。NULL表示不需要内核返回该信号原来的处理模式
sigaction(SIGINT, &act, NULL); // signal()的扩展版
pause();
}
if(x == 0) // 子进程
{
union sigval a;
a.sival_int = 100;
printf("child PID: %d\n", getpid());
sleep(1);
// 给父进程发送信号SIGINT,顺便还发了a这个值
sigqueue(getppid(), SIGINT, a); // kill()的扩展版
}
}
信号响应
1、缺省动作
p1:int main(int argc, char **argv)
{
printf("my PID %d\n",getpid());
pause();
return 0;
}
p2:int main(int argc, char **argv)
{
pid_t pid;
scanf("%d",&pid);
kill(pid,SIGINT);
return 0;
}
通过p2发送信号将p1杀死
2、忽略
p1:int main(int argc, char **argv)
{
printf("my PID %d\n",getpid());
signal(SIGINT,SIG_IGN);
pause();
return 0;
}
p2:int main(int argc, char **argv)
{
pid_t pid;
scanf("%d",&pid);
kill(pid,SIGINT);
return 0;
}
p2向p1发送杀死信号,没有反应,因为p1已经忽略该信号 SIGKILL和SIGSTOP都无法忽略
3、捕捉
p1:void f(int sig)
{
printf("catch sig:%d\n",sig);
}
int main(int argc, char **argv)
{
printf("my PID %d\n",getpid());
signal(SIGINT,f);
pause();
return 0;
}
p2:int main(int argc, char **argv)
{
pid_t pid;
scanf("%d",&pid);
kill(pid,SIGINT);
return 0;
}
f()函数由内核调用,函数预先告诉内核,等接受到SIGINT信号时,调用f()函数
4、阻塞
代码
void catch(int sig)
{
fprintf(stderr,"%d catch SIGQUIT\n",getpid());
}
int main(int argc, char **argv)
{
signal(SIGQUIT,catch);
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig,SIGQUIT);
sigprocmask(SIG_BLOCK,&sig,NULL);
pid_t pid;
pid = fork();
if(pid == 0)
{
fprintf(stderr,"child %d\n",getpid());
int i = 10;
while(i>0)
{
printf("%d\n",i--);
sleep(1);
}
sigprocmask(SIG_UNBLOCK,&sig,NULL);
while(1)
pause();
}
if(pid > 0)
{
fprintf(stderr,"parent %d\n",getpid());
sigprocmask(SIG_UNBLOCK,&sig,NULL);
while(1)
pause();
}
return 0;
}
因为SIGQUIT被阻塞了,子进程接收到SIGQUIT是没到反应,等到10秒后解除阻塞才有反应,而父进程一开始就已经解除阻塞
其他
查看或删除系统中的IPC对象
获取一个当前未用的IPC的key
消息队列MSG
什么是消息队列
消息队列提供一种带有数据标识的特殊管道,使用一段被写入的数据都变成带标识的消息,读取该段消息的进程只要指定这个标识就可以正确的读取,而不受到其他消息的干扰
消息队列使用方法
(1)发送者
a、 获取消息队列的ID int msgget(key_t key, int msgflg);
b、 将数据放入一个附带有标识的特殊的结构体,发送给消息队列
(2)接收者
A、 获取消息队列的ID
B、将指定标识的消息读出
(3 )设置或获取消息队列的相关属性
1、int msgctl(int msqid, int cmd, struct msqid_ds *buf);
2、msqid:消息队列ID
3、cmd:
IPC_STAT:获取该MSG的信息,存储在结构体msqid_ds中
IPC_SET:设置该MSG的信息,储存在结构体msqid_ds中
IPC_RMID:立即删除该MSG,并唤醒所有阻塞在该MSG上的进程,同时忽略第三个参数
IPC_INFO:获取关于当前系统中MSG的限制值信息
MSG_INFO:获取关于当前系统中MSG的相关资源消耗信息
MSG_STAT:同IPC_STAT,但msgid为该消息队列的内核中记录所有信息的数组的下标,因此通过迭代所有的下标可以获得系统 中所有消息队列的相关信息
4、buf:相关信息结构提的缓冲区
5、IPC_STAT获取的属性信息被存放在一下结构体中
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
6、其中,权限相关的信息用如下结构体来表示
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
(4)删除消息队列 msgctl(msgid,IPC_RMID,NULL);
使用步骤
1.设定一个key值
2.在内核空间中申请队列(id)
3.写数据
4.读数据
5.销毁
共享内存段SHM
什么是共享内存
多个进程可以把一段内存映射到自己的进程空间,以此来实现数据的共享以及传输,这也是所有进程间通信方式中最快的一种
共享内存的使用步骤
1、获取共享内存对象的ID
int shmget(key_t key, size_t size, int shmflg);
key:共享内存的键值
size:共享内存的尺寸(PAGE_SIZE的整数倍)
shmflg:
IPC_CREAT:如果key对应的共享内存不存在,则创建
IPC_EXCL:如果该key对应的共享内存已存在,则报错
SHM_HUGETLB:使用大页面在分配共享内存
SHM_NORESERVE:不在交换分区中为这块共享内存保留空间
mode:共享内存的访问权限(八进制,如0644)
返回值
成功:该内存的ID
失败:-1
备注:如果key指定为IPC_PRVATE,则会自动产生一个随机未用的新键值
2、将共享内存映射至本进程虚拟内存空间的某个区域
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);解除映射 shmaddr共享内存的首地址
shmid:共享内存ID
shmaddr:
(1)如果为NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存
(2)如果不为NULL,则系统会根据shmaddr来选择一个合适的内存区域
shmflg:
SHM_RDONLY以只读的方式映射共享内存
SHM_REMAP:重新映射,此时shmaddr不能为NULL
SHM_RND:自动选择比shmaddr小的最大页对齐地址
可用0,表示对共享内存可读可写
第二参数被设置,那么第三个参数设置为SHM_RND,
返回值
成功:共享内存的首地址
失败:-1
3、当不在使用是,解除映射关系
4、当没有进程再需要这块共享内存时,删除它
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享内存ID
cmd:
IPC_STAT:获取属性信息,放到buf中
IPC_SET:设置属性信息为buf指向的内容
IPC_RMID:将共享内存标记为“立即被删除”状态
IPC_INFO:获取关于共享内存的限制值信息
SHM_INFO:获取系统为共享内存消耗的资源信息
SHM_STAT:同IPC_STAT,但shmid为该SHM在内核中记录所有SHM信息的数组下标,因此通过迭代所有下标可以获得系统中所有 SHM的相关信息
SHM_LOCK:禁止系统将该SHM交换至swap分区
SHM_UNLOCK:允许系统将该SHM交换至swap分区
返回值
成功:
IPC_INFO:内核中记录所有SHM信息的数组的下标最大值
SHM_INFO:内核中记录所有SHM信息的数组的下标最大值
SHM_STAT:x下标值为shmid的SHM的ID
失败:-1
IPC_STAT获得的属性信息被存放在以下结构体中:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
其中权限信息结构体:
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
5、可以通过shmctl获取或设置共享内存的相关属性
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享内存的ID
cmd:要采取的动作
常用的值
IPC_STAT:获取属性信息,放到buf中
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
IPC_SET:设置属性为buf指向的内容
IPC_RMID:删除共享内存段
buf:属性信息结构体指针
代码:
shmA.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
//1.获取key值
key_t key = ftok("/", 100);
printf("key = %d\n", key);
//2.获取共享内存id
int shmid = shmget(key, 1024, IPC_CREAT|0777);
if(shmid < 0)
{
perror("get id fail");
return -1;
}
//3.映射--链接
char *p = shmat(shmid, NULL, 0);
memset(p, 0, 1024);
memcpy(p, "hello haha", 11);
getchar();
//4.释放映射
shmdt(p);
//5.获取共内存状态
struct shmid_ds shmbuf;
int ret = shmctl(shmid, IPC_STAT, &shmbuf);
if(ret < 0)
{
perror("获取状态失败");
}
//当映射该SHM的进程个数为0时,删除共享内存
if(shmbuf.shm_nattch == 0)
{
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}
shmB.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
//1.获取key值
key_t key = ftok("/", 100);
printf("key = %d\n", key);
//2.获取共享内存id
int shmid = shmget(key, 1024, IPC_CREAT|0777);
if(shmid < 0)
{
perror("get id fail");
return -1;
}
//3.映射--链接
char *p = shmat(shmid, NULL, 0);
if(p == NULL)
{
perror("shmat fail");
}
printf("p=%s", p);
getchar();
}
信号量SEM
1、概念
信号量的P,V操作最核心的特征是:它们是原子性的,也就是说对信号量元素的值的增加和减少,系统保证CUP的电气特性级别上不可分割,这跟整型数据的加减法有本质的区别
2、 获取信号量的ID
int semget(key_t key, int nsems, int semflg);
key:信号量的键值
nsems:信号量的个数
semflg:
IPC_CREAT:如果key对应的信号量不存在,则创建
IPC_EXCL:如果key对应的信号量存在,则报错
mode:信号量的访问权限
3、对信号量进行P/V操作,或等零操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:信号量的ID(是由semget返回的信号量标识符)
sops:信号量操作结构体数组
struct sembuf
{
unsigned short sem_num; /* 信号量的元素序号(数组下标)(除非需要使用一组信号量,否则它的取值一般为0) */
short sem_op; /* 操作元素(通常只会用到两个值,一个是-1,也就是P操作,它等待信号量变为可用,一个是+1,也就是V操作,它发送信号表示信号量现在已可用) */
short sem_flg; /* 操作选项 */
}
nsops:结构体数组元素个数
4、 获取或设置信号量的相关信息
int semctl(int semid, int semnum, int cmd, ...);
semid:信号量ID
semmum:信号量元素序号(当需要用到成组的信号量是,就需要用到这个数,它一般取值为0,表示这是第一个也是唯一一个信号量)
cmd:将要采取的动作
两个常用的值
SETVAL:用来把信号量初始化为一个已知的值,这是通过union semun中的val成员设置,其作用是咋信号量第一次使用之前对它进行设置
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符
线程
1、概念
线程是系统调度的最小单位。
进程资源(进程间数据独立) 线程资源(一个进程中的多个子线程是共用进程资源)(数据段, 堆)线程有自己的独立栈空间
2、创建一条新的线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
thread:新线程的TID 用pthread_t ID名来创建
attr:线程属性(线程属性如果为NULL,则会创建一个标准属性的线程,)
start_routine:线程例程(线程例程指的是如果线程创建成功,那么该线程会立即去执行的函数)
arg:线程的例程参数
返回值
成功:0
失败:errno
代码
struct student
{
char name[20];
int id;
int phone;
};
void * function(void *arg)
{
struct student *al = ((struct student *)arg);
while(1)
{
printf("name =%s id = %d phone = %d\n",al->name,al->id,al->phone);
sleep(1);
}
}
int main(int argc, char **argv)
{
pthread_t pthread;
struct student arg;
strcpy(arg.name,"wangyisi");
arg.id = 883;
arg.phone = 123456;
int ret = pthread_create(&pthread,NULL,function,(void *)&arg);
if(ret !=0)
{
perror("pthread_ceate fail\n");
exit(1);
}
while(1)
{
printf("wait----\n");
sleep(1);
}
return 0;
}
3、线程属性函数
线程属性变量的使用步骤
(1)定义线程属性变量,并使用pthread_attr_init初始化
int pthread_attr_init(pthread_attr_t *attr);
(2)使用pthread_attr_setXXX来设置相关的属性
(3)使用该线程属性变量创建相应的线程
(4)使用pthread_attr_destroy()销毁该线程属性变量
int pthread_attr_destroy(pthread_attr_t *attr);
设置线程的分离属性
1、一条线程如果可接合,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸进程,同时意味着该线程退出值可以 被 其他线程取代,因此,如果不需要某条线程的退出值,那最好将线程设置为分离状态以保证该线程不会成为僵尸线程
2、pthread_detach(id);
3、pthread_attr_setdetachstate
参数
attr:线程属性变量
detachstate
PTHREAD_CREATE_DETACHED:分离
PTHREAD_CREATE_JOINABLE:接合
代码
#include <stdio.h>
#include <pthread.h>
void *routine(void *arg)
{
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,
PTHREAD_CREATE_DETACHED);
pthread_t tid;
int i=0;
for(i=0; i<10; i++)
{
pthread_create(&tid, &attr, routine, NULL);
}
pause();
return 0;
}
4、线程的退出
void pthread_exit(void *retval);
retval:线程退出值
5、 取消一条线程
(1)int pthread_cancel(pthread_t thread);
给指定的线程发送一条取消请求
(2)thread:线程TID
(3)设置取消状态
int pthread_setcancelstate(int state, int *oldstate); int pthread_setcanceltype(int type, int *oldtype);
参数
state新的取消状态
PTHREAD_CANCEL_ENABLE:使能取消请求
PTHREAD_CANCEL_DISABLE:关闭取消请求
oldstate:旧的取消请求
type:新的取消类型
PTHREAD_CANCEL_DEFERRED:延时响应
PTHREAD_CANCEL_ASYNCHRONOUS:立即响应
oldtype:旧的取消类型
5、线程资源回收
int pthread_join(pthread_t thread, void **retval);
thread:线程TID
retval:储存线程退出值的内存的指针
该函数要注意的几点
如果线程退出是没有退出值,retval可以指定为NULL
pthread_join()指定的线程如果还在运行,那么它将会阻塞等待
代码1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void * function(void *arg)
{
int i = 0;
while(i < 10)
{
printf("%d \n",i++);
sleep(1);
}
}
int main(int argc, char **argv)
{
pthread_t pthread;
int res = pthread_create(&pthread,NULL,function,NULL);
if(res != 0)
{
perror("create failed\n");
exit(1);
}
pthread_join(pthread,NULL);
printf("over\n");
return 0;
}
代码2(将线程里的值返回到线程)
#include <stdio.h>
#include <pthread.h>
void *routine(void *arg)
{
char *s = (char *)arg;
printf("argument: %s", s);
sleep(1);
pthread_exit("Bye-Bye!\n");
}
int main(int argc, char **argv)
{
pthread_t tid;
pthread_create(&tid, NULL, routine, (void *)"testing string\n");
void *p;
pthread_join(tid, &p);
printf("exit value: %s", (char *)p);
return 0;
}
6、 线程互斥锁
(1)互斥锁安装帮助文档
sudo apt-get install manpsges
sudo apt-get install manpsges manpsges-dev manpages-posix-dev
(2)初始化互斥锁
int pthread_mutex_init (pthread_mutex_t *__mutex,const pthread_mutexattr_t *__mutexattr)
(3) 销毁
int pthread_mutex_destroy (pthread_mutex_t *__mutex)
(4)尝试锁
int pthread_mutex_trylock (pthread_mutex_t *__mutex)
(5)上锁
int pthread_mutex_lock (pthread_mutex_t *__mutex)
(6)解锁
int pthread_mutex_unlock (pthread_mutex_t *__mutex)
(7)代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
pthread_mutex_t m;
void * function(void *arg)
{
char *msg = (char *)arg;
pthread_mutex_lock(&m);
while(*msg != '\0')
{
fprintf(stderr,"%c",*msg);
usleep(100);
msg++;
}
pthread_mutex_unlock(&m);
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pthread_mutex_init(&m,NULL);
pthread_t t1,t2;
pthread_create(&t1,NULL,function,"AAAAAAAAAAAAAAAAAAAAA");
pthread_create(&t2,NULL,function,"BBBBBBBBBBBBBBBBBBBBB");
pthread_exit(NULL);
return 0;
}
7、POSIX信号量
A、 POSIX有名信号量
POSIX有名信号量使用步骤
(1)使用sem_open()来创建或打开一个有名信号量
sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
参数
name:信号量的名字,必须翼正斜杠“/”开头
oflag:
O_CREATE:如果该名字对应的信号量不存在,则创建
O_EXCL:如果该名字对应的信号量已存在,则报错
mode:八进制读写权限,0666
value:初始化
(2)使用sem_wait()和sem_post()来分别进行P操作和V操作
int sem_wait(sem_t *sem);将信号量的值减1
int sem_post(sem_t *sem);将信号量的值加1
(3)使用sem_close()来关闭它
int sem_close(sem_t *sem);
sem:信号量指针
(4)使用sem_unlink()来删除它,并释放系统资源
int sem_unlink(const char *name);
name:信号量名字
B、 POSIX匿名信号量
使用步骤
(1)在这些线程都能访问到的区域定义这种变量(如全局变量)类型搜索sem_t
(2)在任何线程使用它之前,用sem_init()初始化它
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数
sem:信号量指针
pshared:该信号量的作用范围:0为线程间,非0为进程间
value:初始值
(3)使用sem_wait()/sem_trywait()和sem_post()来分别进行P操作和V操作
(4)不需要时,使用sem_destroy()来销毁
int sem_destroy(sem_t *sem);
代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
sem_t space,data;
void * function(void *arg)
{
char *buf = (char *)arg;
while(1)
{
sem_wait(&data);//等待有数据,就往里面读
printf("bytes:%d\n",strlen(buf));
sem_post(&space);//读完后空间就加1
}
}
int main(int argc, char **argv)
{
sem_init(&space,0,1);
sem_init(&data,0,0);
char buf[32];
pthread_t tid;
pthread_create(&tid,NULL,function,(void *)buf);
while(1)
{
sem_wait(&space);//等待有空间就往内存写数据
bzero(buf,32);
fgets(buf,32,stdin);
sem_post(&data);//数据就加1
}
return 0;
}
C、使用情况
进程间通信用到同步用到有名信号量,线程间通信同步用到匿名信号量
8、线程条件变量
(1)初始化条件变量
pthread_cond_init(pthread_cond_t *restrict,const pthread_condaddr_t *restrict attr)
cond :条件变量 可以用pthread_cond_t+名
attr:条件变量的属性,一般始终为0
(2)销毁条件变量
pthread_cond_destroy(pthread_cond_t *cond)
cond :条件变量 可以用pthread_cond_t+名
(3)等待条件资源
pthread_cond_wait(pthread_cond_t *restrict,pthread_mutex_t *restrict mutex)
cond :条件变量 可以用pthread_cond_t+名
murex:互斥锁
(4) 发送信号资源
pthread_cond_signal(pthread_cond_t *cond)
广播唤醒信号(唤醒所有等待线程)
pthread_cond_broadcast(pthread_cond_t *cond)