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

linux程序设计学习笔记(7-15)

2016-04-22 18:51 369 查看
七、数据管理内存管理malloc,free,realloc和windows都一样,都是ANSI C。实际上,应用程序并没有直接访问到物理内存,也可以通过malloc获得比实际内存大得多的内存空间,因为系统会使用交换空间(swap space ,可以理解为windows的虚拟内存),如果申请的内存大于物理内存和交换空间,那么系统将提前终止这个进程。
如果在申请的内存里进程指针操作,当移动比如++出了这段分配的内存范围之外,系统会自动报警。
如果向一个空指针里复制数据,会发出越界报警。分配的内存结构定义
struct mem_control_block {
int is_available;
int size;
};详细的内容可以看这篇文章《内存管理内幕》
http://www-128.ibm.com/developerworks/cn/linux/l-memory/
文件锁定使用O_CREAT|O_EXCL的特点可以实现锁文件的效果,书上是用来做进程之间的协调。
但我觉得用来给一个程序只启动一个实例比较实用,但是效果不好,因为采用删除文件的做法,一旦遇到上一个进程异常退出,后面起新进程就会比较麻烦。
所以还是用
fd=open(filename,O_WRONLY|O_CREAT, 0644);
if(flock(fd,LOCK_EX|LOCK_NB)){}
可以更好实现一个程序只有一个实例fcntl和lockf使用不同的底层实现,他们不能同时工作,所以不能混合使用它们。
fcntl可以锁定文件的部分区域。
int fcntl(int fd,int cmd,struct flock * lock);
fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词,参数cmd代表欲操作的指令,参数lock是锁信息,里面规定了锁类型和具体的文件区域。
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
flcok 结构的ll_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定,也就是共享锁,多个进程可以对同一区域共享锁,只要设置了某一区域的共享锁,就再也不能设置独占锁了,值为0。
F_WRLCK 建立一个供写入用的锁定,也就是独占锁,一旦设置后,这个区域就不能再设置任何锁了,值为1。
F_UNLCK 删除之前建立的锁定,也就是解锁。
其他参数见P223,主要是设置具体位置还有加锁的进程号。
返回值 成功则返回0,若有错误则返回-1,错误原因存于errno.注意对文件区域加锁后,一个用read和write调用,而不要使用fread和fwrite ,因为fread和fwrite 有自己的缓冲区。另外一个函数lockf类似,不过注意一点:这2个函数都是建议性的锁,并不会真正的阻止我们从文件中读取或是向文件中写数据。对锁的检测是应用程序的责任。fcntl和lockf的锁不一样,混合用的话会互不相认。八、mysql一些mysql数据库的操作可以看《零散的一些东西》。如果出错获得使用函数mysql_error(MYSQL m_connection)返回的出错码,mysql_errno(MYSQL m_connection)返回字符串也就是出错信息。
具体对应可以errmsg.h mysqld_error.h 。具体可以看这里《MySQL 错误代码以及出错信息对照大全》
http://www.e-gov.org.cn/xuexiyuandi/cunchu/200703/51606.html注意mysql_affected_rows是返回这次mysql_query操作改变的多少行,而不是where匹配的多少行,其实就是我们在直接操作mysql后显示的那个影响多少行。如果是delete所有数据,那么返回的是0 。mysql_field_count可以返回这次查询获得了多少列,这样在sqlrow[]这个数组里使用就不会越界了。注意mysql_store_result和mysql_use_result的区别:
1、 mysql_use_result()的结果必须“一次性用完”,也就是说用它得到一个result后,必须反复用mysql_fetch_row()读取其结果直至该函数返回null为止,否则如果你再次进行mysql查询,会得到“Commands out of sync; you can't run this command now”的错误。而mysql_store_result()得到result是存下来的,你无需把全部行结果读完,就可以进行另外的查询。比如你进行一个查询,得到一系列记录,再根据这些结果,用一个循环再进行数据库查询,就只能用mysql_store_result()。
2、mysql_store_result查询的结果使用使用mysql_num_rows获得的结果是总行数。但是mysql_use_result查询的结果使用使用mysql_num_rows获得的结果是当前行行号。(这点差点让我头大,当时一直奇怪怎么得不到总行数)
3、远程访问数据库占用的内存不一样。mysql_store_result不占用服务端内存的,但是客户端的内存是急剧上升的,待执行完mysql_free_result后客户端内存被释放。mysql_use_result不占用客户端内存,对服务器端内存占用也不大,但是使用mysql_use_result后服务器端内存增加的很明显。其本上都很简单,在mysql_query里直接使用mysql语句就行了。九、开发工具我目前用kdevelop,略。关于kdevelop,里面很多设置都很奇怪,比如给工程增加已存在的文件,需要在很隐蔽的右边栏里的automake里选择添加文件,刚一开始,我要添加已有的文件,只好新建一个同名空文件,然后覆盖掉。具体的设置,可以看这篇文章:
http://blog.csdn.net/linux2linux/archive/2007/12/07/1923231.aspx
不过这工具有个麻烦,调试的时候带不就去参数,只有运行时设置的参数才能带到程序里去,真郁闷。十、调试我目前用kdevelop,略。
像VC一样很方便,里面也可以使用gbd的命令,kdevelop里面没有专门看内存的工具,所以只好用gbd命令了,参数是x\数字sh 0x111111 数字一般是1,显示到\0的字符串,如果这个区域里可能有0,那就要把数字加大了。十一、进程和信号每一个进程都用自己的唯一数字编号,成为PID,代码中获得是getpid ,命令行里获得是ps aux
PID取值范围是2到32768,新进程选择比最大的PID大1的数字做自己的PID,超过最大值重新从2开始。
数字1是init专用的,设置后台进程(不是后台运行,带&参数运行的程序,当断开连接后,将停止运行)也就是僵尸进程,就需要挂在init上,一般设置的方法是,父进程开启一个子进程,然后杀死父进程,这样子进程就挂在init上了。execX是一系列替换函数,实际作用就是把新应用程序的代码替换当前的这条语句。
(与system不同,system是用阻塞的方式调用一个外部命令,其实就是先启动一个shell,再使用shell调用应用程序,除非加参数&否则不运行完是不会返回的。说白了就是和脚本编程一样。)
另外我发现execX系列使用不能执行python的程序呀(当然python是解释性语言,可能中间流程哪里就不对了)fork是复制是自己生成一个子进程,也就是说使用fork又对同样程序启动了一个新进程。
区别是,父进程调用的fork返回子进程号,子进程的fork这一步,返回的是0 。这也就是我们常用的,返回值是0的,说明自己是子进程,返回值非0的,说明自己的父进程。wait这个函数可以使得父进程暂停,直到子进程结束,返回已停止的子进程的PID。参见P391的例子。注意!就算子进程先退出,主进程再进行wait,也可以在wait的返回值里得到子进程的进程号,同样使用WIFEXITED也可以得到子进程的exit返回数值。
原因很简单,子进程在父进程还没有退出时,虽然不运行了,但是并没有真正退出,它仍然存在系统中,直到父进程正常退出或父进程调用wait。此时的子进程也可以被称作僵尸进程,不过这种僵尸进程没什么用。
如果子进程进入僵尸状态,而父进程异常退出,则子进程将init作为自己的父进程,直到被init发现并释放。还有一个命令waitpid也有类似的效果,而且它可以制定子进程号并设置有各种处理行为。周期性检查子进程是否结束可以用waitpid(child_pid, (int *) 0, WNOHANG); 子进程没有结束返回0,已经结束返回pid,失败返回-1 ,错误信息放在errno里。书里说进程编写比线程简单,编写多个互相协作的进程比线程容易,可能我是从windows转移过来的,完全没有这个感觉,我还是喜欢用线程,不知道继续学习下去是不是会改变。信号:感觉有点像windows里的消息,可以给特定进程发送信号,还可以发送自定义信号(自定义信号数值从32到64)并引发自定义函数。不过它会打断sleep、在主线也就是main里执行函数,哪怕你是把sign调用写在了子线程里面,而且最主要的是,自定义函数还没有处理完这个消息,再发过来的新消息都会被丢弃掉,比windows的消息差远了,而且不能带参数,而且是在主线程里执行,天知道会不会有什么问题。1、信号可以打断main里的sleep,无论睡眠多少秒。
2、就算alarm和signal在子线程里定义和调用,一切处理还是在main即主线程来处理的,子线程里的sleep不会被中断。
3、.SIG_IGN 忽略这个信号,SIG_DFL 恢复对这个信号的默认处理。
4、linux的大多数信号是提供给内核的,仅有少数几种信号可以在信号间发送,但是32到64在定义中是空的,可以留给用户自己使用。
5、利用kill可以给制定进程发送信号量,这里可以发送自定义的信号量,在另外一个进程里对这个信号量给于自定义函数。
6、pause()会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断。
7、注意,如果是在2个进程中传递,那么效果是非阻塞的POSTMESSAGE消息。另外消息不被保存,也就是说如果自定义函数还没有处理完这个消息,再发过来的新消息都会被丢弃掉,kill不会返回-1,会返回0,但是实际编码中,后面的信号没有被接受。
8、killall可以给所以同一个应用程序启动的进程发信号。
9、如果发送信号时,进程处于select等待,那么将返回-1 。
10、如果进程没有处理这个信号,无论是系统的信号还是用户自定义的信号,都会立刻杀死进程。如果ctrl+z变成后台处理,则会无视这些信号。但如果是加&启动,还是会被这些未定义信号杀死。具体关于信号的一些代码,可以去看《linux函数整理及杂项》十二、线程记得以前上大学的时候,我们用的《操作系统》一书里还说linux是没有线程的,线程是windows的概念。时代发展的真快。
不过如果说linux没有线程,也不是完全没有道理,至少linux里需要专门的线程库才能使用到线程(pthread.h -lpthread)。
正因为一直在改进,所以本书的代码有几个函数在我们的CS5上还有问题,不过只是一丁点无关大局,也可以看出linux线程在逐渐的规范。pthread_exit(void* retval) 编程发现,这里不能给常量比如字符串,和书上不一致,但是可用null,另外如果2个子线程用同一个全局变量(注意不能给局部变量,子线程退出,堆栈就清空了),并且2个线程在调用pthread_exit时都用它并设置成不同的值,那么肯定冲突,即在2个pthread_join里获得的结果2个显示的都是同一个值,即使它们的退出时间不一致。
如果子线程没有退出,主线程先走完,或主线程调用pthread_exit,整个进程直接退出,子线程也自动结束。
pthread_join是等待制定的子线程结束,否则一直阻塞。使用信号量和互斥量同步,名字不一样,但是功能和windows上差不多。
信号量同步sem_post是以原子操作给某个信号量+1,原子操作,就是不能被更高等级中断抢夺优先的操作,只有它执行完毕了,其他高优先级的才能运行。而其他操作可能被更高优先级的中断,比如信号可以中断sleep等。
sem_wait是以原子操作给信号量-1,如果如果发现当前值已经是0了,那么就不会进行减操作,而是进入等待状态,直到有sem_post给信号量加1,如果当前值非0,那么-1后继续运行。
在2个线程同步的时候,往往需要2个信号量,一个开一个关。pthread_mutex_lock第一次运行的时候不会被锁住,相当于sem_init给的初始值是1的情况。当第二个线程调用pthread_mutex_lock的时候,就阻塞住了,然后等待有其他线程pthread_mutex_unlock。
实际编码的时候发现,原来stdin的标准输入的缓存区是可以存储多次输入的,例子P420,如果sleep的时间改长,连续输入,每次gets获取的值都是正确的。如果对2个线程进行3次加锁,那么就会死锁,可以通过修改pthread_mutex_init第二个参数来改变,比如返回错误或者递归加锁,平时只传空就可以了,感觉在与其他人进行不透明的接口合作的时候才会用到,因为那些人会在给他们的回调函数里调用其他接口,然后造成死锁,以前我处理的方法是,推荐合作方用postmessage来处理,但如果用递归加锁的方法来的话,其实还是挺麻烦的。其实还有种可能引起死锁,比如A,B 2个方法入口都有同一个锁,那么A方法调用B方法的时候就可能引起死锁。可以使用pthread_mutexattr_settype来设置返回错误(PTHREAD_MUTEX_ERRORCHECK)或者递归加锁(PTHREAD_MUTEX_RECURSIVE)。线程的属性可以通过pthread_attr_xxx设置,不过感觉没什么需要动的,唯一需要的估计就是stacksize,不过默认值很大,而且有些不支持,所以感觉用处不大。pthread_cancel可以停止制定的线程,包括停止自己。就算子线程内不设置具体类型,也一样可以用这个函数。十三、进程间通信:管道管道是用来在进程之间交互数据的。个人感觉linux里面通过进程名字找进程号挺麻烦的,可能是在windows上惯坏了,也可能是我没有找到这样的库吧。很多时候,只用用popen来执行一些shell命令,然后获得输出的内容。最后,也是最麻烦的一步——整理字符串。虽然获得对获得的FILE指针用操作文件的函数处理,但是关闭的时候用pclose 。简单的管道就是pipe调用了,将读写放在一个2元素的数组中,标号1是写入用的,标号2是读取用的,遵守先进先出法的原则。然后一个进程write一个进程read(如果没有数据就会阻塞住,如果文件描述关闭了,就会返回-1)
注意可以3个或3个进程以上使用这个pipe调用,不过某个进程读取一个数据后,其他的就只能读取下一个数据了。
但是这个主要是用在父进程和子进程之间的,在不相干的进程之间使用挺麻烦的,除非传递文件描述符作为参数给新进程。
命名管道是一种特殊的文件(当然linux里一切都是文件),在命令行里可以使用mkfifo,在c里也是一样的名字。
不能用O_RDWR打开命名管道,因为一般都是单项传输的,如果要双向,建议开2个命名管道。
可以用open打开文件可以用3个参数4中组合,O_RDONLY,O_WRONLY,O_NONBLOCK
O_RDONLY只读,如果没有另一个进程进行写打开就会阻塞。
O_RDONLY|O_NONBLOCK必然成功并立即返回。
O_WRONLY只写,如果没有另一个进程进行读打开就会阻塞。
O_WRONLY|O_NONBLOCK如果没有进程读打开则返回-1十四:信号量、共享内存、消息队列主要是作用于进程之间的协作的,哎,没一个比windows的自定义消息好用,关键我到现在还没有找到,不建立任何管道或socket或任何其他处理,就可以通过进程号直接通知其他进程处理任务的,信号虽然可以,但是不能带参数,而且处理函数没结束再发的会被丢弃。信号量用来协调进程与线程,我觉得多进程里如果要读写文件用文件锁就够了,主要用来协调共享内存吧。
注意如果没有用semctl把val值设置为1,那么默认为0 。
P操作就是先对val值减1,如果小于0,就阻塞,知道有人进行V操作,其实就是加1 。共享内存允许2个不想干的程序访问同一个逻辑内存,传递起来比较简单,但是没有同步机制,需要使用其他方法来同步,比如用信号或信号量。消息队列是比较有趣的,首先是异步消息,独立与发送和接受进程而存在,可以传递数据,先进先出,可以多个进程添加消息,多个进程读取消息,不过一个进程读取后,这个消息就从消息队列里消失了。
另外在发送的时候不用自己申请内存,也不用担心数据被修改,系统会自动复制一份数据放在自己的队列里。这3种方法都用一个常数key来标记,以便多个进程可以公用同一个信号量、共享内存或信息队列。十五:套接字:以前一直不明白socket的第一个参数有什么用,原来除了网络通信,也可以用AF_UNIX来进行本地进程间的通信。
用一个字符串做标示后,就可以对它进行读写操作了,感觉没有通道好用。至于AF_INET和windows里的没有区别。socket多连接下简单的处理就是针对每个连接开一个进程,资源占用高。或者使用select机制。select如果遇到超时返回0,如果出错返回-1,其他值是socket相应。
第一个参数是需要测试的套接字数目,最好是最大的socket值+1 。
关于超时时间,最好每个循环再重新设置一次,不同系统可能不同处理,会自动修改超时时间这个参数为剩余时间,可能会影响逻辑性。如果要设置非阻塞,可以用fcntl设置。
int setnonblock(int sockId)
{
int f_old;
if(sockId < 0)
{
return -1;
}
f_old = fcntl(sockId, F_GETFL, 0);
if(-1 == f_old) return sockId;
f_old |= O_NONBLOCK;return (fcntl(sockId, F_SETFL, f_old));}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 程序 windows