1 进程创建
// pid_t fork(void);
//函数作用 :用于创建子进程
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
int main(){
pid_t pid = fork();
if(pid==0){
//返回的是子进程
printf("子进程\n");
}
else if(pid>0){
printf("父进程");
}
return 0;
}
注意
//如果当前代码执行在子进程中,pid将会是0,因为fork在子进程中返回0。
//如果当前代码执行在父进程中,pid将会是子进程的进程ID,一个大于0的值。
2 匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用来进程间通信。
参数:int pipefd[2] 这个数组是一个传出参数。
pipefd[0] 对应的是管道的读端
pipefd[1] 对应的是管道的写端
返回值:
成功 0
失败 -1
管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
// 子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
printf("i am parent process, pid : %d\n", getpid());
// 关闭写端
close(pipefd[1]);
// 从管道的读取端读取数据
char buf[1024] = {0};
while(1) {
int len = read(pipefd[0], buf, sizeof(buf));
printf("parent recv : %s, pid : %d\n", buf, getpid());
// 向管道中写入数据
//char * str = "hello,i am parent";
//write(pipefd[1], str, strlen(str));
//sleep(1);
}
} else if(pid == 0){
// 子进程
printf("i am child process, pid : %d\n", getpid());
// 关闭读端
close(pipefd[0]);
char buf[1024] = {0};
while(1) {
// 向管道中写入数据
char * str = "hello,i am child";
write(pipefd[1], str, strlen(str));
//sleep(1);
// int len = read(pipefd[0], buf, sizeof(buf));
// printf("child recv : %s, pid : %d\n", buf, getpid());
// bzero(buf, 1024);
}
}
return 0;
}
子进程将标准输出 stdout_fileno 重定向到管道的写端如何操作
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>
int main() {
// 创建一个管道
int fd[2];
int ret = pipe(fd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
// 关闭写端
close(fd[1]);
// 从管道中读取
char buf[1024] = {0};
int len = -1;
while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {
// 过滤数据输出
printf("%s", buf);
memset(buf, 0, 1024);
}
wait(NULL);
} else if(pid == 0) {
// 子进程
// 关闭读端
close(fd[0]);
// 文件描述符的重定向 stdout_fileno -> fd[1] 意思就是把应该输入到 文件控制面板的数据给重定向到 写端
dup2(fd[1], STDOUT_FILENO);
// 执行 ps aux
execlp("ps", "ps", "aux", NULL);
perror("execlp");
exit(0);
} else {
perror("fork");
exit(0);
}
return 0;
}
3 execlp
#include <unistd.h>
int execlp(const char *file, const char *arg, ... );
- 会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。
- 参数:
- file:需要执行的可执行文件的文件名
a.out
ps
- arg:是执行可执行文件所需要的参数列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的的参数列表。
参数最后需要以NULL结束(哨兵)
- 返回值:
只有当调用失败,才会有返回值,返回-1,并且设置errno
如果调用成功,没有返回值。
4 wait/waitpid
wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:等待任意一个子进程结束,如果任意一个子进程结束了,次函数会回收子进程的资源。
参数:int *wstatus
进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
返回值:
- 成功:返回被回收的子进程的id
- 失败:-1 (所有的子进程都结束,调用函数失败)
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.
waitpid
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收指定进程号的子进程,可以设置是否阻塞。
参数:
- pid:
pid > 0 : 某个子进程的pid
pid = 0 : 回收当前进程组的所有子进程
pid = -1 : 回收所有的子进程,相当于 wait() (最常用)
pid < -1 : 某个进程组的组id的绝对值,回收指定进程组中的子进程
该函数的第二个参数 int *wstatus 是一个指向整数的指针,用于存储子进程的终止状态。
- options:设置阻塞或者非阻塞
0 : 阻塞
WNOHANG : 非阻塞
- 返回值:
> 0 : 返回子进程的id
= 0 : options=WNOHANG, 表示还有子进程存在 wnohang
= -1 :错误,或者没有子进程了
5 如何设置非阻塞
我要知道的是设置非阻塞是对我们的文件描述符进行操作
设置管道非阻塞
#include <fcntl.h>
int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag (获得就用get)
flags |= O_NONBLOCK; // 修改flag的值
fcntl(fd[0], F_SETFL, flags); // 设置新的flag (设置就用set)
6 有名管道
创建fifo文件
1.通过命令: mkfifo 名字
2.通过函数:int mkfifo(const char *pathname, mode_t mode);
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
- pathname: 管道名称的路径
- mode: 文件的权限 和 open 的 mode 是一样的
是一个八进制的数
返回值:成功返回0,失败返回-1,并设置错误号
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
// 判断文件是否存在
int ret = access("fifo1", F_OK);
if(ret == -1) {
printf("管道不存在,创建管道\n");
ret = mkfifo("fifo1", 0664);
if(ret == -1) {
perror("mkfifo");
exit(0);
}
}
return 0;
}
7 内存映射
可以理解为 我们通过内存映射出来的 指针直接对 文件或者数据进行操作 (我不知道为什么要内存映射先记住吧)
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
- 功能:将一个文件或者设备的数据映射到内存中
- 参数:
- void *addr: NULL, 由内核指定
- length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。
获取文件的长度:stat 或者 lseek
获取的方法是什么
int fd = open("test.txt",O_RDWR);
int size = lseek(fd,0,SEEK_END);
对参数的解释
-
fd
:这是一个文件描述符,它是open
函数返回的一个整数,代表了进程和打开文件之间的连接。当文件被打开时,系统会分配一个唯一的文件描述符,程序通过这个文件描述符来进行读写操作。 -
0
:这是offset
参数,它表示相对于whence
参数指定的位置的偏移量。在这里,尽管它的值是0,但是由于whence
参数设置为SEEK_END
,这个值实际上被忽略。 -
SEEK_END
:这是whence
参数,它定义了offset
应该如何解释。SEEK_END
表示相对于文件的末尾进行偏移。因此,当whence
是SEEK_END
且offset
是0时,lseek
函数会将文件指针定位到文件的末尾。
- prot : 对申请的内存映射区的操作权限 用于表示“protection”,即“保护”或“权限”
-PROT_EXEC :可执行的权限
-PROT_READ :读权限
-PROT_WRITE :写权限
-PROT_NONE :没有权限
要操作映射内存,必须要有读的权限。
PROT_READ|PROT_WRITE
- flags :
- MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项(map shared)
- MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)
- fd: 需要映射的那个文件的文件描述符
- 通过open得到,open的是一个磁盘文件
- 注意:文件的大小不能为0,open指定的权限不能和prot参数有冲突。
prot: PROT_READ open:只读/读写
prot: PROT_READ | PROT_WRITE open:读写
- offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不偏移。
- 返回值:返回创建的内存的首地址
失败返回MAP_FAILED,(void *) -1
int munmap(void *addr, size_t length);
- 功能:释放内存映射
- 参数:
- addr : 要释放的内存的首地址
- length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。
题目 将我们一个文件的内容拷贝到另一个文件当中,我们运用的就是内存映射
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
//思路 首先我们需要给当前文件创建内存映射
//创建一个新的文件 设置其信息 也进行 内存映射
//复制就行
int main() {
// 1.对原始的文件进行内存映射
int fd = open("english.txt", O_RDWR);
if(fd == -1) {
perror("11open");
exit(0);
}
// 获取原始文件的大小
int len = lseek(fd, 0, SEEK_END);
printf("%d",len);
// 2.创建一个新文件(拓展该文件)
int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);
if(fd1 == -1) {
perror("11111open");
exit(0);
}
// 对新创建的文件进行拓展
truncate("cpy.txt", len); //将文件扩展到指定长度
//如果文件的大小超过了指定的长度,则超过的部分会被丢弃;
//如果文件的大小小于指定的长度,则文件会被扩展,扩展部分的内容通常是空字节(即’\0’)。
// write(fd1, " ", 1);
// 3.分别做内存映射
void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
void * ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);
if(ptr == MAP_FAILED) {
perror("mmap");
exit(0);
}
if(ptr1 == MAP_FAILED) {
perror("mmap");
exit(0);
}
// 内存拷贝
memcpy(ptr1, ptr, len);
// 释放资源
munmap(ptr1, len);
munmap(ptr, len);
close(fd1);
close(fd);
return 0;
}
8 闹钟设置
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
- 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
函数会给当前的进程发送一个信号:SIGALARM
- 参数:
seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
取消一个定时器,通过alarm(0)。
- 返回值:
- 之前没有定时器,返回0
- 之前有定时器,返回之前的定时器剩余的时间
- SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
alarm(10); -> 返回0
过了1秒
alarm(5); -> 返回9
注意 该函数是不阻塞的
#include <stdio.h>
#include <unistd.h>
int main() {
int seconds = alarm(5);
printf("seconds = %d\n", seconds); // 0
sleep(2);
seconds = alarm(2); // 不阻塞
printf("seconds = %d\n", seconds); // 3
while(1) {
}
return 0;
}
// 第一次调用 alarm(5):
// 设置了一个5秒的闹钟。
// 因为之前没有设置闹钟,所以返回值是0。
// 输出 “seconds = 0\n”:
// 这是因为第一次调用 alarm 时没有之前的闹钟设置,因此返回值是0。
// 调用 sleep(2):
// 进程睡眠2秒。此时,由于之前设置了5秒的闹钟,闹钟计数减少2秒,还剩3秒。
// 第二次调用 alarm(2):
// 重置了闹钟,将其设置为2秒。
// 因为第一次设置的闹钟还有3秒剩余,所以这次调用返回了剩余的3秒。
// 输出 “seconds = 3\n”:
// 这是因为第二次调用 alarm 时返回了第一次闹钟剩余的秒数,即3秒。
8 setitimer(代替alarm)
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
- 功能:设置定时器(闹钟)。可以替代alarm函数。精度微秒us,可以实现周期性定时
- 参数:
- which : 定时器以什么时间计时
ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM 常用 itimer_real
ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM
ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF
- new_value: 设置定时器的属性
struct itimerval { // 定时器的结构体
struct timeval it_interval; // 每个阶段的时间,间隔时间
struct timeval it_value; // 延迟多长时间执行定时器
};
struct timeval { // 时间的结构体
time_t tv_sec; // 秒数
suseconds_t tv_usec; // 微秒
};
过10秒后(it_value),每个2秒(it_iterval)定时一次
- old_value :记录上一次的定时的时间参数,一般不使用,指定NULL
- 返回值:
成功 0
失败 -1 并设置错误号
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
// 过3秒以后,每隔2秒钟定时一次
int main() {
struct itimerval new_value;
// 设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
// 设置延迟的时间,3秒之后开始第一次定时
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
printf("定时器开始了...\n");
if(ret == -1) {
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
9 signal(设置某个信号的捕捉行为)
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- 功能:设置某个信号的捕捉行为
- 参数:
- signum: 要捕捉的信号 比如 9
- handler: 捕捉到信号要如何处理
- SIG_IGN : 忽略信号
- SIG_DFL : 使用信号默认的行为
- 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
回调函数:
- 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
- 不是程序员调用,而是当信号产生,由内核调用
- 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。
- 返回值:
成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
失败,返回SIG_ERR,设置错误号
SIGKILL SIGSTOP不能被捕捉,不能被忽略。
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void myalarm(int num) {
printf("捕捉到了信号的编号是:%d\n", num);
printf("xxxxxxx\n");
}
// 过3秒以后,每隔2秒钟定时一次
int main() {
//设置回调函数
signal(SIGALRM, myalarm);
struct itimerval new_value;
// 设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
// 设置延迟的时间,3秒之后开始第一次定时
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
printf("定时器开始了...\n");
if(ret == -1) {
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
10 sigaction(跟signal一样但是推荐用这个)
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
- 功能:检查或者改变信号的处理。信号捕捉
- 参数:
- signum : 需要捕捉的信号的编号或者宏值(信号的名称)
- act :捕捉到信号之后的处理动作
- oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL
- 返回值:
成功 0
失败 -1
struct sigaction {
// 函数指针,指向的函数就是信号捕捉到之后的处理函数
void (*sa_handler)(int);
// 不常用
void (*sa_sigaction)(int, siginfo_t *, void *);
// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。 默认是空
sigset_t sa_mask;
// 使用哪一个信号处理对捕捉到的信号进行处理
// 这个值可以是0,表示使用sa_handler; 也可以是SA_SIGINFO表示使用sa_sigaction
int sa_flags;
// 被废弃掉了
void (*sa_restorer)(void);
};
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void myalarm(int num) {
printf("捕捉到了信号的编号是:%d\n", num);
printf("xxxxxxx\n");
}
// 过3秒以后,每隔2秒钟定时一次
int main() {
struct sigaction act;
act.sa_flags = 0; //表示使用sa_handler
act.sa_handler = myalarm;
sigemptyset(&act.sa_mask);//清空临时阻塞信号集
//明确意图:通过显式地清空 sa_mask,您可以清楚地表达出在信号处理函数 myalarm 执行期间,没有额外的信号应该被阻塞。
//这意味着除了正在处理的信号(在这个例子中是 SIGALRM),其他所有信号都可以正常到达并被处理。
//如果有一些信号被临时阻塞那我们程序就不会继续运行 所以要清空
// 注册我们要捕捉的信号
sigaction(SIGALRM, &act, NULL);
//设置我们的计时器
struct itimerval new_value;
// 设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
// 设置延迟的时间,3秒之后开始第一次定时
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
printf("定时器开始了...\n");
if(ret == -1) {
perror("setitimer");
exit(0);
}
while(1);
return 0;
}
11 sigset (阻塞信号集)
注意 加入信号集之后就代表的是阻塞;因为我们信号集代表的是阻塞信号集
以下信号集相关的函数都是对自定义的信号集进行操作。
int sigemptyset(sigset_t *set);
- 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
- 参数:set,传出参数,需要操作的信号集
- 返回值:成功返回0, 失败返回-1
int sigfillset(sigset_t *set);
- 功能:将信号集中的所有的标志位置为1
- 参数:set,传出参数,需要操作的信号集
- 返回值:成功返回0, 失败返回-1
int sigaddset(sigset_t *set, int signum);
- 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
- 参数:
- set:传出参数,需要操作的信号集
- signum:需要设置阻塞的那个信号
- 返回值:成功返回0, 失败返回-1
int sigdelset(sigset_t *set, int signum);
- 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
- 参数:
- set:传出参数,需要操作的信号集
- signum:需要设置不阻塞的那个信号
- 返回值:成功返回0, 失败返回-1
int sigismember(const sigset_t *set, int signum);
- 功能:判断某个信号是否阻塞
- 参数:
- set:需要操作的信号集
- signum:需要判断的那个信号
- 返回值:
1 : signum被阻塞
0 : signum不阻塞
-1 : 失败
#include <signal.h>
#include <stdio.h>
int main() {
// 创建一个信号集
sigset_t set;
// 清空信号集的内容
sigemptyset(&set);
// 判断 SIGINT 是否在信号集 set 里
int ret = sigismember(&set, SIGINT);
if(ret == 0) {
printf("SIGINT 不阻塞\n");
} else if(ret == 1) {
printf("SIGINT 阻塞\n");
}
// 添加几个信号到信号集中
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
// 判断SIGINT是否在信号集中
ret = sigismember(&set, SIGINT);
if(ret == 0) {
printf("SIGINT 不阻塞\n");
} else if(ret == 1) {
printf("SIGINT 阻塞\n");
}
// 判断SIGQUIT是否在信号集中
ret = sigismember(&set, SIGQUIT);
if(ret == 0) {
printf("SIGQUIT 不阻塞\n");
} else if(ret == 1) {
printf("SIGQUIT 阻塞\n");
}
// 从信号集中删除一个信号
sigdelset(&set, SIGQUIT);
// 判断SIGQUIT是否在信号集中
ret = sigismember(&set, SIGQUIT);
if(ret == 0) {
printf("SIGQUIT 不阻塞\n");
} else if(ret == 1) {
printf("SIGQUIT 阻塞\n");
}
return 0;
}
11 sigprocmask (将阻塞加入内核,什么是未决?)
什么是未决信号集:未决信号集(Pending Signal Set) 是一个内核数据结构,它记录了已经发送给进程但尚未被处理的所有信号; (比如阻塞信号就是 我们发送给进程了 但是他阻塞在哪里就会放在未决信号当中去 )
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- 功能:将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
- 参数:
- how : 如何对内核阻塞信号集进行处理
SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中,内核中原来的数据不变
假设内核中默认的阻塞信号集是mask, mask | set
SIG_UNBLOCK: 根据用户设置的数据,对内核中的数据进行解除阻塞
mask &= ~set
SIG_SETMASK:覆盖内核中原来的值
- set :已经初始化好的用户自定义的信号集
- oldset : 保存设置之前的内核中的阻塞信号集的状态,可以是 NULL
- 返回值:
成功:0
失败:-1
设置错误号:EFAULT、EINVAL
int sigpending(sigset_t *set);
- 功能:获取内核中的未决信号集
- 参数:set,传出参数,保存的是内核中的未决信号集中的信息。
- 返回值:
成功:0
失败:-1
// 编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
//这里代码就是作用是 首先我们设置我们的 2 3 信号变成阻塞状态(2号 ctrl + c 本来作用是结束进程 但是我们设置它在进行crtl+ c之后变成 阻塞状态)
//查看他的未决状态信息
int main() {
// 设置2、3号信号阻塞
//创建信号集
sigset_t set;
sigemptyset(&set);//该函数的作用是初始化一个信号集为空集,即不包含任何信号。
// 将2号和3号信号添加到信号集中
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
// 修改内核中的阻塞信号集
sigprocmask(SIG_BLOCK, &set, NULL); //将我们阻塞信号集加入内核当中去
int num = 0;
while(1) {
num++;
// 获取当前的未决信号集的数据
sigset_t pendingset;
sigemptyset(&pendingset);
sigpending(&pendingset);//功能:获取内核中的未决信号集
// 遍历前32位
for(int i = 1; i <= 31; i++) {
if(sigismember(&pendingset, i) == 1) {//如果信号 i 存在于pendingset 指向的信号集中,则返回 1。
//如果信号 signum 不存在于 pendingset 指向的信号集中,则返回0。
printf("1");
}else if(sigismember(&pendingset, i) == 0) {
printf("0");
}else {
perror("sigismember");
exit(0);
}
}
printf("\n");
sleep(1);
if(num == 10) {
// 解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
}
return 0;
}
12 共享内存
共享内存相关的函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
- 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。
新创建的内存段中的数据都会被初始化为0
- 参数:
- key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。
一般使用16进制表示,非0值
- size: 共享内存的大小
- shmflg: 属性
- 访问权限
- 附加属性:创建/判断共享内存是不是存在
- 创建:IPC_CREAT
- 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用
IPC_CREAT | IPC_EXCL | 0664 (0664这是一个权限的意思)
- 返回值:
失败:-1 并设置错误号
成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 功能:和当前的进程进行关联
- 参数:
- shmid : 共享内存的标识(ID),由shmget返回值获取
- shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定
- shmflg : 对共享内存的操作
- 读 : SHM_RDONLY, 必须要有读权限 rdonly
- 读写: 0
- 返回值:
成功:返回共享内存的首(起始)地址。 失败(void *) -1
int shmdt(const void *shmaddr);
- 功能:解除当前进程和共享内存的关联
- 参数:
shmaddr:共享内存的首地址
- 返回值:成功 0, 失败 -1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。
- 参数:
- shmid: 共享内存的ID
- cmd : 要做的操作
- IPC_STAT : 获取共享内存的当前的状态
- IPC_SET : 设置共享内存的状态
- IPC_RMID: 标记共享内存需要被销毁
- buf:需要设置或者获取的共享内存的属性信息
- IPC_STAT : buf存储数据
- IPC_SET : buf中需要初始化数据,设置到内核中
- IPC_RMID : 没有用,NULL
写端
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main() {
// 1.创建一个共享内存
int shmid = shmget(100, 4096, IPC_CREAT|0664);
printf("shmid : %d\n", shmid);
// 2.和当前进程进行关联
void * ptr = shmat(shmid, NULL, 0);
char * str = "helloworld";
// 3.写数据
memcpy(ptr, str, strlen(str) + 1);
printf("按任意键继续\n");
getchar();
// 4.解除关联
shmdt(ptr);
// 5.删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
读端
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main() {
// 1.获取一个共享内存
int shmid = shmget(100, 0, IPC_CREAT);
printf("shmid : %d\n", shmid);
// 2.和当前进程进行关联
void * ptr = shmat(shmid, NULL, 0);
// 3.读数据
printf("%s\n", (char *)ptr);
printf("按任意键继续\n");
getchar();
// 4.解除关联
shmdt(ptr);
// 5.删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
13 守护进程
它通常在系统启动时启动,并在系统关闭时终止。守护进程通常没有控制终端,它们在后台独立运行,不与任何用户会话关联。
写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。
#include<stdio.h>
#include<sys/types.h>//fork setsid open
#include<unistd.h>//fork exit setsid write close
#include<sys/stat.h> //open
#include<fcntl.h> //open
#include<sys/time.h> //setitimer
#include<signal.h> //sigaction
#include<time.h> //localtime
#include<string.h> //strlen
#include<stdlib.h>//exit
//我们想要不断读取时间到固定文件 一直读取那种
void work(int num){
time_t tm = time(NULL);
struct tm * loc = localtime(&tm);
char * s = asctime(loc);
int fd = open("time5.txt",O_RDWR|O_CREAT|O_APPEND,0664);
//注意 open怎么使用
write(fd,s,strlen(s));
close(fd);
}
int main(){
//首先创建一个进程
pid_t pid = fork();
//如果是父进程 直接结束 只有子进程才可以继续
if(pid>0){
exit(0);
}
//子进程重新设置回话
setsid();
//设置掩码
umask(022); //umask设置仅影响当前进程及其后续创建的子进程。
// 第一位数字0对应用户(user/owner)权限,表示不屏蔽任何权限,因此用户拥有全部权限(读、写、执行)。
// 第二位数字2对应组(group)权限,2在八进制表示法中是010,表示禁用写(write)权限,因此组用户只有读和执行权限。
// 第三位数字2对应其他(others)权限,同样表示禁用写权限,因此其他用户也只有读和执行权限
//修改进程的当前目录;
chdir("/home/it/");//防止文件被删除
//关闭标准输入输出错误的描述符号(把本来应该在控制台产生的放在了我们固定的这个文件/dev/null)
int fd = open("/dev/null/",O_RDWR);
//把标准输入输出等重定向到这个文件
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
//开始核心业务逻辑
//我们要更改定时器 输出位置
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work;
sigemptyset(&act.sa_mask);//把阻塞信号弄成空
sigaction(SIGALRM, &act, NULL);
struct itimerval val;
val.it_interval.tv_sec = 2;
val.it_interval.tv_usec = 0;
val.it_value.tv_sec = 2;
val.it_value.tv_usec = 0;
//首先我们要设置定时器
setitimer(ITIMER_REAL,&val,NULL);
while(1){
sleep(10);
}
return 0;
}
守护进程(Daemon)需要重新设置会话(Session)的原因是为了确保它们能够独立于任何用户会话运行,从而在后台持久地执行任务。
----------------------------------------------------------------------------------------------------------------------------
多线程章节
1 pthread_create
一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程
称之为子线程。
程序中默认只有一个进程,fork()函数调用,2个进程
程序中默认只有一个线程,pthread_create()函数调用,2个线程。
#include <pthread.h>
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
失败:返回错误号。这个错误号和之前errno不太一样。
获取错误号的信息: char * strerror(int errnum);
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void * callback(void * arg) {
printf("child thread...\n");
printf("arg value: %d\n", *(int *)arg);
return NULL;
}
int main() {
pthread_t tid;
int num = 10;
// 创建一个子线程
int ret = pthread_create(&tid, NULL, callback, (void *)&num);
if(ret != 0) {
char * errstr = strerror(ret);
printf("error : %s\n", errstr);
}
for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}
sleep(1);
return 0; // exit(0);
}
2 mutex (互斥锁)
互斥量的类型 pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
- 初始化互斥量
- 参数 :
- mutex : 需要初始化的互斥量变量
- attr : 互斥量相关的属性,NULL
- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。
pthread_mutex_t *restrict mutex = xxx;
pthread_mutex_t * mutex1 = mutex;
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 释放互斥量的资源
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 尝试加锁,如果加锁失败,不会阻塞,会直接返回。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 解锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 全局变量,所有的线程都共享这一份资源。
int tickets = 1000;
// 创建一个互斥量
pthread_mutex_t mutex;
void * sellticket(void * arg) {
// 卖票
while(1) {
// 加锁
pthread_mutex_lock(&mutex);
if(tickets > 0) {
usleep(6000);
printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);
tickets--;
}else {
// 解锁
pthread_mutex_unlock(&mutex);
break;
}
// 解锁
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main() {
// 初始化互斥量
pthread_mutex_init(&mutex, NULL);
// 创建3个子线程
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, sellticket, NULL);
pthread_create(&tid2, NULL, sellticket, NULL);
pthread_create(&tid3, NULL, sellticket, NULL);
// 回收子线程的资源,阻塞
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_exit(NULL); // 退出主线程
// 释放互斥量资源
pthread_mutex_destroy(&mutex);
return 0;
}
3 信号量 (sem)
信号量的类型 sem_t
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 初始化信号量
- 参数:
- sem : 信号量变量的地址
- pshared : 0 用在线程间 ,非0 用在进程间
- value : 信号量中的值
int sem_destroy(sem_t *sem);
- 释放资源
int sem_wait(sem_t *sem);
- 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_post(sem_t *sem);
- 对信号量解锁,调用一次对信号量的值+1
int sem_getvalue(sem_t *sem, int *sval);
sem_t psem;
sem_t csem;
init(psem, 0, 8);
init(csem, 0, 0);
producer() {
sem_wait(&psem);
sem_post(&csem)
}
customer() {
sem_wait(&csem);
sem_post(&psem)
}
4 条件变量(cond)
条件变量(不是锁)的类型 pthread_cond_t
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 等待,调用了该函数,线程会阻塞。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
int pthread_cond_signal(pthread_cond_t *cond);
- 唤醒一个或者多个等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
- 唤醒所有的等待的线程