您的位置:首页 > 运维架构 > Linux

Linux文件/IO,应用编程

2017-07-11 11:14 148 查看
-----------------------应用框架结构------------------------------• 应用编程框架介绍信号:多线程:(多核心CPU)-----------------------------------------------------------------------------------------------------------------------------------------------------------• 文件操作的一些系统API什么是操作系统API?:API其实就是一些函数,APP通过操作系统提供的API来使用硬件干活,而硬件如何干活,则就是驱动程序的工作了。

Linux一些常用的文件IO接口:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/****
4000
********************
*OPEN_API函数的FLAG测试*
************************/
int main(int argc, const char *argv[])
{
int fd = -1;
int ret = -1;
char read_buf[100] = {0};
char write_buf[100] = "my_loveianidn\nsaindisnafhabfa";

//第一步:打开文件
//int open(const char *pathname, int flags); 返回一个文件描述符
fd = open("test.txt", O_RDWR | O_APPEND | O_EXCL | O_CREAT, 0666);

if (-1 == fd)
{
//printf("打开失败.\n");
perror("打开失败");
_exit(-1);
}
else
{
printf("打开成功,成功打开的文件操作符为[%d].\n", fd);
}

#if 0
//第二步:读取文件
//ssize_t read(int fd, void *buf, size_t count);
ret = read(fd, read_buf, 20);
if(-1 == ret)
{
//printf("读取失败.\n");
perror("读取失败");
_exit(-1);
}
else
{
printf("读取成功,成功读取的大小为:[%d].\n", ret);
printf("读取的内容为:[%s].\n", read_buf);
}
#endif

#if 1
//第三步:写入文件
//ssize_t write(int fd, const void *buf, size_t count)
ret = write(fd, write_buf, strlen(write_buf));
if(-1 == ret)
{
//printf("写入失败.\n");
perror("写入失败");
_exit(-1);
}
else
{
printf("写入成功,成功写入了[%d]字节.\n", ret);
}
#endif

//第四步:关闭文件
close(fd);
return 0;
}


open,read,write,close
Iseek,改变文件指针

操作文件的一般步骤:
(1)静态文件(2)动态文件
文件都是存放在块设备中的(INAND,SD)这些文件称为静态文件,当需要操作文件的时间,利用API(OPEN)来打开文件,此时操作系统采取的措施是,先将这个文件加载到内存中,然后给这个文件它的数据结构(方便读写),然后进行读写。这段在内存中的文件就是动态文件,当我们更改结束时,需要用CLOSE,将它加载同步回静态文件,所以这也是为什么不保存就不会发生改变的原因。

为什么要这样设计?
也许你要问为什么不直接在硬盘上进行操作呢?这是由于硬盘的一些特性决定的,我们知道硬盘的特点是容量大,但是操作不灵活,而内存(RAM)的特性,可以随机访问,且速度快,所以这样设计。

重要概念:文件描述符
文件描述符其实就是一个数字,用来表示区分,打开的文件在当前进程中的。他的生命周期就是当前进程。当你打开同时打开3个进程,操作系统怎么知道你需要操作的是哪个文件呢?文件描述符就是对这些打开的文件进行编号区分,便于操作。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------

• 一个文件读写的实例
fd->文件描述符
(1)open函数返回的是fd ,其余函数返回的是另外的ret,且参数O_RDWR(现在还不明白为什么这样)
(2)read函数返回的是ret,当成功时返回一个成功读取的字节数,当失败时,返回一个-1.
(3)write函数返回的是ret,当成功时返回一个成功读取的字节数,当失败时,返回一个-1.
(4)close函数返回的ret,一般不会出错,所以没加以判断,但应该判断,这是好习惯。
它的传参是fd.
-----------------------------------------------------------------------
------------------------------------------------------------------------------------

• open的flag详解
O_TRUNC:
加了这个flag,则若读取,就会置空本次读取的文件。
O_APPEND:
加了这个flag,若写入,则会在源文件后面,添加上本次写入的信息。
O_CREAT:
打开文件有一个问题,如果不存在这个文件,那么调用open打开,会报错。但有时间我们就需要向VIM直接打开并创建一个文件,用O_CREAT flag即可。
O_EXCL:
以上,万一创建的时间有一个文件和名字一样,那么现在再创建的时间会覆盖掉源文件,会产生恶劣影响,我们又需要像windowS那样的提示,不让我们创建,那么就需要 O_EXCL和O_CREAT合作使用了。
O_SYNC:
——阻塞式与非阻塞式:
当函数需要某项功能,它开始调用一些功能(比如蜂鸣器),然后这个时间有许多的程序都在调用这个功能,它有两个选择
(1)原地等待,指导前面的程序跑完,然后它开始使用蜂鸣器。(阻塞式)
(2)先遛了,然后待会没有人使用的时间在回来(非阻塞式)
异同:
阻塞式明显有个好处,那就是它一定可以结束完成后,再返回,所以它返回的结果是一定成功的。
非阻塞式,它不能带回一个确切的结果,但是它节约了时间。

O_NONBLOCK
特性:


很多时间我们需要调用系统的硬件驱动,而对硬件的操作的程序一般都是存放在缓冲区中的,因为频繁的操作硬件,会减少硬件的寿命。所以,这里的程序都是阻塞式的,但是有些时间,我们迫切需要使用到这个硬件,那么就需要O_NONBLOCK,来告知硬件系统,现在就开始使用硬件。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------

• 文件读写的一些细节
errno(错误信息编码)
perror(打印错误信息)
查询man手册确定该函数是否带errno
(主要查看RETURN VALUE)



write 和 read 的conut 和阻塞式
write 和 read 的conut我们在读取或者写入时是无法确切知道的,因为我们也不知道需要写入或者读写到底多大,这里牵扯出一个问题
文件里有20b的内容,而我们read打开时,需要读取的内容是30b,这里
20faa
正好是阻塞式的,然后程序就会在原地等待剩下的10b,这样其实没有必要,且效率很低。



所以系统把文件IO封装成标准IO (fOPEN)
用标准IO操作文件,然后存在标准IO的BUF中,然后等待一个合适的大小,一起存入操作系统内的buf中。这样就大大提高了效率。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• Linux系统如何管理文件
硬盘中的静态文件和inode(i节点)
通过之前的学习我们知道什么是静态文件,也就是存放在硬盘中的文件,那么,一般都是怎么读取这些文件的呢?静态文件的管理分为两个部分
(1)硬盘中的管理项
管理项就相当于索引点,系统通过这些索引,来找到相关文件的内容(inode),其实就是一个对应数据结构的结构体,它里面存放着一些文件的信息,当需要哪个文件的时间,我们就通过检索这个结构体,与需要的文件相匹配,由此找到文件。
(2)硬盘中的存储项
这个就不说了,就是一个一个的块设备,来存储信息的。

特性:格式化U盘,有两种办法
(1):快速格式化,这个格式化速度很快,其实就是只删除了文件管理项。也就是索引。所以已经删除的信息是可以找回的。
(2):低级格式化,这个格式化速度很慢(亲测),这个才是彻底删除信息,也就是将信息从内存中抹去。

内存中的V节点(vnode)
我们知道文件分为动态和静态的,所以这里的操作都是在内存中,自然就是动态的。当某个进程需要某个文件,则将它从硬盘中将它的文件管理项读出,并且利用进程管理,去管理它,此时进程会建立一个v节点,也就是这个文件的数据结构,一个V节点中存放了这个文件的fd,以及各种信息,所以就可以对这个文件进行各种操作了。

文件与流的概念:
流:(stream)其实编写的程序,比如,续接两个字符串,我们就是通过循环,对一个一个的字符进行操作对比,以及拼接。文件也是如此,文件的操作不可能是一块一块的,其实归根结底也是一个一个的字符进行操作。对一个一个字符的读写,也就是形成了流的概念。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• lseek详解
(1)文件指针
RDWR隐式的更改文件指针,lSEEK显示的更改文件指针。一开始打开文件时,都是默认到文件开头的,RD,WR都是向后偏移一个。
lSEEK,是指定文件指针的偏移量;
off_t lseek(int fd, off_t offset, int whence);
SEEK_SET 从文件头
SEEK_CUR 从文件中
SEEK_END 从文件尾
(1)利用lseek计算文件大小
lseek(fd, 0 SEEK_END)直接把文件指针移动到文件尾所的到的偏移量就是文件大小。
(2)利用lseek构造空洞文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/************************
*   计算文件大小小程序 *
************************/
//iseek:移动文件指针到文件头,文件中,文件尾,返回移动的偏移量,可以利用这个特性计算文件大小
int cal_lenFun(const char *pathname);
int main(int argc, const char *argv[])
{
if (argc != 2)
{
printf("—————————————————————————————————————————————\n");
printf("请正确输入,请在程序后加索要计算的文件名.\n");
printf("譬如:");
printf("%s pathname.\n", argv[0]);
printf("—————————————————————————————————————————————\n");
_exit(-1);
}

printf("文件的大小为:%d.\n", cal_lenFun(argv[1]));

return 0;
}

int cal_lenFun(const char *pathname)
{
int ret = -1;
int fd = -1;
//打开文件
//int open(const char *pathname, int flags, mode_t mode);
fd = open(pathname, O_RDWR | O_CREAT, 0666);
if (-1 == fd)
{
perror("打开错误");
return -1;
}

//计算长度
//off_t lseek(int fd, off_t offset, int whence);
ret = lseek(fd, 0, SEEK_END);
if (-1 == ret)
{
perror("lseek error");
return -1;
}

return ret;

}


-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 打开多个文件(文件共享)
两个文件指针(两种情况)
1.读取



(1)分别读取
(2)续借读取
同时打开两个文件进行读写
对两个文件进行读取,如图所示,文件指针是由不同进程的文件管理表管理的,所以拥有两个不同的文件指针。所以是分别读取的。
2.写入
那么在写入的时间,由于是两个分离的文件指针,所以当fd1写入后,fd2写入则会覆盖掉原来的文件。
O_APPEND标志:
加了这个标志就可以解决覆盖的问题。
原子特性:
O_APPEND实现的原理就是原子特性的,原子特性就是当一个进程开始工作以后,除非到它结束,否则就不能停止。
(1)同一个进程多次OPEN文件



(2)多个进程分别OPEN文件



_____dup系统:
dup和重定位:
dup系统调用,就是重新复制一个fd,之前发现,打开文件后fd总是从3开始,那么012去哪里呢?
0-----stdin
1-----stdout
2-----stderr
所以当我们close(1)然后再用dup去复制一个fd那么系统就会自动分配fd = 1给我们用,这时间,我们再往输出上的信息就会重定位到我们当前fd=1的文件里了。
(tips)fd的管理其实是一个数组。所以数值上是连续的切不可跳跃的。
_____dup2系统:
dup2可以指定一个确切的fd,函数原型如下。
int dup2(int oldfd, int newfd);
所以便可以轻松实现上面的输出重定位。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• FCNTL接口
int fcntl(int fd, int cmd, ... /* arg */ );
可以实现仿制dum2,dum2的缺点在于需要指定一个Fd,而万一你指定的这个fd正好在被使用,那么就会产生错误,而fcntl则不会产生这种问题,他会分配一个最靠近且不在占用的Fd给你。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/************************
*	fcntl函数的使用
************************/
//利用fcntl拷贝一个文件描述符,比较安全
#define FILENAME	"1.txt"
int main(int argc, const char *argv[])
{
//打开文件
int fd1 = -1;
int fd2 = -1;
fd1 = open(FILENAME, O_RDWR | O_CREAT | O_TRUNC, 0666);
if (-1 == fd1)
{
perror("open");
_exit(-1);
}
printf("成功打开文件fd1:%d.\n", fd1);
//close(1); //关闭1  然后复制一个fd,则会分配1
fd2 = fcntl(fd1, F_DUPFD, 1);
//(fd, 指令flag, 预计得到的fd,如果占用就选择一个没被使用的,而且是最接近的)
if (-1 == fd1)
{
perror("fcntl");
_exit(-1);
}
printf("11111.\n");
printf("成功打开文件fd2:%d.\n", fd2);

#if 0
while(1)
{
write(fd1, "aa", 2);
sleep(1);
write(fd2, "cc", 2);
}
#endif

return 0;
}


-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 标准输入输出
Fopen Fclose Fwrite Fread Ffulsh Fseek
Fread r+
Fwrite w+
在fclose后一定要将fp指针归0.
fp = NULL;
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define FILENAME	"1.txt"
/************************
*   标准输入输出	    *
************************/
int main(int argc, const char *argv[])
{
FILE *fp = NULL;
char read_buf[100] = {0};
size_t len = 0;
#if 0
//FILE *fopen(const char *path, const char *mode);
//打开(目的读取)文件
fp = fopen(FILENAME, "r+");
if (NULL == fp)
{
perror("Fopen");
_exit(-1);
}
printf("成功打开文件fp:%p\n", fp);

//读取文件
//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
memset(read_buf, 0,   sizeof(read_buf));
len = fread(read_buf, sizeof(read_buf[0]),
sizeof(read_buf)/sizeof(read_buf[0]), fp);
printf("fread is [%s].\n", read_buf);
printf("lenoffile:[%d].\n", len*sizeof(read_buf[0]));
#endif
//FILE *fopen(const char *path, const char *mode);
//打开(目的读取)文件
fp = fopen(FILENAME, "w+");
char write_buf[10] = "CCAAADWe";
int ret = -1;
if (NULL == fp)
{
perror("Fopen");
_exit(-1);
}
printf("成功打开文件fp:%p\n", fp);
//写入文件
//size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
len = fwrite(write_buf, 1, 10, fp);
if (-1 == len)
{
perror("fwrite");
_exit(-1);
}
printf("lenoffile:[%d].\n", len);
ret = fclose(fp);
if(0 == ret)
printf("okokok.\n");
fp = NULL;
printf("fp=%p.\n", fp);
return 0;
}


-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 文件属性

文件属性的获取
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
stat是获取从一个路径获取文件属性,所以是从硬盘里读取
Fstas是从fd获取,所以是从内存中获取,是从已打开文件中获取,所以速度会快写。
lstat是获取文件权限,区别于上面两个的是,上面两个获取的文件属性如果是链接文件,则会自动追踪到源文件,而Lstat则不会追踪,它获取的就是当前文件的属性。

一些宏和St_mode

如果测试权限是如上可读的那么就是返回1,利用了这样一个位与的操作
文件权限不是谁创建的它而是谁运行的它
第一组是属主,第二组是属主所在的组,第三组是其他
指令:
(vvvvvvvvip)_________________________________________
权限测试API ——————access
当前用户对于测试文件的权限。
修改权限API—————— chomd
chown root filename修改文件的属主
chgrp root filename文件所在的组
umask 0000为补
读取目录文件API—————— opendir 、readdir
重入版本
具体使用请看代码。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 关于时间的概念
GMT时间:格林尼治时间
UTC时间:原子钟时间
点时间:具体某点的时间(RTC)
段时间:点时间 -点时间(定时器)
电脑在开机时会去读取一个RTC,然后根据系统的节拍,给jiffies加数,算出一个距离1970的时间节点的秒数、
•API实战:
time :获取计算机元年到现在的一共的秒数
ctime:通过time获取的秒数,的到一个字符型的时间
gmtime:UTC时间
localtime:当地时间
asctime:和ctime一样
strftime:用户定义时间格式

•随机数
随机数API rand
srand(time(NULL));设置随机数种子
rand()%n0~n-1 的随机数
linux内核中产生一个半真正意义上的随机数种子,依靠一些随机产生的动作,比如,用户操作鼠标,键盘等,依靠这些作为随机数种子,即可产生一个意义上的随机数。
•proc文件系统和sys系统
/proc/ /sys/
这快文件系统是虚拟的,只有动态内存,没有静态内存,一般都用于调试。

-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 进程
main函数的返回值返回给谁,它被谁调用??
main函数被系统调用,其实是在运行阶段,链接
argc argv 怎么传递参数的?
通过shell采集到参数,然后通过系统,加载器加载到代码中运行
atexit :当程序结束时,运行时的代码,栈原理运行,先入后出。
•程序运行有两种方式结束:
(1)正常结束
(2)强制结束
return 0 exit :
_exit:扛把子。直接退出,不运行系统结束的时的函数。
进程

•环境变量:
许多程序,在本机开发环境中可以执行,一换机器就不能执行,这大部分都是环境变量的问题。我们可以通过export打印出环境变量。
在程序中也可以使用environ变量去打印。
environ是一个字符串数组,所以应该是一个二重指针。
getenv 函数获取一个环境变量
•虚拟地址
虚拟地址就是,每个进程都分配了4G的逻辑内存,其中0-1G为OS,1-4G为应用使用内存,然而实际它是操作系统分配的虚拟地址,当每个进程需要一定内存,就去物理内存上去获取,这有点像银行储蓄。

虚拟地址的作用?
(1)进程隔离,因为进程是独立的虚拟地址,所以相互独立。
(2)提供多进程共同运行。

•进程进入:

ps -aux :向终端打印所有的进程
进程控制块:PCB 就是一个数据结构,用来管理新进的一些信息。
进程ID:getpid getppid
多进程的调度原理:进程间相互调度,所以在微观上是串行的,而在宏观上是并行的。合理的进程调度,提高效率。

•fork函数:

fork函数会返回两个进程,一个父进程,一个子进程,内部实现的原理是,当需要产生一个新的进程时,就从老的拷贝一份
子进程:
子进程和父进程的关系 子进程有自己的PCB 子进程被内核同等调度。
•父子进程对文件的操作:
(1)打开同一个文件, 接续的,因为同一个文件打开,文件指针会置位。
(2)分别打开同一个文件,分别的,因为两个进程有两个文件管理表,所以文件指针时分别的,需要加O_APPEND
子进程的最终目的就是实现独立的运行程序。

进程的诞生和消亡:
(1)进程0,1(init)
(2)fork
(3)vfork
僵尸进程:
父进程会fork出来一个子进程,当子进程运行结束时,操作系统会回收子进程的资源,但是剩下8K的管理内存没回收,此为僵尸进程。其实就是子进程运行完毕没被父进程回收。
可以显示的用wait/waitpid回收。
孤儿进程:
父进程先死了,所以没人给正在运行的子进程回收,这是系统会自动把这个进程分配init进程(进程1)做为它的父进程(找个老爸)。
wait:
(请阐述wait这个系统调用的原理)
父进程会利用一个系统调用显示的回收子进程,这个系统调用就是wait,当需要回收是会遇到两个问题
(1)子进程先运行,然后运行结束,父进程回收子进程,正常情况
(2)父进程先运行,子进程还没运行结束,那么他需要等待子进程结束,这就是wait系统调用的阻塞原理,他会把父进程阻塞,然后等待子进程的结束。
那么当父进程没有子进程的时间,wait会怎么样呢?当然不会一直阻塞在那里。它会以错误形式返回一个-1;
wait的参数
原型如下 pid_t wait(int *status);
它的输入参数中有一个输出型参数(没加const),这个status,就会返回回收的子进程的状态,结合下面几个宏定义。
WIFEXITED
-> 是不是正常终止,正常终止返回1,否则返回0
WEXITSTATUS
-> 返回子进程的结束状态,也就是返回值。
WIFSIGNALED
-> 是不是非正常终止(信号)正常终止返回1,否则返回0
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/************************
*	wait回收进程和status
************************/
//wait,默认阻塞式回收子进程,通过传入的status,与响应的宏进程比较可以获取到进程的响应信息
int main(int argc, const char *argv[])
{

pid_t pid = -1;
pid = fork();
int status;
if (pid == 0)
{
printf("这是子进程%d.\n", getpid());
return 120;
}
if (pid > 0)
{
printf("这是父进程%d.\n", getpid());
pid = wait(&status);
printf("回收的进程号是%d.\n", pid);
}
if (WIFEXITED(status))
{
printf("\n程序正常终止.\n");
}

if (WIFSIGNALED(status))
printf("程序非正常终止.\n");

printf("程序的返回状态为%d.\n", WEXITSTATUS(status));
return 0;
}


waitpid:
原型如下:pid_t waitpid(pid_t pid, int *status, int options);
可以指定的回收某个进程,可以选择是否采用阻塞非阻塞模式。
(1)waitpid(-1, &status, 0)
-1:不指定pid,收回任意满足条件的子进程,0,阻塞式,其实就和wait( )功能基本一样。
(2)waitpid(-1, &status, WNOHANG);
不固定某个pid,使用非阻塞式,也有两种情况
1.父进程先执行,然后调用wait,子进程还没执行完,那么直接返回0.
2.给父进程加了sleep,在父进程休眠的这段时间,子进程运行结束,然后父进程唤醒,回收子进程。
(3)固定某个不存在的pid,使用阻塞式
因为waitpid可以指定回收某个进程号,所以当我们指定一个不存在的进程号,而且采用阻塞式的,那么它就会返回一个错误,因为操作系统的设计是很合理的,不可能让程序在原地卡死。

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

/************************
*	waitpid与阻塞非阻塞
************************/
//waitpid可以用户自定义阻塞与非阻塞式回收子进程,可以指定或不指定(-1)回收子进程;
int main(int argc, const char *argv[])
{
pid_t pid = -1;
pid = fork();
int status;
if (pid == 0)
{
printf("子进程:%d.\n ", getpid());
}
if (pid > 0)
{

#if 0
//(1)不固定某个pid,切使用默认项,就是阻塞式的
printf("父进程:%d.\n", getpid());
pid = waitpid(-1, &status, 0);
printf("被回收的进程为:%d.\n", pid);
#endif

#if 0
sleep(1);
//(2)不固定某个pid,使用非阻塞式
printf("父进程:%d.\n", getpid());
pid = waitpid(-1, &status, WNOHANG);
printf("被回收的进程为:%d.\n", pid);
#endif

#if 1
//sleep(1);
//(3)固定某个不存在的pid,使用阻塞式
printf("父进程:%d.\n", getpid());
pid = waitpid(pid+3, &status, 0);
printf("被回收的进程为:%d.\n", pid);
#endif

}

return 0;
}


exec族函数:
(请阐述exec族函数调用的原理)
通过传参,指定要执行程序的路径,然后执行程序
export:终端显示系统的环境变量
echo &PATH 。。。。
execl,直接加参数,参数后要加NULL
execv,通过定义一个字符串数组指针,要加NULL
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/************************
*	execl,execv,调用函数
************************/
//exec族函数:用来在进程中调用一个已经写好的程序 v就是传参不太一样
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if (pid > 0)
{
printf("父进程进程号:%d.\n", getpid());
}

if (pid == 0)
{
printf("子进程进程号:%d.\n", getpid());
/*
char *const argv[] = {"ls", "-la", NULL};
execv("/bin/ls", argv);
*/
char *const argv[] = {NULL};
execv("./exe", argv);
}

return 0;
}


execlp,execvp,
如果不加p,那么让找不到给定路径的程序时,则会返回错误值,加了p以后如果找不到就回去系统默认添加的PATH里去找。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

/************************
*	execlp,execvp,调用函数
************************/
//P:exec不加P,就只会在指定目录查找一次,没有则返回错误,带P,还会到系统默认的目录查找一次。PATH的意思。
int main(int argc, const char *argv[])
{
pid_t pid = -1;
pid = fork();
if (pid > 0)
{
printf("父进程PID:%d.\n", getpid());
}
if (pid == 0)
{
char *const arg[] = {"ls", "-la", NULL};
printf("子进程:.\n");
execvp("ls", arg);
}

return 0;
}


execle,execve,
即多传一个参数,环境变量

进程的状态和system函数
(1)进程的五种状态:(请阐述进程运行的各种状态)
1.就绪态具备一切运行条件,等待CPU调度
2.运行态正在占用CPU
3.僵尸态运行结束,但还未被回收
4.等待态 (浅度休眠)(深度休眠)
浅度休眠,是正在等待某个条件,或者某个条件,但是可以通过信号去唤醒
深度睡眠,是一定需要某个条件,等待资源的到来以后才会唤醒(比如文件要读写,但是硬盘被占用,他就需要硬盘的占用结束,然后才能进行工作,这是硬盘占用结束就是等到了资源,这是间程序就会唤醒)
5.暂停态收到信号,暂时停止,但不是真正的停止。真正的停止是僵尸态。
(2)各个进程的状态图

system函数
(1)system是原子操作的,原子操作就是一种不可打断的操作,他的执行一定要结束,所以会长时间占用CPU.这种操作可以避免竞争状态的产生
与之相反,fork+exec就不是原子的,当我们不需要原子操作时,就可以用fork+exec。
(2)用system函数使用ls命令
int system(const char *command);
直接加入要执行的PATH名即可

进程关系
(1)无关系
(2)父子关系
(3)进程组(group)
许多进程不好管理,所以需要一个群体管理,便于某些进程有相互需要时的作用。
(4)会话(session)
就是进程组的集群。

守护进程
查看进程的命令
(1)ps -ajx
(2)ps -aux
守护进程:(请阐述守护进程的基本特性)
(daemon)守护进程是一般以d结尾,与控制台脱离的后台运行程序,它都是在后台运行,一般用于服务器程序。长期运行
1、syslogd
系统日志守护进程
2、cron
操作系统中时间管理, 比如linux中的需要定时程序。
自己编写守护进程
任何程序都能变成守护进程,主要是看它有没有必要。

编程实现守护进程的步骤:
(1):子进程等待父进程退出,自己独立
(2):子进程使用setsid建立一个新的会话期,脱离控制台
函数 setsid
(3):调用chdir将工作目录修改到/用户主目录下
函数chdir
(4):将umask设置成0,使得文件的权限最大
函数umask
(5):关闭所有文件描述符,这里因为要动态获取一个文件描述符的最大值,所以需要调用sysconf
函数sysconf ,可以获取系统的参数
(6):将文件 0 1 2 定位到/dev/null 垃圾堆。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
void creat_daemon(void);
/************************
*	守护进程的编写制作  *
************************/
//守护进程就是一个后台运行的进程,代码实现如下:
int main(int argc, const char *argv[])
{
creat_daemon();
while (1)
{
printf("im runing .\n");
}

return 0;
}

void creat_daemon(void)
{
//子进程等待父进程结束
pid_t pid = -1;
pid = fork();
if (pid < 0)
{
perror("fork");
exit(-1);
}
if (pid > 0)
{
//这里就是父进程了,直接退出
exit(0);
}

if (pid ==0 )
{
//下面就是子进程

//(1)调用setsid,建立一个会话期使其与控制台脱离
//原型:pid_t setsid(void);
pid = setsid();
if (-1 == pid)
{
perror("setsid");

}
//(2)将用户工作目录修改到 /目录下,保证安全,
chdir("/");

//(3)将umask设置为0
umask(0);

//(4)关闭所有文件描述符,因为系统不同所以文件描述符的大小也不定,这里我们调用一个函数去动态获取这个值
int cnt = sysconf(_SC_OPEN_MAX);
int i = 0;
for (i=0; i<cnt; i++)
{
close(i);
}

//(5)将0,1,2重定位到/dev/null
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);

}
return ;
}


利用openlog,syslog,closelog将调试信息
(请阐述syslog函数的工作原理)
linux下有一个后台守护进程syslogd,当我们需要系统信息时,syslogd会和syslog,之间开辟一条通道去传输,由此实现了syslog记录日志信息的功能。
这种服务性质的原理在操作系统中有很多
log日志信息在/var/log/syslog下查看
#include <stdio.h>
#include <syslog.h>
/************************
*	syslog调试信息      *
************************/
//往日志信息里输入调试信息
int main(int argc, const char *argv[])
{
//(1)void openlog(const char *ident, int option, int facility);
openlog("a.out", LOG_PID | LOG_CONS, LOG_USER);
//上面这个a.out,其实就是你指定一个标号,用来查询日志信息。可以任意修改

//(2)void syslog(int priority, const char *format, ...);
syslog(LOG_INFO, "This is syslog for a.outlog");
syslog(LOG_INFO, "This is syslog for a.outlog");
syslog(LOG_INFO, "This is syslog for a.outlog");
//(3)void closelog(void);
closelog();

return 0;
}


让程序单一运行:
原理:就是在程序运行是创建一个文件,然后当有进程要运行时去检测这个文件在不在,如果存在,就表示进程正在运行,否则就不在运行;
利用的函数
注意创建文件的目录。如果在根目录下,那么权限可能有问题。
(1)open()O_CREAT, O_EXEC(如果文件存在则会报错,errno会被置位,可以通过errno判断)
(2)atexit需要在结束时清理掉创建的文件

(3)remove删除文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
/************************
* 让程序单次运行的技巧 *
************************/
//原理:当程序执行时创建一个怪异的文件,当程序再次执行时去检查这个文件是否存在,存在则禁止执行,当程序结束则删除这个文件。
void delay_file(void);
#define FILE_NAME	"/var/my_singel" //注意,在根目录下的权限,一般是无法创建文件的所以可能无法执行创建文件操作
int main(int argc, const char *argv[])
{
int fd = -1;
fd = open(FILE_NAME, O_RDWR | O_TRUNC | O_CREAT | O_EXCL, 0666);
if (fd < 0)
{
if (errno == EEXIST)
{
printf("请勿重复运行.\n");
return -1;
}
}

printf("打开成功.\n");

atexit(delay_file);

int i = 20;
while (i-->0)
{
printf("I am runing ....\n");
sleep(1);
}

return 0;
}

void delay_file(void)
{
remove(FILE_NAME);
}


LINUX下IPC(进程间通信)
管道 (无名管道):

(1)原理:就是在内核中开辟一段空间(pipe),然后利用子进程继承父进程的文件描述符的特性,然后进行读写,但是通信方式是半双工的,就是同时智能实现单边通信(pipe)创建后,会返回两个文件描述符,分别读写。
(父进程fork子进程后,子进程继承父进程的管道fd,然后进行通信,发送端、接收端同时只能一个)

(2)缺点:只是适用于父子进程间通信。

如图则是双工的实现

管道 (有名管道):fifo
通过一个指定的文件名,利用mkfifo,利用open分别获取这个文件的fd,然后一个读一个写。

system V IPC
(1)消息队列(fifo)
类似于链表先入先出,进程间通信时通过建立一个消息队列,一个进程发,一个进程从另一头读

(2)信号量
有点类似于编程思维中的flg,标号,其实信号量就是一个变量,当信号量为特定数时开始发送信号,信号量为特定数时开始接收信号。
(3)共享内存

linux下,前面两种通信速率都比较慢,而共享内存的通信办法就比较快速,这里是通过在内核中开辟一块内存,进程1将自己需要和进程2通信的信息放在这块内存中,进程2去读取即可,这里的内存和进程1,2之间有这虚拟映射,就避免了给进程2拷贝的副作用(如果不用共享内存则通信的话就需要拷贝)
举个栗子:
进程1是摄像头程序,它需要将拍的照片,传送给进程2处理压缩、编解码,那么有两种办法
(1)拷一份给他
(2)就是用上面的共享内存,但是共享内存有一个问题,万一进程1还没往共享内存里放完,进程2就开始读了,怎么办?这里就又需要信号的配合了,其实许多硬件都有这样的特性,共享内存有这广泛的使用。


信号
(1)SIGINT 2

Ctrl+C时OS送给前台进程组中每个进程
(2)SIGABRT 6

调用abort函数,进程异常终止
(3)SIGPOLL SIGIO 8

指示一个异步IO事件,在高级IO中提及
(4)SIGKILL 9

杀死进程的终极办法
(5)SIGSEGV 11

无效存储访问时OS发出该信号
(6)SIGPIPE 13
涉及管道和socket
(7)SIGALRM 14
涉及alarm函数的实现
(8)SIGTERM 15

kill命令发送的OS默认终止信号
(9)SIGCHLD 17

子进程终止或停止时OS向其父进程发此信号
(10)SIGUSR1 10

用户自定义信号,作用和意义由应用自己定义
SIGUSR2 12

两个函数
(1)signal

(2)sigactonal
很类似,
(3)alarm
定时闹钟,同时操作系统只能响应一个alarm,所以alarm会覆盖前面。
(4)pause
将cpu挂起。
//宏的定义在/usr/include/i386-linux-gnu/bits/signum.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: