1.open函数
open函数: 用来打开或者创建一个文件或者设备。
1.函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode)
2.参数:
- pathname:指定需要打开的文件的路径
- flags:flags 是通过 O_RDONLY, O_WRONLY 或 O_RDWR (指明文件是以只读 , 只写或 读写方式打开的) 与 下面的 零个 或 多个 可选模式 按位或操作符 得到的:
- O_CREATE:如果文件不存在就创建一个新文件。
- O_EXCL: 通过 O_CREATE, 生成 文件 , 若 文件 已经 存在 , 则 open出错 , 调用失败.这个O_EXCL与O_CREATE一起使用。
- O_TRUNC:如果文件已经存在,并且该文件是可写的,那么将会设置该文件的长度为0(清空文件的内容)。
- O_APPEND:文件以追加模式打开,在写之前,文件读写指针被置末尾。
- mode:参数mode指明文件使用的权限
3.返回值:
返回 一个 新的 文件描述符 (若是 有 错误 发生 返回 -1 ,并在 errno 设置 错误信息).
使用见下例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//在指定路径下创建一个新文件
int fd1 = open("./helloworld1.txt",O_CREAT | O_RDWR | O_TRUNC,0664);
printf("%d\n",fd1);
//在指定路径下创建一个新文件
int fd2 = creat("./helloworld2.txt",0664);
printf("%d\n",fd2);
//在指定路径下创建一个新文件
int fd3 = open("./helloworld3.txt",O_CREAT | O_RDWR | O_TRUNC);
printf("%d\n",fd3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
这样的话运行可执行程序。运行结果如下:
3
4
5
这里我们也可以看出文件描述符的取值最小从3开始,0,1,2都已经被使用。
我们再来看一个例子:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
close(1);
printf("Hello");
int fd = open("./log",O_CREAT | O_RDWR);
printf("%d\n",fd);
return 0;
}
运行成功后查看log文件的内容发现Hello和fd的值写进log文件中。fd的值为1.并且永远取 未用描述符的最小值
接下来看这个例子:
my_touch.c如下:
// 简单实现touch命令
#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <unistd.h>
int main(int argc,char* args[])
{
if(argc < 2)
{
printf("touch: 缺少了文件操作数\nTry 'touch --help' for more information.\n");
}
int i;
for(i = 1 ; i < argc ; i++)
{
int fd = open(args[i],O_RDONLY | O_CREAT,0644);
close(fd);
}
return 0;
}
编译成功后运行可执行程序:
[xiaocer@localhost practice]$ gcc my_touch.c -o touch
##这样我们就可以使用touch创建文件,类似于Linux中的touch命令
[xiaocer@localhost practice]$ touch 1.c 2.c 3.c
2.close函数
close函数: 关闭一个文件描述符, 使它 不在 指向 任何 文件 和 可以 在 新的 文件 操作 中该文件描述符可以 被 再次使用.
1.函数原型: int close(int fd);
2.参数:
fd:文件描述符
3.返回值:
返回0表示成功,返回-1表示失败。
3.write函数
write函数: write向文件描述符fd所引用的文件中写入从buf开始的缓冲区中 count个字节的数据.POSIX规定,当使用了write()之后再使用read(),那么读取到的应该是更新后的数据.
函数原型:
ssize_t write(int fd, const void *buf, size_t count);
函数参数:
- fd:文件描述符
- buf:缓冲区
- count:字节数
函数返回值:
成功时返回所写入的字节数(若为零则表示没有写入数据).错误时返回-1,并置errno为相应值.若count为零,对于普通文件无任何影响,但对特殊文件 将产生不可预料的后果.
即成功时返回写入的字计数;
失败时返回-1并设置errno;
返回0表示没有数据写入。
4.read函数
read函数:
read() 从文件描述符 fd 中读取 count 字节的数据并放入从 buf 开始的缓冲区中. 如果 count 为零,read()返回0,不执行其他任何操作. 如果 count 大于SSIZE_MAX,那么结果将不可预料.
函数原型:
ssize_t read(int fd, void *buf, size_t count);
函数参数:
- fd:文件描述符
- buf:缓冲区
- count:字节数
函数返回值:
成功时返回读取的字计数;
失败时返回-1,并设置errno的值;
读到文件末尾的话就返回0.
简单模拟cat命令实现代码如下
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* args[])
{
if(argc < 2)
{
while(scanf("%s",args[2]))
{
printf("%s\n", args[2]);
}
}
int i;
int ret = 1;
for(i = 1 ; i < argc ; i++ )
{
int fd;
fd = open(args[i],O_RDONLY);
char buff[1024];
ret = read(fd,buff,sizeof(buff));
if(ret != -1)
{
write(1,buff,ret);
}
close(fd);
}
return 0;
}
运行编译生成的可执行程序可以打开存在的小文件。
5.lseek函数
lseek函数: The lseek() function repositions(改变什么位置) the offset of the open file associated with the file descriptor fd to the argument offset according to the directive whence as follows:
-
SEEK_SET:The offset is set to offset bytes.文件首
-
SEEK_CUR:The offset is set to its current location plus offset bytes.当前位置
-
SEEK_END:The offset is set to the size of the file plus offset bytes.文件尾
函数原型: off_t lseek(int fd, off_t offset, int whence);
函数参数:
- fd:文件描述符
- offset:偏移量
函数返回值:
成功的话返回文件当前位置到开始的长度;
失败就返回-1并设置errno
功能1示例:移动文件指针读写位置
示例:新建一个文件并向其中写入数据,然后读取该文件的内容将其写入到屏幕上。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* args[])
{
if(argc == 1)
{
printf("usage:%s filename", args[0]);
return -1;
}
//新建一个文件并写入HelloWorld,然后读取该文件的内容,将内容输出到屏幕上。
int fd = open(args[1],O_CREAT | O_RDWR,0664);
write(fd,"HelloWorld",10);
//此时文件指针位置已经到末尾需要重新将指针移到首部。
lseek(fd,0,SEEK_SET);
char buff[11];
int ret = read(fd,buff,sizeof(buff));
if(ret != -1)
{
write(STDOUT_FILENO,buff,ret);
}
close(fd);
return 0;
}
功能二:计算文件大小
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char* args[])
{
int fd = open("./hi.txt",O_RDONLY | O_CREAT,0664);
if(fd == -1)
{
return -1;
}
int ret = lseek(fd,0,SEEK_END);
printf("%d\n",ret);
return 0;
}
编译成功后第一次运行新建一个原本不存在的文件,所以字节数为0;然后向其中编辑写点数据,第二次运行后就和运行前ls -lrt查看的字节数一样了。
6.获取环境变量getenv()函数
#include <stdio.h>
#include <stdlib.h>
int main()
{
//读取环境变量HOME的值
printf("%s\n",getenv("HOME"));
return 0;
}
查看当前环境下的所有环境变量的值:
xiaocer@linux:~$ env
7.fork函数
fork函数::创建子进程。
父进程与子进程运行在不同的内存空间,子进程拥有独一无二的task structrue(PCB),但是内存空间的内容是一样的除了一些特殊的比如说进程id不一样。
函数返回值:
- 失败时:父进程返回-1,不创建子进程;
- 成功时:父进程返回子进程的id,子进程返回0;
举个例子:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("begin:\n");
//返回值为子进程的id
pid_t child_id = fork();
//创建子进程失败
if(child_id < 0)
{
perror("create child process failed!\n");
exit(-1);
}
//子进程
else if(child_id == 0)
{
printf("child id:%d parent id:%d\n",getpid(),getppid());
}
//父进程
else
{
printf("child id:%d self id:%d parent id:%d\n",child_id,getpid(),getppid());
}
printf("end\n");
return 0;
}
上诉例子执行结果如下:
xiaocer@linux:~/练习$ ./a.out
begin:
child id:44960 self id:44959 parent id:44159
end
child id:44960 parent id:1
end
子进程中的parent id有些时候运行结果为1,为什么是1呢? 这个涉及到孤儿进程的知识,原因是父进程已经死了,孤儿进程就被init进程领养了,由init进程负责孤儿进程资源的回收。当然了你还可以通过以下命令查看ps aux
孤儿进程被哪个进程领养了;或者通过ps ajx
查看父子进程之间的信息;
8.getpid函数
getpid函数:获取当前进程的id
9.getppid函数
getppid函数: 获取当前进程的父进程的id
10.exec族函数
这些exec族函数的共同特点:
- 只有当错误发生才有返回值,返回值为-1;
- 这些函数都是可变参数的函数,所以参数列表必须以空指针结束;
1.execl函数:
//path为环境变量,arg为待执行文件的参数从argv[0]开始
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
2.execlp函数:
//file为需要执行的程序名
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
举例:
#include <stdio.h>
#include <unistd.h>
int main()
{
//execlp("ls","ls","-l",NULL);
//这里无需判断返回值,因为exec族函数只有当产生错误才有返回值为-1
//perror("execlp error!\n");
//execl("/bin/ls","ls","-l",NULL);
//perror("execl error!\n");
execl("/bin/ls","ls",NULL);
return 0;
}
11孤儿进程与僵尸进程
孤儿进程: 父进程死了,被init进程领养
僵尸进程: 子进程死了,父进程没有回收子进程的资源
示例孤儿进程:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t child_id = fork();
//孤儿进程
if(child_id == 0)
{
while(1)
{
printf("子进程id:%d 父进程id:%d\n",getpid(),getppid());
sleep(1);
}
}
else if(child_id > 0)
{
printf("父进程id:%d 父进程的父进程id:%d\n",getpid(),getppid());
sleep(5);
printf("I will died\n");
}
return 0;
}
示例僵尸进程:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t child_id = fork();
//子进程
if(child_id == 0)
{
printf("I am child,child id:%d parent id:%d\n",getpid(),getppid());
sleep(2);
printf("child process died!\n");
}
//父进程
else if(child_id > 0)
{
while(1)
{
printf("I am parent,parent id:%d\n",getpid());
sleep(2);
}
}
return 0;
}
这样就创建了一个僵尸进程,然后通过ps ajx
命令可以查看到有一个进程的状态stat为Z+,这个进程就是僵尸进程。那么如何回收僵尸进程呢?通过kill -9 父进程id
命令杀死父进程,让僵尸进程交给init进程领养,然后init进程负责回收。
12wait函数
wait函数: 等待子进程状态的改变.
调用此函数成功,允许OS释放子进程的资源。注意:本函数会阻塞直到子进程状态改变(比如说子进程中止)或者本函数的调用被信号打断。因此推荐使用waitpid函数
- 成功时:函数返回子进程的id
- 失败时:函数返回-1
//wstatus可以获取子进程状态改变的信息
pid_t wait(int *wstatus);
//wait(NULL)等同于 waitpid(-1, NULL, 0);
通过这个函数我们可以避免僵尸进程的出现,等待子进程状态的改变,让系统回收子进程的资源,如下所示:
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("child process,child id:%d parent id:%d\n",getpid(),getppid());
sleep(2);
exit(100);
}
else if(id > 0)
{
printf("parent process,child id:%d\n",id);
//status保留已死子进程的信息
int status;
//wait回收僵尸进程的信息
pid_t child_id = wait(&status);
//如果子进程正常退出
if(WIFEXITED(status))
{
printf("child process are exited with %d\n", WEXITSTATUS(status));
}
//如果子进程被信号杀死
if( WIFSIGNALED(status))
{
printf("child process are killed by %d\n", WTERMSIG(status));
}
printf("child_id:%d\n",child_id);
//while(1)
//{
// sleep(2);
//}
}
return 0;
}
运行结果如下:
xiaocer@localhost:~/练习$ ./a.out
parent process,child id:3079
child process,child id:3079 parent id:3078
child process are exited with 100
child_id:3079
13.waitpid函数
waitpid函数:
pid_t waitpid(pid_t pid, int *wstatus, int options);
pid:
<-1表示回收进程组id为pid绝对值的任意子进程.
-1表示回收任意子进程
0回收进程组id相同的组内的子进程
>0回收指定进程id的子进程
options:
0与wait相同也会阻塞
WNOHANG表示如果当前没有子进程退出也会返回
返回值:
如果设置了WNOHANG,如果没有子进程退出则返回0;
如果有子进程退出则返回子进程的id;
举例:
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("child process:child id:%d parent id:%d\n",getpid(),getppid());
sleep(2);
exit(100);
}
else if(id > 0)
{
printf("parent process:parent id:%d\n",getpid());
int ret;
while((ret = waitpid(0,NULL,WNOHANG)) == 0)
{
}
printf("ret:%d\n",ret);
//此时子进程已经死去被回收那么再次调用waitpid时返回值就一定是-1了
ret = waitpid(0,NULL,WNOHANG);
printf("ret:%d\n",ret);
//不让父进程死去
while(1)
{
sleep(2);
}
}
return 0;
}
运行结果如下
xiaocer@localhost:~/练习$ ./a.out
parent process:parent id:3350
child process:child id:3351 parent id:3350
ret:3351
ret:-1
示例二:使用waitpid回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t id;
int i;
//创建多个子进程
for(i = 0 ; i < 5 ; i++)
{
id = fork();
//子进程退出for循环,让其不具备再生能力
if(id == 0)
{
printf("I am child process:child id:%d parent id:%d\n",getpid(),getppid());
break;
}
}
//父进程
if(i == 5)
{
while(1)
{
int ret_id = waitpid(-1,NULL,WNOHANG);
if(ret_id == -1)
{
break;
}
else if(ret_id > 0)
{
printf("ret_id:%d\n",ret_id);
}
}
//不让父进程死去
while(1)
{
sleep(2);
}
}
//子进程
if(i < 5)
{
//为了让创建的子进程顺序死亡
sleep(i);
}
return 0;
}
运行结果如下:
xiaocer@localhost:~/练习$ ./a.out
I am child process:child id:3615 parent id:3614
I am child process:child id:3616 parent id:3614
ret_id:3615
ret_id:3616
I am child process:child id:3618 parent id:3614
ret_id:3618
I am child process:child id:3617 parent id:3614
I am child process:child id:3619 parent id:3614
ret_id:3617
ret_id:3619
14.pipe函数
pipe函数: 创建用于进程间通信的管道,该管道只能用于有血缘关系的进程间通信,并且是单工通信。
函数声明:
#include <unistd.h>
int pipe(int pipefd[2]);
调用此函数成功后,pipefd数组的元素指向管道的两端,pipefd[0]指向读端,pipefd[1]指向写端。 函数执行成功返回0,否则返回-1.
比如说:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>
int main()
{
int fd[2];
pipe(fd);
pid_t id = fork();
if(id == 0)
{
//即使子进程睡5秒,父进程的read函数也会阻塞着知道read函数从读一端的管道读取到数据
sleep(5);
//子进程向写的管道端写数据
write(fd[1],"HelloWorld",10);
}
else if(id > 0)
{
char buff[10];
read(fd[0],buff,10);
printf("%s\n",buff);
waitpid(-1, nullptr, WNOHANG);
}
return 0;
}
通过ulimit -a
命令可以查看管道缓冲区的大小。
14.mkfifo函数
- mkfifo函数:创建一个FIFO有名管道文件
- FIFO:称为有名管道,实现无血缘关系管道通信
- 创建管道伪文件命令:
mkfifo 文件名
- 示例:先使用
mkfifo 文件名
创建一个FIFO有名管道文件,然后多个进程读写
向myfifo文件写的进程示例如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
if(argc != 2)
{
printf("usage:%s fileName\n",argv[0]);
exit(-1);
}
int fd = open(argv[1],O_WRONLY);
char buff[256];
int num = 1;
while(1)
{
memset(buff,0,sizeof(buff));
sprintf(buff,"xiaohua%04d",num++);
write(fd,buff,strlen(buff));
//别让刷屏太快
sleep(1);
}
return 0;
}
向myfifo文件读的进程:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc,const char* argv[])
{
if(argc != 2)
{
printf("usage:%s fileName\n",argv[0]);
exit(-1);
}
int fd = open(argv[1],O_RDONLY);
char buff[256];
int ret;
while(1)
{
memset(buff,0,sizeof(buff));
ret = read(fd,buff,sizeof(buff));
if(ret > 0)
{
printf("read from myfifo:%s\n",buff);
}
}
return 0;
}
15.mmap函数
mmap函数: 将文件或者设备映射到内存
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
函数参数:
- addr:指定新映射区内存的地址,一般0即可
- length:指定映射区的长度,必须大于0
- prot:
常用的两个:
PROT_READ Pages may be read.
PROT_WRITE Pages may be written. - flags:
常用的两个:
MAP_SHARED:可以更改文件内容
MAP_PRIVATE:对其他进程不可以更改文件内容 - fd:指定需要映射的文件
- offset:指定从fd的那个文件的指定位置开始映射,一般offset为0,因为offset必须为4K的倍数。
返回值:
- 成功时返回映射区的首地址;
- 失败返回MAP_FAILED。
munmap函数: 释放映射区资源;
返回值: 成功返回0,失败返回-1;
函数使用举例:
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("example.txt",O_RDWR | O_CREAT);
// 将文件映射到内存
char* mapArea = mmap(NULL,8,PROT_WRITE,MAP_SHARED,fd,0);
if(mapArea == MAP_FAILED)
{
perror("mmap err!\n");
}
strcpy(mapArea,"HelloWorld");
printf("%s\n", mapArea); // HelloWorld
int ret = munmap(mapArea,8);
if(ret == -1)
{
printf("munmap err!");
}
return 0;
}
示例二:mmap函数创建对已知的文件的映射区,实现父子进程间的通信
8 #include<stdio.h>
9 #include <sys/mman.h>
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16
17 int main()
18 {
19 //创建一个对已知的文件的映射
20 int fd = open("example.txt",O_RDWR);
21 int *mapArea = mmap(NULL,4,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
22 if(mapArea == NULL)
23 {
24 perror("mmap err\n");
25 exit(-1);
26 }
27 //创建子进程
28 pid_t id = fork();
29 //子进程
30 if(id == 0)
31 {
32 *mapArea = 100;
33 printf("child mapArea:%d\n",*mapArea);
34 sleep(3);
35 printf("child mapArea:%d\n",*mapArea);
36 }
37 //父进程
38 else if(id > 0)
39 {
40 *mapArea = 88;
41 printf("parent mapArea:%d\n",*mapArea);
42 sleep(1);
43 printf("parent mapArea:%d\n",*mapArea);
44 *mapArea = 1001;
45 //回收子进程
46 wait(NULL);
47 }
48 return 0;
49 }
示例三:mmap函数创建匿名映射,使用MAP_ANONYMOUS宏。
8 #include<stdio.h>
9 #include <sys/mman.h>
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 int main()
15 {
16 //创建匿名映射
17 int* mapArea = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS,-1,0);
18 if(mapArea == NULL)
19 {
20 perror("mmap error!\n");
21 exit(-1);
22 }
23 pid_t id = fork();
24 if(id == 0)
25 {
26 *mapArea = 100;
27 printf("child mapArea:%d\n",*mapArea);
28 sleep(3);
29 printf("child mapArea:%d\n",*mapArea);
30
31 }
32 else if(id > 0)
33 {
34 *mapArea = 999;
35 printf("father mapArea:%d\n",*mapArea);
36 sleep(1);
37 *mapArea = 666;
38 wait(NULL);
39 }
40 return 0;
41 }
示例四:mmap函数实现无血缘关系的进程之间的通信。
//写进程
8 #include<stdio.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <fcntl.h>
12 #include <unistd.h>
13 #include <sys/wait.h>
14 #include <stdlib.h>
15 #include <sys/mman.h>
16
17 typedef struct Student
18 {
19 int id;
20 char name[20];
21 }_student;
22 //mmap实现不同进程之间的通信
23 int main(int argc,const char* argv[])
24 {
25 if(argc != 2)
26 {
27 printf("usage:%s fileName\n",argv[0]);
28 exit(-1);
29 }
30 int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0664);
31 int length = sizeof(_student);
32 //将文件的大小截断为指定大小,不管之前文件的大小比length大还是小
33 ftruncate(fd,length);
34 _student *stu = mmap(NULL,length,PROT_WRITE,MAP_SHARED,fd,0);
35 if(stu == MAP_FAILED)
36 {
37 perror("mmap error!\n");
38 exit(-1);
39 }
40 int num = 1;
41 while(1)
42 {
43 stu->id = num;
44 sprintf(stu->name,"xiaoming-%04d\n",num++);
45 sleep(1);
46 }
47 close(fd);
48 munmap(stu,length);
49 return 0;
50 }
7 //读进程
8 #include <stdio.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 #include <fcntl.h>
13 #include <sys/mman.h>
14 #include <stdlib.h>
15
16 typedef struct Student
17 {
18 int id;
19 char name[20];
20 }_student;
21
22 int main(int argc,const char* argv[])
23 {
24 if(argc != 2)
25 {
26 printf("usage:%s fileName\n",argv[0]);
27 exit(-1);
28 }
29 int fd = open(argv[1],O_RDWR);
30 int length = sizeof(_student);
31 _student* stu = mmap(NULL,length,PROT_READ,MAP_SHARED,fd,0);
32 if(stu == MAP_FAILED)
33 {
34 perror("mmap error!\n");
35 exit(-1);
36 }
37 while(1)
38 {
39 printf("name:%s id:%d\n",stu->name,stu->id);
40 sleep(1);
41 }
42 close(fd);
43 munmap(stu,length);
44 return 0;
45 }
16.kill函数
kill函数:给一个进程或者进程组发信号
函数声明:
int kill(pid_t pid, int sig);
函数返回值:成功返回0,失败返回-1
函数参数:
pid:
1. pid > 0:信号sig发送给指定的进程
2. pid = 0:信号发送给与调用进程同组内的所有进程
3. pid = -1:信号发送给调用进程有权限发送信号的所有进程
4. pid < -1:信号发送给进程组id为-pid内的所有进程
sig:
0表示不发送信号
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{
//其中一个子进程将父进程杀死
pid_t id;
int i;
for(i = 0 ; i < 5 ; i++)
{
id = fork();
if(id == 0)
{
break;
}
}
//3号进程杀死父进程
if(i == 2)
{
printf("after 5 seconds:\n");
sleep(5);
// 给父进程发送SIGKILL信号
kill(getppid(),SIGKILL);
}
// 父进程
else if(i == 5)
{
while(1)
{
printf("I am father\n");
sleep(1);
}
}
return 0;
}
17raise函数
raise函数:给调用者(进程或者线程)发送信号
- 函数声明:
int raise(int sig);
返回值:成功返回0,失败返回非0
函数参数:
- 用法示例:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main()
{
printf("I am happy!\n");
//raise函数给自己发送信号
raise(SIGKILL);
// 不输出,main线程已死
printf("I am happy!\n");
return 0;
}
18alarm函数
alarm函数: 设置信号发送的时间,即多少秒后给调用进程发送SIGALARM信号,该信号的默认处理是中止进程。
函数声明:
unsigned int alarm(unsigned int seconds);
函数参数:
seconds:
0:表示取消时钟
返回值:对剩余时钟的描述。如果先前未设置时钟则返回0;
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("I am happy\n");
int ret = alarm(6);
printf("%d\n",ret); //0
sleep(2);
ret = alarm(5);
printf("%d\n",ret); //4
//四秒后中止程序
while(1)
{
printf("Hello\n");
sleep(2);
}
return 0;
}
19setitimer函数
setitimer函数: 设置中止时钟的值;当时钟到期,一个作用于调用进程的信号产生,具体产生什么信号,由which参数决定。
函数声明:
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
函数参数:
- which:可能的取值:
- ITIMER_REAL:实际时间,当时钟到期后,SIGALRM信号产生。
- ITIMER_VIRTUAL:用户使用的CPU时间,时钟到期后, SIGVTALRM信号产生。
- ITIMER_PROF:用户和系统使用的CPU时间,时钟到期后,SIGPROF信号产生。
- itimerval结构体:
struct itimerval {
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value; /* Time until next expiration */
};
20.信号集函数
一系列信号集函数:
int sigemptyset(sigset_t *set); //清空信号集
int sigfillset(sigset_t *set); //填充信号集
int sigaddset(sigset_t *set, int signum);//往信号集添加某个信号
int sigdelset(sigset_t *set, int signum); //删除信号集的
某个信号
int sigismember(const sigset_t *set, int signum); //判断某个信号是否是信号集的成员
typedef unsigned long sigset_t;
21.sigpending函数
sigpending函数: 获取未决信号集
22.sigprocmask函数
sigprocmask函数: 设置阻塞或者解除某个阻塞信号。
函数声明:
/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
23.信号处理函数
signal函数和signalaction函数: 每一个信号都有默认的处理动作,这两个函数可以改变对信号的默认处理。推荐使用signalaction函数。
signal函数: :改变信号的动作
如果signal函数指定的signal handler为SIG_IGN表示信号被忽略。SIG_DFL表示执行信号的默认处理动作。自定义的函数表示执行用户对信号定义的行为.注意:The signals SIGKILL and SIGSTOP cannot be caught or ignored.
//signum为需要处理的信号,handler为信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
sigaction函数: 改变信号的动作
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
//只能对sa_handler或者sa_sigaction其中一个赋值。
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//设置捕捉函数期间需要阻塞的信号集
int sa_flags;//一般填0表示使用第一个函数指针,SA_SIGINFO将会使用第二个函数指针
void (*sa_restorer)(void); //无效
};
示例1:定期捕获信号
8 #include<stdio.h>
9 #include <signal.h>
10 #include <sys/time.h>
11 #include <unistd.h>
12
13 void catch_sig(int num )
14 {
15 printf("catched signal:%d\n",num);
16 }
17 int main()
18 {
19 //注册以下捕捉函数
20 //SIGALRM为ITIMER产生的信号
21 struct sigaction act;
22 act.sa_flags = 0; //表明使用第一个函数指针
23 act.sa_handler = catch_sig;
24 sigemptyset(&act.sa_mask); //清空以下信号集
25 sigaction(SIGALRM,&act,NULL);
26 //新建一个闹钟
27 struct itimerval myit = {{3,0},{5,0}}; //第一次五秒后闹钟响起,之后周期性三秒
28 setitimer(ITIMER_REAL,&myit,NULL);
29
30 while(1)
31 {
32 printf("I am very happy\n");
33 sleep(1);
34 }
35 return 0;
36 }
24.SIGCHLD
SIGCHLD 20,17,18 Ign Child stopped or terminated
,我们可以利用子进程中止时产生SIGCHLD信号进行子进程回收。
26setsid函数
setsid函数: 创建新会话并且设置进程组id。查看一个进程的会话id可以ps ajx
所在列SID即为会话id。
进程组和会话的概念: 如果一个父进程通过fork函数产生了好多个子进程,那么这些父进程和子进程在同一个组内,并且进程组id和父进程id是相同的,父进程称为组长。同时子进程和父进程在同一个会话内,父进程也成为会长。
本函数注意事项:
1.父进程不能调用此函数。父进程调用setsid函数,函数返回-1,设置ERROR为EPERM。
2.如果一个会话中会长所在的那个进程中止,那么SIGHUP信号将会发送给控制终端下与会长所在进程在同一个进程组的所有进程。
函数声明:
pid_t setsid(void);
//本函数成功的话返回调用进程的会话id,失败返回-1
27.守护进程
示例:创建一个守护进程
8 #include <stdio.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 #include <fcntl.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <signal.h>
16 #include <time.h>
17 #include <sys/time.h>
18
19 //信号捕捉函数
20 void creatFile(int num)
21 {
22 char fileName[128] = {0};
23 memset(fileName,0,sizeof(fileName));
24 sprintf(fileName,"%s/example/zy.%ld",getenv("HOME"),time(NULL));
25 int fd = open(fileName,O_CREAT|O_TRUNC|O_WRONLY,0664);
26 if(fd == 0)
27 {
28 exit(-1);
29 }
30 close(fd);
31 }
32 //创建一个守护进程,实现每隔五秒钟创建一个文件
33 int main()
34 {
35 //创建子进程,父进程死亡
36 pid_t id = fork();
37 if(id > 0)
38 {
39 exit(0);
40 }
41 //子进程调用setsid函数,子进程当会长
42 setsid();
43 //切换家目录
44 chdir(getenv("HOME"));
45 //设置掩码
46 umask(0);
47 //业务逻辑
48
49 //新建闹钟
50 struct itimerval myit = {{5,0},{5,0}}; //五秒钟后闹钟响起,之后每隔五秒钟
51 setitimer(ITIMER_REAL , &myit , NULL);
52 //注册信号处理函数
53 struct sigaction sig;
54 sig.sa_flags = 0;
55 sig.sa_handler = creatFile;
56 //屏蔽信号集
57 sigemptyset(&sig.sa_mask);
58 sigaction(SIGALRM,&sig,NULL);
59
60 while(1)
61 {
62 sleep(1);
63 }
64 return 0;
65 }
当然如果我们的业务不是很复杂,可以通过nohup指令达到与守护进程相同的效果。指令如下nohup yourcommand [>xxx.log] &
nohup表示不让你要执行的程序yourcommand收到信号SIGHUP,&表示在后台运行。
比如说我们需要达到上述守护进程的效果,则可以实现一个简单的程序如下:
7 //每个五秒在一个指定目录下创建文件
8 #include<stdio.h>
9 #include <string.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <fcntl.h>
13 #include <stdlib.h>
14 #include <time.h>
15 #include <unistd.h>
16
17 int main()
18 {
19 char fileName[128] = {0};
20 while(1)
21 {
22 memset(fileName,0,sizeof(fileName)); //在每次创建一个新的文件前清空fileName里面的内容
23 //getenv("HOME");表示返回环境变量HOME的值
24 //time(NULL)表示获取当前时间戳
25 sprintf(fileName,"%s/example/log.%ld",getenv("HOME"),time(NULL));
26 int fd = creat(fileName,0664);
27 if(fd == -1)
28 {
29 perror("creat error!\n");
30 break;
31 }
32 close(fd);
33 sleep(5);
34 }
35 return 0;
36 }
然后执行命令nohup ./a.out &
即可。然后我们发现a.out这个进程在后台运行着.
xiaocer 4664 0.0 0.0 2320 760 pts/2 S 19:37 0:00 ./a.out
28.pthread_create函数
- pthread_create函数:创建一个新线程。
注意当使用POSIX线程库时需要:Compile and link with -lpthread(或者-pthread). - 函数声明
函数返回值:
成功返回0,失败则返回其他值
- 创建一个新线程举例
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
void* carryFunc(void * arg)
{
printf("curent thread:process id:%d thread id:%lu\n",getpid(),pthread_self());
}
int main()
{
pthread_t thread;
pthread_create(&thread,NULL,carryFunc,NULL);
printf("main process:process id:%d thread id:%lu thread:%lu\n",getpid(),pthread_self(),thread);
//睡一秒钟让子线程有机会执行完
sleep(1);
return 0;
}
// main process:process id:91536 thread id:139871103076160 thread:139871103072000
// curent thread:process id:91536 thread id:139871103072000
pthread_self 函数:返回调用线程的id就像getpid()函数返回调用进程id一样。
29pthread_exit函数
pthread_exit函数: 中止调用中的线程
exit()函数 :中止调用的进程.所以在主线程中一般使用pthread_exit函数退出。
示例如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* carryFunc(void * arg)
{
while (1)
{
printf("I am another thread:tid:%lu\n",pthread_self());
sleep(5);
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,carryFunc,NULL);
printf("tid:%lu\n",pthread_self());
//让主线程退出,进程不退出
pthread_exit(NULL);
return 0;
}
29pthread_join函数
pthread_join函数 :中止指定的线程,用于回收线程资源。
特点:阻塞等待,会等待要中止的那个线程;可以获取线程的返回值。
举例如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* carryFunc(void * arg)
{
printf("I am subthread\n");
sleep(4); //即使让这个子线程睡五秒钟,但是pthread_join函数阻塞等待着,主线程没有继续往下执行。
return (void*)"success";
}
int main()
{
//tid就是创建的子线程的id
pthread_t tid;
pthread_create(&tid,NULL,carryFunc,NULL);
void* ret;
pthread_join(tid,&ret);
// 获取线程的返回值
printf("ret:%s\n",(char*)ret);
printf("I am main thread\n");
return 0;
}
// I am subthread
// ret:success
// I am main thread
30pthread_cancel函数
pthread_cancel函数: 发送取消请求给指定的线程,杀死某个指定的线程。指定的线程何时或者是否反应这个取消请求取决于指定的线程的两个属性:state and type。 一个新线程的被取消状态默认是enabled ,但是也可以通过 pthread_setcancelstate()函数设置这个状态属性;一个新线程的被取消类型默认是deferred,但是这个属性也可以通过pthread_setcanceltype()函数设置,所以取消请求不会被应答,除非指定的那个线程内部调用了一个取消点函数。
31pthread_detach函数
pthread_detach函数: 分离一个线程,标记一个线程的状态为分离状态。当一个分离状态的线程中止,它的资源被系统自动回收。对已经处于detached状态的线程使用pthread_join将会返回error number EINVAL。
8 #include<stdio.h>
9 #include <pthread.h>
10 #include <unistd.h>
11 #include <string.h>
12
13 void* carryFunc(void* arg)
14 {
15 printf("subthread:tid:%lu\n",pthread_self());
16 sleep(4);
17 printf("subthread:tid:%lu\n",pthread_self());
18 return NULL;
19 }
20
21 int main()
22 {
23 pthread_t tid;
24 pthread_create(&tid,NULL,carryFunc,NULL);
25 //分离线程
26 pthread_detach(tid);
27
28 //让子线程全部执行完
29 sleep(5);
30 //尝试join
31 int ret;
32 if(ret = pthread_join(tid,NULL) != 0)
33 {
34 printf("ret:%d error string:%s\n",ret,strerror(ret));
35 }
36 return 0;
37 }
运行结果如下:
xiaocer@localhost:~/练习$ ./a.out
subthread:tid:140331142579968
subthread:tid:140331142579968
ret:1 error string:Operation not permitted
32.pthread_mutex_init函数
先来看一个例子,让我们对并发访问数据有个了解:
8 //模拟实现对共享资源的访问
9 #include<stdio.h>
10 #include <pthread.h>
11 #include <unistd.h>
12 #include <stdlib.h>
13
14 char var[20];
15
16 void* carryFunc1(void* arg)
17 {
18 for(int i = 0 ; i < 20 ; i++)
19 {
20 var[i] = '0';
21
22 //睡0到2微秒钟
23 usleep(rand()%3);
24 }
25
26 return NULL;
27 }
28 void* carryFunc2(void* arg)
29 {
30 for(int i = 0 ; i < 20 ; i++)
31 {
32 var[i] = '1';
33 usleep(rand()%3);
34 }
35
36 return NULL;
37 }
38 int main()
39 {
40 //创建两个线程,并且对共享全局变量var访问修改
41 pthread_t thread[2];
42 pthread_create(&thread[0],NULL,carryFunc1,NULL);
43 pthread_create(&thread[1],NULL,carryFunc2,NULL);
44
45 //中止子线程
46 pthread_join(thread[0],NULL);
47 pthread_join(thread[1],NULL);
48
49 printf("%s\n",var);
50 return 0;
51 }
运行结果如下:
xiaocer@zy:~/练习$ ./a.out
11111111010111000000
pthread_mutex_init函数:根据mutex的属性初始化mutex对象,如果不指定mutex的属性则使用mutex默认的属性。这个默认的属性为fast。这个属性呢由参数mutexattr决定。
当然对于每一种mutex都有另一种初始化方式:比如说fast类型的mutex:
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
该函数声明:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
mutex的种类:
1.fast
2.recursive
3.error checking
33.pthread_mutex_lock函数
pthread_mutex_lock函数: 锁定给定的互斥量,也就是加锁。如果给定的互斥量已经被另一个线程锁定,则pthread_mutex_lock函数等着
直到互斥量被解锁。如果对一个已经被当前线程锁定的互斥量调用pthread_mutex_lock函数加锁,则调用后的行为取决于互斥量的种类。
与此函数对应的是pthread_mutex_unlock函数,pthread_mutex_unlock函数: 解除对互斥量的锁定,也就是解锁。对于fast种类的互斥量
,调用此函数将使得互斥量从锁定的状态转变为解除锁定的状态。对于recursive种类的互斥量
,调用此函数将会减少同一个线程对mutex锁定的锁的数量,当这个锁减少为0,那么mutex解除锁定。
示例如下: 两个子线程循环轮流打印helloworld和HELLOWORLD.
8 #include<stdio.h>
9 #include <pthread.h>
10 #include <unistd.h>
11 #include <stdlib.h>
12
13 //静态初始化一个种类为fast的mutex
14 pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
15
16 void* carryFunc1(void* arg)
17 {
18 while(1)
19 {
20 //加锁
21 pthread_mutex_lock(&fastmutex);
22 printf("hello");
23 sleep(rand()%3);
24 printf("world\n");
25 //释放锁
26 pthread_mutex_unlock(&fastmutex);
27 //为了让另一个子线程可以抢到CPU执行的机会,所以释放锁后睡几秒钟
28 sleep(3);
29 }
30 return NULL;
31 }
32
33 void* carryFunc2(void* arg)
34 {
35 while(1)
36 {
37 pthread_mutex_lock(&fastmutex);
38 printf("HELLO");
39 sleep(rand()%3);
40 printf("WORLD\n");
41 pthread_mutex_unlock(&fastmutex);
42 sleep(3);
43 }
44 return NULL;
45 }
46 int main()
47 {
48 //创建两个线程
49 pthread_t thread[2];
50 pthread_create(&thread[0],NULL,carryFunc1,NULL);
51 pthread_create(&thread[1],NULL,carryFunc2,NULL);
52
53 //让系统自动释放资源
54 //pthread_detach(thread[0]);
55 //pthread_detach(thread[1]);
56
57 pthread_join(thread[0],NULL);
58 pthread_join(thread[1],NULL);
59
60 return 0;
61 }
34.pthread_mutex_destroy函数
pthread_mutex_destroy函数: 释放mutex对象的资源.
35.pthread_mutex_trylock函数
pthread_mutex_trylock函数: 本函数不阻塞,如果对一个已经被锁定的互斥量加锁,则本函数立即返回错误码EBUSY。
示例如下:
8 #include<stdio.h>
9 #include <pthread.h>
10 #include <string.h>
11 #include <unistd.h>
12
13 pthread_mutex_t fastmutex;
14 void* carryFunc(void* arg)
15 {
16 while(1)
17 {
18 //加锁
19 pthread_mutex_lock(&fastmutex);
20 printf("HelloWorld\n");
21 sleep(2);
22 //解锁
23 pthread_mutex_unlock(&fastmutex);
24
25 }
26
27 return NULL;
28 }
29 int main()
30 {
31 pthread_t thread;
32 //初始化一个种类为fast的互斥量
33 pthread_mutex_init(&fastmutex,NULL);
34 //创建一个子线程
35 pthread_create(&thread,NULL,carryFunc,NULL);
36
37 //主线程尝试给fastmutex这个已经被枷锁的互斥量加锁
38 while(1)
39 {
40 int ret = pthread_mutex_trylock(&fastmutex);
41 sleep(5);
42 printf("ret:%d error mess:%s\n",ret,strerror(ret));
43 }
44 return 0;
45 }
36读写锁相关函数
pthread_rwlock_init函数: 初始化一个读写锁对象,分配使用读写锁需要的资源。
pthread_rwlock_destroy函数: 销毁一个读写锁对象,释放被锁使用的资源
pthread_rwlock_wrlock函数: 加上一把写锁;遵循写锁独占,读锁共享的原则。
pthread_rwlock_rdlock函数: 加上一把读锁;遵循写锁独占,读锁共享的原则.也就是说:
1. 如果已经给一把锁加上写锁,那么再尝试加读锁或者写锁将会阻塞。
2. 如果一把锁已经加上写锁,再尝试加上读锁或者写锁都将会阻塞,那么原来的写锁释放之后,尝试加上写锁的优先级更高。3. 如果一把锁已经加上读锁,那么其他线程或者当前线程尝试加上读锁会成功。
If the Thread Execution Scheduling option is supported, when threads executing with the scheduling policies SCHED_FIFO,SCHED_RR, or SCHED_SPORADIC are waiting on the lock, they shall acquire the lock in priority order when the lock becomes available.
pthread_rwlock_tryrdlock函数: 这个函数与pthread_rwlock_rdlock函数的区别是不阻塞,
调用此函数只有两种结果:1.成功获得锁2.失败返回error numberpthread_rwlock_trywrlock函数亦然。
pthread_rwlock_unlock函数:解锁
37.条件变量相关的函数
pthread_cond_init函数:初始化条件变量cond.Linux实现中如果第二参数为NULL,表示不为条件变量设置属性。调用此函数等同于pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
这样的静态初始化。
pthread_cond_signal函数:重启一个等着条件变量cond的线程。通俗说就是通知一个线程
pthread_cond_broadcast函数:重启所有等着条件变量的线程。通俗说就是通知所有线程
pthread_cond_wait函数:自动地解除对mutex这个互斥量的锁定,并且等待条件变量的通知。
pthread_cond_timedwait函数:自动地解除对mutex这个变量的锁定,并且等待条件变量的通知,但是等待的时间是一定的
。
pthread_cond_destroy 函数:销毁一个条件变量。
38.信号量相关的函数
信号量的理解:互斥量的加强版
sem_init函数:初始化一个无名信号量
函数原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
函数参数:
sem:表示再sem地址处初始化一个无名信号量
pshared:0表示信号量再一个进程的所有线程之间共享;非0表示再进程之间共享.
value:指定信号量的值
sem_wait函数:锁定一个互斥量。调用此函数成功的话信号量value将会减一。如果调用前信号量的值为0,那么本函数将会阻塞。
sem_timedwait函数:锁定一个互斥量。调用此函数成功的话信号量value将会减一。如果调用前信号量的值为0,那么本函数将会阻塞一定的时间,一定的时间过后还是不能锁定信号量,那么此函数调用失败并且设置errno为ETIMEDOUT
sem_trywait函数:和sem_wait函数类似,但是调用这个函数失败的话不阻塞而是返回错误error.
sem_post函数:解除对一个信号量的锁定。调用此函数成功后信号量value将会加一。
示例1:通过信号量实现生产者和消费者模型
8 //通过信号量实现生产者消费者模型
9 #include <stdio.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12 #include <unistd.h>
13 #include <stdlib.h>
14
15 sem_t blank,black;
16 int queue[5]; //生产者的最大数量
17 int beginNum = 1000;
18
19 void* thr_producter(void* arg)
20 {
21 int i = 0;
22 while(1)
23 {
24 sem_wait(&blank);
25 printf("funcName:%s,tid:%lu,num:%d\n",__FUNCTION__,pthread_self(),beginNum);
26 queue[(i++) % 5] = beginNum++;
27 sem_post(&black); //消费者信号量加一,就可以解除消费者对sem_wait调用的阻塞
28 sleep(rand() % 3);
29 }
30 return NULL;
31 }
32
33 void* thr_customer(void* arg)
34 {
35 int i = 0;
36 int num = 0;
37 while(1)
38 {
39 sem_wait(&black); //刚开始还有产品给消费者消费,所以阻塞着
40 num = queue[(i++) % 5];
41 printf("funcName:%s,tid:%lu,num:%d\n",__FUNCTION__,pthread_self(),num);
42 sem_post(&blank);
43 }
44 return NULL;
45 }
46
47 int main()
48 {
49 //初始化信号量
50 sem_init(&blank,0,5);
51 sem_init(&black,0,0); //消费者的初始化一开始默认没有产品
52
53 //创建两个子线程
54 pthread_t thread[2];
55 pthread_create(&thread[0],NULL,thr_producter,NULL);
56 pthread_create(&thread[1],NULL,thr_customer,NULL);
57
58 //销毁两个子线程
59 pthread_join(thread[0],NULL);
60 pthread_join(thread[1],NULL);
61
62 //销毁信号量
63 sem_destroy(&blank);
64 sem_destroy(&black);
65
66 return 0;
67 }
39进程间使用互斥量来达到同步的相关函数
40fcntl函数设置文件锁
fctnl函数: 操控文件描述符。当使用fcntl函数设置文件锁(record lock)后就只能有一个进程打开文件。
41.perror函数:打印系统错误信息
- 函数声明
#include <stdio.h>
void perror(const char* s);