Linux文件/IO,应用编程
2017-07-11 11:14
148 查看
-----------------------应用框架结构------------------------------• 应用编程框架介绍信号:多线程:(多核心CPU)-----------------------------------------------------------------------------------------------------------------------------------------------------------• 文件操作的一些系统API什么是操作系统API?:API其实就是一些函数,APP通过操作系统提供的API来使用硬件干活,而硬件如何干活,则就是驱动程序的工作了。
Linux一些常用的文件IO接口:
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构造空洞文件
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 打开多个文件(文件共享)
两个文件指针(两种情况)
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给你。
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 标准输入输出
Fopen Fclose Fwrite Fread Ffulsh Fseek
Fread r+
Fwrite w+
在fclose后一定要将fp指针归0.
fp = NULL;
-----------------------------------------------------------------------
------------------------------------------------------------------------------------
• 文件属性
文件属性的获取
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
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可以指定回收某个进程号,所以当我们指定一个不存在的进程号,而且采用阻塞式的,那么它就会返回一个错误,因为操作系统的设计是很合理的,不可能让程序在原地卡死。
•exec族函数:
(请阐述exec族函数调用的原理)
通过传参,指定要执行程序的路径,然后执行程序
export:终端显示系统的环境变量
echo &PATH 。。。。
execl,直接加参数,参数后要加NULL
execv,通过定义一个字符串数组指针,要加NULL
execlp,execvp,
如果不加p,那么让找不到给定路径的程序时,则会返回错误值,加了p以后如果找不到就回去系统默认添加的PATH里去找。
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 垃圾堆。
利用openlog,syslog,closelog将调试信息
(请阐述syslog函数的工作原理)
linux下有一个后台守护进程syslogd,当我们需要系统信息时,syslogd会和syslog,之间开辟一条通道去传输,由此实现了syslog记录日志信息的功能。
这种服务性质的原理在操作系统中有很多
log日志信息在/var/log/syslog下查看
•让程序单一运行:
原理:就是在程序运行是创建一个文件,然后当有进程要运行时去检测这个文件在不在,如果存在,就表示进程正在运行,否则就不在运行;
利用的函数
注意创建文件的目录。如果在根目录下,那么权限可能有问题。
(1)open()O_CREAT, O_EXEC(如果文件存在则会报错,errno会被置位,可以通过errno判断)
(2)atexit需要在结束时清理掉创建的文件
(3)remove删除文件
•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.
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.
相关文章推荐
- Linux应用编程基础--(2)文件IO
- 嵌入式成长轨迹17 【Linux应用编程强化】【Linux下的C编程 下】【文件系统编程】
- linux系统编程之文件与IO(四):目录访问相关系统调用
- Linux编程-标准IO(4)-临时文件
- 七、Linux系统编程-文件和IO(五)fcntl函数及常用操作、文件锁
- Linux下C编程-----IO/文件操作/内存映射 实现简单记录存储(3)
- Linux环境高级编程:文件IO
- Linux下C编程入门笔记——文件IO操作(一)
- Linux应用编程【0】文件系统编程之open函数
- Linux学习记录--文件IO操作相关系统编程
- Linux下C编程-----IO/文件操作 模拟linux ls程序显示文件系统树形结构(2)
- linux带缓存IO文件 复制应用
- Unix/Linux C++应用开发-文件系统编程
- Linux应用编程【1】文件系统编程之几个头文件
- Linux下C编程入门笔记——文件IO操作(二)
- linux系统编程之文件与IO(一):文件描述符、open,close
- linux系统编程之文件与IO:stat()系统调用获取文件信息
- 【linux草鞋应用编程系列】_1_ 开篇_系统调用IO接口与标准IO接口
- 四、Linux系统编程-文件和IO(二)文件的读写
- linux系统编程之文件与io(四)